Skip to content

Commit e6f6e0d

Browse files
Backport "Fix java typer problems with inner class references and raw types" to LTS (#20934)
Backports #19747 to the LTS branch. PR submitted by the release tooling. [skip ci]
2 parents cdcbf1f + 5f7f0e4 commit e6f6e0d

13 files changed

+484
-16
lines changed

compiler/src/dotty/tools/dotc/core/ContextOps.scala

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ object ContextOps:
3434
if (elem.name == name) return elem.sym.denot // return self
3535
}
3636
val pre = ctx.owner.thisType
37-
if ctx.isJava then javaFindMember(name, pre, required, excluded)
37+
if ctx.isJava then
38+
// Note: I didn't verify if there exists a code path that would require `lookInCompanion = true`,
39+
// it is just to preserve the original behavior.
40+
javaFindMember(name, pre, lookInCompanion = true, required, excluded)
3841
else pre.findMember(name, pre, required, excluded)
3942
}
4043
else // we are in the outermost context belonging to a class; self is invisible here. See inClassContext.
@@ -43,7 +46,13 @@ object ContextOps:
4346
ctx.scope.denotsNamed(name).filterWithFlags(required, excluded).toDenot(NoPrefix)
4447
}
4548

46-
final def javaFindMember(name: Name, pre: Type, required: FlagSet = EmptyFlags, excluded: FlagSet = EmptyFlags): Denotation =
49+
/** Look in the prefix with Java semantics.
50+
* @param lookInCompanion If true, try in the companion class of a module as a fallback.
51+
* Note: originally this was used to type Select nodes in Java code,
52+
* but that is no longer the case.
53+
* It is preserved in case it is necessary for denotNamed, but this is unverified.
54+
*/
55+
final def javaFindMember(name: Name, pre: Type, lookInCompanion: Boolean, required: FlagSet = EmptyFlags, excluded: FlagSet = EmptyFlags): Denotation =
4756
assert(ctx.isJava)
4857
inContext(ctx) {
4958

@@ -53,7 +62,7 @@ object ContextOps:
5362
val directSearch = pre.findMember(name, pre, required, excluded)
5463

5564
// 2. Try to search in companion class if current is an object.
56-
def searchCompanionClass = if preSym.is(Flags.Module) then
65+
def searchCompanionClass = if lookInCompanion && preSym.is(Flags.Module) then
5766
preSym.companionClass.thisType.findMember(name, pre, required, excluded)
5867
else NoDenotation
5968

compiler/src/dotty/tools/dotc/core/SymDenotations.scala

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1501,6 +1501,13 @@ object SymDenotations {
15011501
def namedType(using Context): NamedType =
15021502
if (isType) typeRef else termRef
15031503

1504+
/** Like typeRef, but the prefix is widened.
1505+
*
1506+
* See tests/neg/i19619/Test.scala
1507+
*/
1508+
def javaTypeRef(using Context) =
1509+
TypeRef(maybeOwner.reachablePrefix.widen, symbol)
1510+
15041511
/** Like typeRef, but objects in the prefix are represented by their singleton type,
15051512
* this means we output `pre.O.member` rather than `pre.O$.this.member`.
15061513
*

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1539,17 +1539,19 @@ class Namer { typer: Typer =>
15391539
end parentType
15401540

15411541
/** Check parent type tree `parent` for the following well-formedness conditions:
1542-
* (1) It must be a class type with a stable prefix (@see checkClassTypeWithStablePrefix)
1542+
* (1) It must be a class type with a stable prefix (unless `isJava`) (@see checkClassTypeWithStablePrefix)
15431543
* (2) If may not derive from itself
15441544
* (3) The class is not final
15451545
* (4) If the class is sealed, it is defined in the same compilation unit as the current class
1546+
*
1547+
* @param isJava If true, the parent type is in Java mode, and we do not require a stable prefix
15461548
*/
1547-
def checkedParentType(parent: untpd.Tree): Type = {
1549+
def checkedParentType(parent: untpd.Tree, isJava: Boolean): Type = {
15481550
val ptype = parentType(parent)(using completerCtx.superCallContext).dealiasKeepAnnots
15491551
if (cls.isRefinementClass) ptype
15501552
else {
15511553
val pt = checkClassType(ptype, parent.srcPos,
1552-
traitReq = parent ne parents.head, stablePrefixReq = true)
1554+
traitReq = parent ne parents.head, stablePrefixReq = !isJava)
15531555
if (pt.derivesFrom(cls)) {
15541556
val addendum = parent match {
15551557
case Select(qual: Super, _) if Feature.migrateTo3 =>
@@ -1618,7 +1620,9 @@ class Namer { typer: Typer =>
16181620
val parentTypes = defn.adjustForTuple(cls, cls.typeParams,
16191621
defn.adjustForBoxedUnit(cls,
16201622
addUsingTraits(
1621-
ensureFirstIsClass(cls, parents.map(checkedParentType(_)))
1623+
locally:
1624+
val isJava = ctx.isJava
1625+
ensureFirstIsClass(cls, parents.map(checkedParentType(_, isJava)))
16221626
)
16231627
)
16241628
)

compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,10 @@ trait TypeAssigner {
150150
val pre = maybeSkolemizePrefix(qualType, name)
151151
val mbr =
152152
if ctx.isJava then
153-
ctx.javaFindMember(name, pre)
153+
// don't look in the companion class here if qual is a module,
154+
// we use backtracking to instead change the qual to the companion class
155+
// if this fails.
156+
ctx.javaFindMember(name, pre, lookInCompanion = false)
154157
else
155158
qualType.findMember(name, pre)
156159

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -453,6 +453,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
453453
if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType
454454
else if (ctx.isJava && defDenot.symbol.isStatic) {
455455
defDenot.symbol.namedType
456+
}
457+
else if (ctx.isJava && defDenot.symbol.isClass) {
458+
// in a java context a raw identifier to a class should have a widened prefix.
459+
defDenot.symbol.javaTypeRef
456460
} else {
457461
val effectiveOwner =
458462
if (curOwner.isTerm && defDenot.symbol.maybeOwner.isType)
@@ -4210,14 +4214,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42104214
val tree1 =
42114215
if ((pt eq AnyTypeConstructorProto) || tp.typeParamSymbols.isEmpty) tree
42124216
else {
4213-
val tp1 =
4214-
if (ctx.isJava)
4215-
// Cook raw type
4216-
AppliedType(tree.tpe, tp.typeParams.map(Function.const(TypeBounds.empty)))
4217-
else
4218-
// Eta-expand higher-kinded type
4219-
tree.tpe.EtaExpand(tp.typeParamSymbols)
4220-
tree.withType(tp1)
4217+
if (ctx.isJava)
4218+
// Cook raw type
4219+
val typeArgs = tp.typeParams.map(Function.const(TypeBounds.empty))
4220+
val tree1 = AppliedTypeTree(tree, typeArgs.map(TypeTree(_)))
4221+
val tp1 = AppliedType(tree.tpe, typeArgs)
4222+
tree1.withType(tp1)
4223+
else
4224+
// Eta-expand higher-kinded type
4225+
val tp1 = tree.tpe.EtaExpand(tp.typeParamSymbols)
4226+
tree.withType(tp1)
42214227
}
42224228
if (ctx.mode.is(Mode.Pattern) || ctx.mode.is(Mode.QuotedPattern) || tree1.tpe <:< pt) tree1
42234229
else err.typeMismatch(tree1, pt)

tests/neg/i19619/InnerClass.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// InnerClass.java
2+
3+
package lib;
4+
5+
public class InnerClass {
6+
7+
public class Inner<U> {
8+
public U innerField;
9+
10+
public Inner(U innerField) {
11+
this.innerField = innerField;
12+
}
13+
14+
public U getInnerField() {
15+
return innerField;
16+
}
17+
}
18+
19+
public class Outer<U> {
20+
21+
public class Nested<V> {
22+
23+
public U outerField;
24+
public V innerField;
25+
26+
public Nested(U outerField, V innerField) {
27+
this.outerField = outerField;
28+
this.innerField = innerField;
29+
}
30+
31+
public U getOuterField() {
32+
return outerField;
33+
}
34+
35+
public V getInnerField() {
36+
return innerField;
37+
}
38+
}
39+
}
40+
41+
public <U> Inner<U> createInner(U innerField) {
42+
return new Inner<>(innerField);
43+
}
44+
45+
public <U, V> Outer<U>.Nested<V> createNested(U outerField, V innerField) {
46+
Outer<U> outer = new Outer<>();
47+
return outer.new Nested<>(outerField, innerField);
48+
}
49+
50+
public static <U> InnerClass.Inner<U> createInnerStatic(U innerField) {
51+
InnerClass innerClass = new InnerClass();
52+
return innerClass.new Inner<>(innerField);
53+
}
54+
55+
public static <U, V> InnerClass.Outer<U>.Nested<V> createNestedStatic(U outerField, V innerField) {
56+
InnerClass innerClass = new InnerClass();
57+
InnerClass.Outer<U> outer = innerClass.new Outer<>();
58+
return outer.new Nested<>(outerField, innerField);
59+
}
60+
61+
public static <U, V> void consumeNestedStatic(InnerClass.Outer<U>.Nested<V> nested) {
62+
}
63+
64+
public static <U, V> void consumeNestedStatic2(Outer<U>.Nested<V> nested) {
65+
}
66+
67+
}

tests/neg/i19619/InnerClassGen.java

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// InnerClassGen.java
2+
3+
package lib;
4+
5+
public class InnerClassGen<T> {
6+
7+
public class Inner<U> {
8+
public T rootField;
9+
public U innerField;
10+
11+
public Inner(T rootField, U innerField) {
12+
this.rootField = rootField;
13+
this.innerField = innerField;
14+
}
15+
16+
public T getRootField() {
17+
return rootField;
18+
}
19+
20+
public U getInnerField() {
21+
return innerField;
22+
}
23+
}
24+
25+
public class Outer<U> {
26+
27+
public class Nested<V> {
28+
public T rootField;
29+
public U outerField;
30+
public V innerField;
31+
32+
public Nested(T rootField, U outerField, V innerField) {
33+
this.rootField = rootField;
34+
this.outerField = outerField;
35+
this.innerField = innerField;
36+
}
37+
38+
public T getRootField() {
39+
return rootField;
40+
}
41+
42+
public U getOuterField() {
43+
return outerField;
44+
}
45+
46+
public V getInnerField() {
47+
return innerField;
48+
}
49+
}
50+
}
51+
52+
public static class OuterStatic<U> {
53+
public static class NestedStatic<V> {
54+
}
55+
}
56+
57+
public <U> Inner<U> createInner(T rootField, U innerField) {
58+
return new Inner<>(rootField, innerField);
59+
}
60+
61+
public <U, V> Outer<U>.Nested<V> createNested(T rootField, U outerField, V innerField) {
62+
Outer<U> outer = new Outer<>();
63+
return outer.new Nested<>(rootField, outerField, innerField);
64+
}
65+
66+
public static <T, U> InnerClassGen<T>.Inner<U> createInnerStatic(T rootField, U innerField) {
67+
InnerClassGen<T> innerClassGen = new InnerClassGen<>();
68+
return innerClassGen.new Inner<>(rootField, innerField);
69+
}
70+
71+
public static <T, U, V> InnerClassGen<T>.Outer<U>.Nested<V> createNestedStatic(T rootField, U outerField, V innerField) {
72+
InnerClassGen<T> innerClassGen = new InnerClassGen<>();
73+
InnerClassGen<T>.Outer<U> outer = innerClassGen.new Outer<>();
74+
return outer.new Nested<>(rootField, outerField, innerField);
75+
}
76+
77+
public static <T, U, V> void consumeNestedStatic(InnerClassGen<T>.Outer<U>.Nested<V> nested) {
78+
}
79+
80+
}

tests/neg/i19619/Test.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import lib.InnerClass
2+
import lib.InnerClassGen
3+
4+
@main def Test =
5+
6+
locally:
7+
val ici: InnerClass = new InnerClass()
8+
val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error
9+
10+
locally:
11+
val ici: InnerClassGen[String] = new InnerClassGen()
12+
val ici_inner1: ici.Inner[Long] = ici.createInner[Long]("Hello", 47L) // error

tests/run/i19619/InnerClass.java

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// InnerClass.java
2+
3+
package lib;
4+
5+
public class InnerClass {
6+
7+
public class Inner<U> {
8+
public U innerField;
9+
10+
public Inner(U innerField) {
11+
this.innerField = innerField;
12+
}
13+
14+
public U getInnerField() {
15+
return innerField;
16+
}
17+
}
18+
19+
public class Outer<U> {
20+
21+
public class Nested<V> {
22+
23+
public U outerField;
24+
public V innerField;
25+
26+
public Nested(U outerField, V innerField) {
27+
this.outerField = outerField;
28+
this.innerField = innerField;
29+
}
30+
31+
public U getOuterField() {
32+
return outerField;
33+
}
34+
35+
public V getInnerField() {
36+
return innerField;
37+
}
38+
}
39+
}
40+
41+
public <U> Inner<U> createInner(U innerField) {
42+
return new Inner<>(innerField);
43+
}
44+
45+
public <U, V> Outer<U>.Nested<V> createNested(U outerField, V innerField) {
46+
Outer<U> outer = new Outer<>();
47+
return outer.new Nested<>(outerField, innerField);
48+
}
49+
50+
public static <U> InnerClass.Inner<U> createInnerStatic(U innerField) {
51+
InnerClass innerClass = new InnerClass();
52+
return innerClass.new Inner<>(innerField);
53+
}
54+
55+
public static <U, V> InnerClass.Outer<U>.Nested<V> createNestedStatic(U outerField, V innerField) {
56+
InnerClass innerClass = new InnerClass();
57+
InnerClass.Outer<U> outer = innerClass.new Outer<>();
58+
return outer.new Nested<>(outerField, innerField);
59+
}
60+
61+
public static <U, V> void consumeNestedStatic(InnerClass.Outer<U>.Nested<V> nested) {
62+
}
63+
64+
public static <U, V> void consumeNestedStatic2(Outer<U>.Nested<V> nested) {
65+
}
66+
67+
}

0 commit comments

Comments
 (0)