-
Notifications
You must be signed in to change notification settings - Fork 3.1k
SI-9111 / SI-10107 resolution of static references in Java code #5606
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
This version changes the mode for mixed compilation, which used to be 1. scalac *.java *.scala -d o 2. javac *.java -d o -cp o 3. scalac *.scala -d o -cp o Now the third step is skipped. This required some adjustments to existing tests. - t7014 is split in two groups, the fix is for separate compilation. - t7582 is also split. It tests inliner warnings when inling code that accesses Java-defined package-private code. Inlining from Java only works in separate compilation (no bytecode available in mixed compilation). - Java compiler warnings of "run" tests were not reported in the old scheme, now they are. Deprecation / unchecked warnings were removed from t6240, t8786, varargs. - t4788 required a .check file update to pass, which hints at a bug. I will re-open SI-4788 and investigate later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Great! I have a few small suggestions.
val searchCompanions = isPlainClassInJavaUnit(pre.typeSymbol) | ||
val sym = pre.findMember(name, excludedFlags = Flags.BridgeFlags, requiredFlags = 0, stableOnly = false, searchCompanions).filter(qualifies) | ||
if (searchCompanions && sym.owner.isModuleClass) | ||
pre = sym.owner.typeOfThis |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Much neater than companionSymbolOf
!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we ever get here while completing a lazy type for sym.owner
? Maybe that's not possible because of the Java-specific code path here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sym
is a member, and sym.owner
is a module class. I think when completing a lazy type of a class, its members' types are not completed.
@@ -984,6 +985,10 @@ trait Contexts { self: Analyzer => | |||
|
|||
def isNameInScope(name: Name) = lookupSymbol(name, _ => true).isSuccess | |||
|
|||
def isPlainClassInJavaUnit(sym: Symbol): Boolean = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would it be possible/make sense to get the unit
from sym
, so that we can encapsulate this logic in findMember
instead of computing searchCompanions
at its call sites?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are many uses of findMember
other than the ones updated in this PR. This PR only searches in companions when type-checking Ident
s or Select
s while type-checking a Java source file. For example, given
public class A { public static class T }
public class B extends A
A reference B.T
resolves to A.T
when it appears inside a Java source file, but should (?) give an error when inside a Scala source file. So we need to check context.unit.isJava
, not only sym.isJavaDefined
.
There's no compilationUnit
on symbols, only sourceFile
, but that's says where a symbol is defined. The context.unit.isJava
check is about where the symbol is referenced.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about:
class HasStatic { static void foo() {} }
class Client { void test() { ScalaSubclass.foo(); } }
class ScalaSubclass extends HasStatic
This shows that a Java client can access a Java static selected from a Scala subclass of the owner of the static member.
I think we should drop the check for sym.isJavaDefined
here, and instead only use this to filter out companions in FindMember
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree with your point of moving the sym.isJavaDefined
check into FindMember
. However, your example is not going to work, and it will be very hard to support it:
// A.java
public class A {
public static class C {
public static class I { }
}
public static class E extends D.I { }
}
// Test.scala
class D extends A.C
object Test {
new A.E()
}
In mixed compilation:
A.java:5: error: not found: value D
public static class E extends D.I { }
^
one error found
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It works if D
has a companion object. It also works in separate compilation.
Thinking more about it, we should support D.I
in a Java source even if D
is defined in Scala and not a value. This would be very similar as the special case being implemented in this PR (allow A.T
if A is a java class).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To make it a little trickier:
// Java
public class A {
public static class I { }
}
// Scala
class B {
// class I // (1)
}
class C extends A
object C extends B {
// class I // (2)
}
What is C.I
in a Java source? In separate compilation (mixed compilation should behave the same):
- With 1 and 2 commented,
C.I
(in a Java source) resolves toA$I
- Un-commenting 1 doesn't change anything: class
B
is not a parent of the classC
- it's a parent of the module classC$
. If a Java source referencesC.I
, the Java compiler doesn't look inC$
. - Un-commenting 2 triggers some compat-mode in the
InnerClass
attribute and marksI
as being a static member class ofC
(not ofC$
), so aC.I
reference in Java resolves toC$I
.
So when we are in a Java compilation unit and look for a member of a module class C
we should
- first check the
C
module class (but not any of its parents) - then the
C
class and its parents, and also all parents module classes
If we also start type checking Java terms (in the future) things would get more complicated. Right now, in findMember
, we don't know whether we're looking for a static or instance member. A type selection Outer.Inner
, it can resolve to a static or non-static inner class, the same syntax is used for both. For terms, A.foo
should not return an instance method.
Related older fix I wasn't aware of: scala/bug#3120. When type-checking a selection A.foo
fails in a Java compilation unit, a synthetic SelectFromTypeTree
tree will be type-checked as second try.
// For Java compilation units, a `T` or `A.T` can refer to a static inner class `T`. Static inner | ||
// classes are placed in the companion object. Under `searchCompanions`, we always check the companion | ||
// before proceeding to the next base class. | ||
if (searchCompanions) bcs.flatMap(bc => List(bc, bc.companionModule.moduleClass)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you add some more details from your commit message here? Maybe some variation of:
In Java, static members are inherited, so
A.T
can refer to a static member defined in a parent ofA
. In Scala we model static members as members of the class's companion object. In addition to the inheritance class hierarchy, we must be careful to follow the implied parallel hierarchy of the companion objects.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, will do
Fixed a while ago, according to comments on the issue.
Scala puts static members of Java classes into the companion object symbol. When parsing Java code, references to static members need to be looked up in these companions. In 2.11.x, a synthetic `import A._` was added to Java classes having static members, which lead to some bugs (SI-9111). The scheme was changed in 4e822d7 (2.12.x only): when a lookup of `T` / `A.T` fails, the typer searches in the companion of the current class. This fixes some issues, but not all - In Java, `A.T` can refer to a static member defined in a parent of `A` - If a static `A.T` exists, but a parent of `A` defines a non-static member class `T`, the non-static one would be chosen (wrongly) This commit pushes the logic to consider companion objects into `FindMembers` and makes sure that for ever class in the base type sequence, the companion is considered before moving to the next base type.
SI-4788 implemented support for `@Retention` annotations of Java annotation classes: if a Java annotation class is itself annotated `@Retention(value=SOURCE)`, it is not emitted to the annotated (Scala) classfile. However, the Java parser currently skips annotations altogether, so if a Java annotation is passed to the Scala compiler in mixed compilation, its `@Retention` not respected. Instead, uses of the annotation class will be written to the classfile. The existing tests for t4788 basically both tested separate compilation, because partest's "mixed" compilation used to compile Scala sources twice (the second time with the Java classfiles on the classpath). This was fixed recently, uncovering the difference. Additionally, the actual `t4788-separate-compilation` did not work as intended because the filename groups were wrong (the `.scala` files should all have been in the `_2` group.).
@@ -984,6 +985,10 @@ trait Contexts { self: Analyzer => | |||
|
|||
def isNameInScope(name: Name) = lookupSymbol(name, _ => true).isSuccess | |||
|
|||
def isPlainClassInJavaUnit(sym: Symbol): Boolean = | |||
unit.isJava && sym.isJavaDefined && sym.isClass && !sym.isModuleClass && !sym.isPackageClass |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why exclude sym.isModuleClass
? I'm trying to get this scenario to work (using scaladoc to more eagerly force infos):
public class A {
public static class T {}
}
public class B extends A {
public static class U extends T { }
// ^
// error: not found: type T
}
// members: `A.T` can resolve to a static member defined in a parent of `A`. | ||
// class C { class T }; class B extends C { static class T }; class A extends B | ||
// Here, `A.T` resolves to the static class `B.T`, so we need to check B's companion before proceeding to C. | ||
if (searchCompanions) bcs.flatMap(bc => List(bc, bc.companionModule.moduleClass)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Based on my example above, I think we also need to support the case where we're looking for the base classes of a module class. The companion modules of its companion class's base classes should also be included.
Improvements in this area make it easier for us to typecheck more Java code, which we need to do (robustly) to improve support for annotations in jointly compiled sources (e.g. for the SI-4788 limitation noted above), or to typecheck the RHS of constant expressions. I like your approach of addressing this at its core, in However, we'll have to be quite careful in making membership dependent on the context of the unit containing the reference. Will this break any caches? I can't find any problems right now. e.g. |
I'm going to put this on hold, busy with other issues for now. Hope to pick it up for 2.12.2. |
Hello, Is there anything I can do to help so that this gets fixed in, say, 2.12.4? (I assume it's already too late for 2.12.3). It really makes me frustrated, because it's a regression, blocking us from migration to Scala 2.12, significant work had already been put into it and now it seems like it's stuck in a limbo. |
I'm sorry this slipped, I really need to pick it up again for 2.12.4. I have it on my list, but it just keeps getting bumped. I will take a look, keeping in mind that perfect is the enemy of good. |
Hi, Where can we track the evolution of this bug ? Thanks |
Thanks |
Scala puts static members of Java classes into the companion
object symbol. When parsing Java code, references to static
members need to be looked up in these companions.
In 2.11.x, a synthetic
import A._
was added to Java classeshaving static members, which lead to some bugs (SI-9111). The
scheme was changed in 4e822d7 (2.12.x only): when a lookup
of
T
/A.T
fails, the typer searches in the companion of thecurrent class.
This fixes some issues, but not all
A.T
can refer to a static member defined in aparent of
A
A.T
exists, but a parent ofA
defines anon-static member class
T
, the non-static one would bechosen (wrongly)
This commit pushes the logic to consider companion objects into
FindMembers
and makes sure that for ever class in the base typesequence, the companion is considered before moving to the next
base type.