bug
bug copied to clipboard
Java parser/typer rejects import of Java inner class
If you have Scala code with an inner class and attempt to use a wildcard import in Java so that you can use that class name unqualified, the Scala compiler will give an error when reading in that Java source.
Here is an example:
// scalapkg/Main.scala
package scalapkg
import javapkg.JavaClass
object Main extends App {
println(new JavaClass().num)
}
class SomeScala {
class InnerClass
}
// javapkg/JavaClass.java
package javapkg;
import scalapkg.SomeScala.*;
public class JavaClass {
public int num = 1;
InnerClass inner = null;
}
Compile with:
dnadolny@donny-desktop:~/tools/scala-2.10.2/bin$ ./scalac javapkg/*.java scalapkg/*.scala
javapkg/JavaClass.java:3: error: object SomeScala is not a member of package scalapkg
Note: class SomeScala exists, but it has no companion object.
import scalapkg.SomeScala.*;
^
one error found
This is valid Java code, and would work if SomeScala was compiled first, then JavaClass compiled depending on the SomeScala*.class files.
A workaround is using the partially qualified name "SomeScala.Inner" rather than "Inner"
(This came up on the mailing list, https://groups.google.com/forum/#!topic/scala-ide-user/zkGaA_-uig8 )
Imported From: https://issues.scala-lang.org/browse/SI-7675?orig=1 Reporter: Donny Nadolny (dnadolny) Affected Versions: 2.10.2 See #3120
@paulp said: It's only valid java code because java doesn't know anything about scala. That it would work compiling against the class files is a manifestation of the same thing. But scalac doesn't have to be ignorant of its own language semantics just because javac is. As it says, there is no object SomeScala from which to import SomeScala.*.
Donny Nadolny (dnadolny) said: There is no object SomeScala, but in Java that import means you can refer to the inner class unqualified.
Given this Scala: {code:title=scalapkg/Main.scala} package scalapkg
import javapkg.JavaClass
object Main extends App { val someScala = new SomeScala("abc") new JavaClass().useInner(new someScala.InnerClass) }
class SomeScala(str: String) { class InnerClass { def printStr() = println(str) } }
This compiles and runs when passed as source to scala:
{code:title=javapkg/JavaClass.java}
package javapkg;
import scalapkg.SomeScala;
public class JavaClass {
public void useInner(Object probablyInner) {
SomeScala.InnerClass inner = (SomeScala.InnerClass) probablyInner;
inner.printStr();
}
}
This would be compiled by java to the exact same .class file as the one above, but it does not compile when passed to scala as source: {code:title=javapkg/JavaClass.java} package javapkg;
import scalapkg.SomeScala.*;
public class JavaClass { public void useInner(Object probablyInner) { InnerClass inner = InnerClass probablyInner; inner.printStr(); } } {code}
When you put "SomeScala.InnerClass" in the signature for "JavaClass.useInner", instead of Object, then the scala compiler complains (although the Java compiler allows it, and it runs and does what you expect). So maybe this is just a case of scala only rejecting method signatures that it doesn't like, rather than checking the body of the method.
How are you supposed to use a scala inner class from java?
@paulp said: "How are you supposed to use a scala inner class from java?"
The question presupposes that you're supposed to. It's sure to be unsound - there are good reasons you can't "new Foo#Bar" in scala - and plenty of scala features are effectively inaccessible from java.
Donny Nadolny (dnadolny) said: Maybe I'm missing something, but: The code isn't trying to do "new Foo#Bar" (which the Java compiler would already reject), it's just using Foo#Bar in a method signature. Is there a good reason why this scala class works: {code:title=scala} class Whatever { def useInner(inner: SomeScala#InnerClass) = inner.printStr }
but this java class shouldn't?
{code:title=java}
class JavaClass {
public void useInner(SomeScala.InnerClass inner) {
inner.printStr();
}
}
They're equivalent, aren't they? Yet the scala compiler will reject the 2nd one.
I don't understand why using scala inner classes from java is sure to be unsound. The example above should be fine, and the only unsound usage I can think of isn't prevented by the scala compiler: {code:title=java} SomeScala a = new SomeScala("a"); InnerClass aa = a.new InnerClass(); SomeScala b = new SomeScala("b");
a.useMyInner(aa); //useMyInner is "def useMyInner(inner: InnerClass) = ???" b.useMyInner(aa); //invalid if this was scala, but in java it works even when the scala compiler reads in the source {code}
If you're not convinced, this probably won't convince you but I'll voice my opinion anyway: I would prefer a scala compiler that didn't do extra checks on java code, unless they were just given as warnings. Rejecting java code that would compile with the java compiler means you can't convert your java project to a mixed scala/java project. To anyone who complained that they can use java to do unsound things with scala classes (eg the "b.useMyInner(aa)" example), I think a fair response would be "if you want the safety of the scala compiler, write in scala".
@retronym said: If you have mixed Java/Scala sources, scalac has to parse and typecheck everything other than method/field RHSs. The error isn't coming from an "extra check", its just coming from a flawed attempt to reuse Scala's typechecker for Java sources which have slightly different semantics.
In general, if the Java source can be separately compiled, scalac should also be able to jointly compile it.
We've got this code in Typers which looks like it ought to apply to this case. We'll have to investigate why it doesn't.
if (!reallyExists(sym)) {
def handleMissing: Tree = {
def errorTree = missingSelectErrorTree(tree, qual, name)
def asTypeSelection = (
if (context.unit.isJava && name.isTypeName) {
// SI-3120 Java uses the same syntax, A.B, to express selection from the
// value A and from the type A. We have to try both.
atPos(tree.pos)(gen.convertToSelectFromType(qual, name)) match {
case EmptyTree => None
case tree1 => Some(typed1(tree1, mode, pt))
}
}
else None
)
@paulp said: I lost track of this, but I had meant to add that I was being reflexively contrary. That is supported by the fact that I participated in the writing of that snippet.
@retronym said: I took a stab at allowing this. Didn't get all the way there, but the branch at least shows which spots need to be modified:
https://github.com/retronym/scala/tree/ticket/7675
@retronym said: From a ticket marked as a duplicate:
{code}package p1; public class EnclosingClass { public class NestedClass { } } import p1.EnclosingClass.NestedClass; public class ConsumingClass { NestedClass nc; } {code}
This results in:
NestedClass is not a member of _root_.p1.EnclosingClass.
@retronym said: See also #3120. I think the fix for this ticket needs to extend the approach taken for that one.