Skip to content

Commit cfe98b3

Browse files
committed
SI-9111 / SI-10107 resolution of static references in Java code
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.
1 parent d29d5a9 commit cfe98b3

File tree

17 files changed

+170
-41
lines changed

17 files changed

+170
-41
lines changed

src/compiler/scala/tools/nsc/typechecker/Contexts.scala

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,9 @@
66
package scala.tools.nsc
77
package typechecker
88

9-
import scala.collection.{ immutable, mutable }
9+
import scala.collection.{immutable, mutable}
1010
import scala.annotation.tailrec
11+
import scala.reflect.internal.Flags
1112
import scala.reflect.internal.util.shortClassOfInstance
1213
import scala.tools.nsc.reporters.Reporter
1314

@@ -984,6 +985,10 @@ trait Contexts { self: Analyzer =>
984985

985986
def isNameInScope(name: Name) = lookupSymbol(name, _ => true).isSuccess
986987

988+
def isPlainClassInJavaUnit(sym: Symbol): Boolean =
989+
unit.isJava && sym.isJavaDefined && sym.isClass && !sym.isModuleClass && !sym.isPackageClass
990+
991+
987992
/** Find the symbol of a simple name starting from this context.
988993
* All names are filtered through the "qualifies" predicate,
989994
* the search continuing as long as no qualifying name is found.
@@ -1017,14 +1022,11 @@ trait Contexts { self: Analyzer =>
10171022
)
10181023
)
10191024
def lookupInPrefix(name: Name) = {
1020-
val sym = pre.member(name).filter(qualifies)
1021-
def isNonPackageNoModuleClass(sym: Symbol) =
1022-
sym.isClass && !sym.isModuleClass && !sym.isPackageClass
1023-
if (!sym.exists && unit.isJava && isNonPackageNoModuleClass(pre.typeSymbol)) {
1024-
// TODO factor out duplication with Typer::inCompanionForJavaStatic
1025-
val pre1 = companionSymbolOf(pre.typeSymbol, this).typeOfThis
1026-
pre1.member(name).filter(qualifies).andAlso(_ => pre = pre1)
1027-
} else sym
1025+
val searchCompanions = isPlainClassInJavaUnit(pre.typeSymbol)
1026+
val sym = pre.findMember(name, excludedFlags = Flags.BridgeFlags, requiredFlags = 0, stableOnly = false, searchCompanions).filter(qualifies)
1027+
if (searchCompanions && sym.owner.isModuleClass)
1028+
pre = sym.owner.typeOfThis
1029+
sym
10281030
}
10291031
def accessibleInPrefix(s: Symbol) = isAccessible(s, pre, superAccess = false)
10301032

src/compiler/scala/tools/nsc/typechecker/Typers.scala

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -668,8 +668,9 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
668668
case SuperType(clazz, _) if callSiteWithinClass(clazz.typeSymbol) => true
669669
case _ => phase.next.erasedTypes
670670
}
671-
if (includeLocals) qual.tpe member name
672-
else qual.tpe nonLocalMember name
671+
val excluded = BridgeFlags | (if (includeLocals) 0 else LOCAL)
672+
val searchCompanions = context.isPlainClassInJavaUnit(qual.tpe.typeSymbol)
673+
qual.tpe.findMember(name, excludedFlags = excluded, requiredFlags = 0, stableOnly = false, searchCompanions)
673674
}
674675

675676
def silent[T](op: Typer => T,
@@ -4795,16 +4796,6 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
47954796
if (isStableContext(tree, mode, pt)) tree setType clazz.thisType else tree
47964797
}
47974798

4798-
4799-
// For Java, instance and static members are in the same scope, but we put the static ones in the companion object
4800-
// so, when we can't find a member in the class scope, check the companion
4801-
def inCompanionForJavaStatic(pre: Type, cls: Symbol, name: Name): Symbol =
4802-
if (!(context.unit.isJava && cls.isClass && !cls.isModuleClass)) NoSymbol else {
4803-
val companion = companionSymbolOf(cls, context)
4804-
if (!companion.exists) NoSymbol
4805-
else member(gen.mkAttributedRef(pre, companion), name) // assert(res.isStatic, s"inCompanionJavaStatic($pre, $cls, $name) = $res ${res.debugFlagString}")
4806-
}
4807-
48084799
/* Attribute a selection where `tree` is `qual.name`.
48094800
* `qual` is already attributed.
48104801
*/
@@ -4831,7 +4822,7 @@ trait Typers extends Adaptations with Tags with TypersTracking with PatternTyper
48314822
dyna.wrapErrors(t, (_.typed1(t, mode, pt)))
48324823
}
48334824

4834-
val sym = tree.symbol orElse member(qual, name) orElse inCompanionForJavaStatic(qual.tpe.prefix, qual.symbol, name) orElse {
4825+
val sym = tree.symbol orElse member(qual, name) orElse {
48354826
// symbol not found? --> try to convert implicitly to a type that does have the required
48364827
// member. Added `| PATTERNmode` to allow enrichment in patterns (so we can add e.g., an
48374828
// xml member to StringContext, which in turn has an unapply[Seq] method)

src/reflect/scala/reflect/internal/Types.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,8 +1022,8 @@ trait Types
10221022
* @param requiredFlags Returned members do have these flags
10231023
* @param stableOnly If set, return only members that are types or stable values
10241024
*/
1025-
def findMember(name: Name, excludedFlags: Long, requiredFlags: Long, stableOnly: Boolean): Symbol = {
1026-
def findMemberInternal = new FindMember(this, name, excludedFlags, requiredFlags, stableOnly).apply()
1025+
def findMember(name: Name, excludedFlags: Long, requiredFlags: Long, stableOnly: Boolean, searchCompanions: Boolean = false): Symbol = {
1026+
def findMemberInternal = new FindMember(this, name, excludedFlags, requiredFlags, stableOnly, searchCompanions).apply()
10271027

10281028
if (this.isGround) findMemberInternal
10291029
else suspendingTypeVars(typeVarsInType(this))(findMemberInternal)
@@ -1115,7 +1115,7 @@ trait Types
11151115
// todo see whether we can do without
11161116
override def isError: Boolean = true
11171117
override def decls: Scope = new ErrorScope(NoSymbol)
1118-
override def findMember(name: Name, excludedFlags: Long, requiredFlags: Long, stableOnly: Boolean): Symbol = {
1118+
override def findMember(name: Name, excludedFlags: Long, requiredFlags: Long, stableOnly: Boolean, searchCompanion: Boolean): Symbol = {
11191119
var sym = decls lookup name
11201120
if (sym == NoSymbol) {
11211121
sym = NoSymbol.newErrorSymbol(name)

src/reflect/scala/reflect/internal/tpe/FindMembers.scala

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,18 @@ trait FindMembers {
1313
this: SymbolTable =>
1414

1515
/** Implementation of `Type#{findMember, findMembers}` */
16-
private[internal] abstract class FindMemberBase[T](tpe: Type, name: Name, excludedFlags: Long, requiredFlags: Long) {
17-
protected val initBaseClasses: List[Symbol] = tpe.baseClasses
16+
private[internal] abstract class FindMemberBase[T](tpe: Type, name: Name, excludedFlags: Long, requiredFlags: Long, searchCompanions: Boolean) {
17+
protected val initBaseClasses: List[Symbol] = {
18+
val bcs = tpe.baseClasses
19+
// For Java compilation units, a `T` or `A.T` can refer to a static inner class `T`. In the Scala compiler,
20+
// we model static members are members of the companion object. Under `searchCompanions`, we always check the
21+
// companion before proceeding to the next base class. This follows Java's semantics of "inheriting" static
22+
// members: `A.T` can resolve to a static member defined in a parent of `A`.
23+
// class C { class T }; class B extends C { static class T }; class A extends B
24+
// Here, `A.T` resolves to the static class `B.T`, so we need to check B's companion before proceeding to C.
25+
if (searchCompanions) bcs.flatMap(bc => List(bc, bc.companionModule.moduleClass))
26+
else bcs
27+
}
1828

1929
// The first base class, or the symbol of the ThisType
2030
// e.g in:
@@ -191,7 +201,7 @@ trait FindMembers {
191201
}
192202

193203
private[reflect] final class FindMembers(tpe: Type, excludedFlags: Long, requiredFlags: Long)
194-
extends FindMemberBase[Scope](tpe, nme.ANYname, excludedFlags, requiredFlags) {
204+
extends FindMemberBase[Scope](tpe, nme.ANYname, excludedFlags, requiredFlags, searchCompanions = false) {
195205
private[this] var _membersScope: Scope = null
196206
private def membersScope: Scope = {
197207
if (_membersScope eq null) _membersScope = newFindMemberScope
@@ -215,8 +225,8 @@ trait FindMembers {
215225
}
216226
}
217227

218-
private[reflect] final class FindMember(tpe: Type, name: Name, excludedFlags: Long, requiredFlags: Long, stableOnly: Boolean)
219-
extends FindMemberBase[Symbol](tpe, name, excludedFlags, requiredFlags) {
228+
private[reflect] class FindMember(tpe: Type, name: Name, excludedFlags: Long, requiredFlags: Long, stableOnly: Boolean, searchCompanions: Boolean)
229+
extends FindMemberBase[Symbol](tpe, name, excludedFlags, requiredFlags, searchCompanions) {
220230
// Gathering the results into a hand rolled ListBuffer
221231
// TODO Try just using a ListBuffer to see if this low-level-ness is worth it.
222232
private[this] var member0: Symbol = NoSymbol

test/files/neg/t9111c.check

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
Test_1.scala:6: error: type mismatch;
2-
found : A_1.P.T
3-
required: A_1.T
4-
assert(j.foo(new A_1.P.T()) == 1) // TODO SI-9111: should compile without error (currently fails, required: A_1.T)
1+
Test_1.scala:5: error: type mismatch;
2+
found : A_1.T
3+
required: A_1.P.T
4+
assert(j.foo(new A_1.T()) == 1) // type mismatch, required: A_1.P.T
55
^
66
one error found

test/files/neg/t9111c/A_1.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@ public class Deeper {
88
public int foo(T t) { return t.f(); }
99
}
1010
}
11+
12+
public static void crossCheck() {
13+
Inner i = new Inner();
14+
Inner.Deeper d = i.new Deeper();
15+
d.foo(new P.T());
16+
}
1117
}

test/files/neg/t9111c/Test_1.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,6 @@ object Test extends App {
22
val i = new A_1.Inner()
33
val j = new i.Deeper()
44

5-
assert(j.foo(new A_1.T()) == 1) // TODO SI-9111: should not compile (need an A_1.P.T) (currently passes)
6-
assert(j.foo(new A_1.P.T()) == 1) // TODO SI-9111: should compile without error (currently fails, required: A_1.T)
5+
assert(j.foo(new A_1.T()) == 1) // type mismatch, required: A_1.P.T
6+
assert(j.foo(new A_1.P.T()) == 1) // compiles, no error, see run/t9111b
77
}

test/files/neg/t9111d.check

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
Test_1.scala:4: error: type T is not a member of object A_1.C
2+
val f2: A_1.C.T = e.foo() // error: T is not a member of A_1.C (it's a non-static class)
3+
^
4+
Test_1.scala:5: error: type mismatch;
5+
found : A_1.D.T
6+
required: A_1.C#T
7+
val f3: A_1.C#T = e.foo() // mismatch, found: A_1.D.T
8+
^
9+
Test_1.scala:8: error: type mismatch;
10+
found : A_1.C#T
11+
required: Test.e.T
12+
val b2: e.T = e.bar() // mismatch, bar returns A_1.C#T, not e.T - no path dependent types in Java
13+
^
14+
Test_1.scala:9: error: type mismatch;
15+
found : A_1.C#T
16+
required: A_1.D.T
17+
val b3: A_1.D.T = e.bar() // mismatch, found: A_1.C#T
18+
^
19+
Test_1.scala:12: error: type T is not a member of object A_1.E
20+
val m2: A_1.E.T = A_1.miz() // error: T is not a member of A_1.E - this selection is OK in Java, but not in Scala
21+
^
22+
Test_1.scala:13: error: type mismatch;
23+
found : A_1.D.T
24+
required: A_1.C#T
25+
val m3: A_1.C#T = A_1.miz() // mismatch, found: A_1.D.T
26+
^
27+
Test_1.scala:16: error: type mismatch;
28+
found : A_1.D.T
29+
required: A_1.C#T
30+
val p2: A_1.C#T = A_1.paz() // mismatch, found: A_1.D.T
31+
^
32+
Test_1.scala:19: error: type mismatch;
33+
found : A_1.C#T
34+
required: A_1.D.T
35+
val t2: A_1.D.T = A_1.tux() // mismatch, found: A_1.C#T
36+
^
37+
8 errors found

test/files/neg/t9111d/A_1.java

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
public class A_1 {
2+
static class C {
3+
class T {}
4+
}
5+
static class D extends C {
6+
static class T {}
7+
}
8+
static class E extends D {
9+
10+
// An unqualified T resolves to the static class D.T.
11+
// The scala compiler puts D.T into the companion object of D.
12+
// When performing a lookup of T, we need to make sure to consider the companion object of D
13+
// before checking the superclass C, which has an instance member T (SI-9111).
14+
15+
public T foo() {
16+
return new D.T(); // cross-check: T is D.T
17+
}
18+
public C.T bar() {
19+
// C.T is not a static class, `this` is implicitly used as enclosing instance - `this.new T()` doesn't work.
20+
return new C.T();
21+
}
22+
}
23+
24+
// A qualified E.T also resolves to D.T
25+
public static E.T miz() {
26+
return new D.T(); // cross-check: E.T is D.T
27+
}
28+
29+
public static D.T paz() {
30+
return new E.T();
31+
}
32+
33+
public static C.T tux() {
34+
C c = new C();
35+
return c.new T();
36+
}
37+
}

test/files/neg/t9111d/Test_1.scala

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
object Test {
2+
val e = new A_1.E()
3+
val f1: A_1.D.T = e.foo() // no error
4+
val f2: A_1.C.T = e.foo() // error: T is not a member of A_1.C (it's a non-static class)
5+
val f3: A_1.C#T = e.foo() // mismatch, found: A_1.D.T
6+
7+
val b1: A_1.C#T = e.bar() // no error
8+
val b2: e.T = e.bar() // mismatch, bar returns A_1.C#T, not e.T - no path dependent types in Java
9+
val b3: A_1.D.T = e.bar() // mismatch, found: A_1.C#T
10+
11+
val m1: A_1.D.T = A_1.miz() // no error
12+
val m2: A_1.E.T = A_1.miz() // error: T is not a member of A_1.E - this selection is OK in Java, but not in Scala
13+
val m3: A_1.C#T = A_1.miz() // mismatch, found: A_1.D.T
14+
15+
val p1: A_1.D.T = A_1.paz() // no error
16+
val p2: A_1.C#T = A_1.paz() // mismatch, found: A_1.D.T
17+
18+
val t1: A_1.C#T = A_1.tux() // no error
19+
val t2: A_1.D.T = A_1.tux() // mismatch, found: A_1.C#T
20+
}

test/files/run/t10107/A_1.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public class A_1 {
2+
public static class AInner {
3+
public static interface InnerInterface { }
4+
}
5+
}

test/files/run/t10107/B_1.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public class B_1 {
2+
public static class BInner extends A_1.AInner {
3+
public class ProblemClass implements InnerInterface { }
4+
}
5+
}

test/files/run/t10107/Test_1.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
new B_1()
4+
val bi = new B_1.BInner
5+
new bi.ProblemClass()
6+
}
7+
}

test/files/run/t9111a/A_1.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,9 @@ public static final class Inner {
44
public static final class T { public int x = 2; }
55
public T newT() { return new T(); }
66
}
7+
8+
public static int crossCheck() {
9+
Inner i = new Inner();
10+
return i.newT().x;
11+
}
712
}

test/files/run/t9111a/Test_1.scala

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
object Test extends App {
22
val i = new A_1.Inner()
33
assert(i.newT().x == 2)
4+
assert(A_1.crossCheck() == 2)
45
}

test/files/run/t9111b/A_1.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,10 @@ public class Deeper {
88
public int foo(T t) { return t.f(); }
99
}
1010
}
11+
12+
public static void crossCheck() {
13+
Inner i = new Inner();
14+
Inner.Deeper d = i.new Deeper();
15+
d.foo(new P.T());
16+
}
1117
}

test/files/run/t9111b/Test_1.scala

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,6 @@ object Test extends App {
22
val i = new A_1.Inner()
33
val j = new i.Deeper()
44

5-
// TODO SI-9111: causes a NoSuchMethodError, should fail in mixed compilation.
6-
// assert(j.foo(new A_1.T()) == 1)
7-
8-
// TODO SI-9111: fails in mixed compilation (found: A_1.P.T, required: A_1.T), should pass.
9-
// assert(j.foo(new A_1.P.T()) == 1)
5+
// assert(j.foo(new A_1.T()) == 1) // type mismatch, required: A_1.P.T, see neg/t9111
6+
assert(j.foo(new A_1.P.T()) == 1) // compiles, no error
107
}

0 commit comments

Comments
 (0)