bug icon indicating copy to clipboard operation
bug copied to clipboard

Java parser/typer rejects import of Java inner class

Open scabug opened this issue 12 years ago • 10 comments

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 )

scabug avatar Jul 17 '13 21:07 scabug

Imported From: https://issues.scala-lang.org/browse/SI-7675?orig=1 Reporter: Donny Nadolny (dnadolny) Affected Versions: 2.10.2 See #3120

scabug avatar Jul 17 '13 21:07 scabug

@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.*.

scabug avatar Jul 18 '13 08:07 scabug

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?

scabug avatar Jul 18 '13 12:07 scabug

@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.

scabug avatar Jul 18 '13 15:07 scabug

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".

scabug avatar Jul 18 '13 19:07 scabug

@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
            )

scabug avatar Jul 22 '13 04:07 scabug

@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.

scabug avatar Jul 22 '13 13:07 scabug

@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

scabug avatar Jul 24 '13 05:07 scabug

@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.

scabug avatar Feb 13 '14 17:02 scabug

@retronym said: See also #3120. I think the fix for this ticket needs to extend the approach taken for that one.

scabug avatar Feb 13 '14 17:02 scabug