Skip to content

Commit abaae76

Browse files
authored
Root of Java select must be class or rooted package (#21800)
Fixes #21333 Edge case in Java interop.
2 parents 399d008 + d0f05ba commit abaae76

File tree

14 files changed

+128
-18
lines changed

14 files changed

+128
-18
lines changed

compiler/src/dotty/tools/dotc/reporting/MessageRendering.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ trait MessageRendering {
6262
}
6363

6464
val syntax =
65-
if (ctx.settings.color.value != "never")
65+
if (ctx.settings.color.value != "never" && !ctx.isJava)
6666
SyntaxHighlighting.highlight(new String(pos.linesSlice)).toCharArray
6767
else pos.linesSlice
6868
val lines = linesFrom(syntax)

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,8 +125,8 @@ trait TypeAssigner {
125125

126126
/** The type of the selection `tree`, where `qual1` is the typed qualifier part. */
127127
def selectionType(tree: untpd.RefTree, qual1: Tree)(using Context): Type =
128-
val qualType0 = qual1.tpe.widenIfUnstable
129128
val qualType =
129+
val qualType0 = qual1.tpe.widenIfUnstable
130130
if !qualType0.hasSimpleKind && tree.name != nme.CONSTRUCTOR then
131131
// constructors are selected on type constructor, type arguments are passed afterwards
132132
errorType(em"$qualType0 takes type parameters", qual1.srcPos)
@@ -199,7 +199,7 @@ trait TypeAssigner {
199199

200200
/** Type assignment method. Each method takes as parameters
201201
* - an untpd.Tree to which it assigns a type,
202-
* - typed child trees it needs to access to cpmpute that type,
202+
* - typed child trees it needs to access to compute that type,
203203
* - any further information it needs to access to compute that type.
204204
*/
205205
def assignType(tree: untpd.Ident, tp: Type)(using Context): Ident =

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

+36-15
Original file line numberDiff line numberDiff line change
@@ -1022,20 +1022,44 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
10221022
record("typedSelect")
10231023

10241024
def typeSelectOnTerm(using Context): Tree =
1025-
val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
10261025
if ctx.isJava then
1027-
javaSelection(qual)
1026+
// permitted selection depends on Java context (type or expression).
1027+
// we don't propagate (as a mode) whether a.b.m is a type name; OK since we only see type contexts.
1028+
// to allow correct selections, approximate by fallback for x.y: take x as class or (rooted) package.
1029+
def tryQualFallback(qual: untpd.Ident, name: Name)(using Context): Tree =
1030+
val qualTpe =
1031+
findRef(name.toTypeName, WildcardType, EmptyFlags, EmptyFlags, qual.srcPos) match
1032+
case tpe: NamedType if tpe.symbol.isClass => tpe
1033+
case _ =>
1034+
val maybePackage = defn.RootPackage.info.member(name)
1035+
if maybePackage.exists then maybePackage.info else NoType
1036+
if qualTpe.exists then
1037+
javaSelection(assignType(cpy.Ident(qual)(name), qualTpe))
1038+
else
1039+
errorTree(tree, em"no class or package to resolve `$name`") // just fail fallback
1040+
def tryQual(qual: untpd.Tree)(using Context): Tree =
1041+
javaSelection(typedExpr(qual, shallowSelectionProto(tree.name, pt, this, tree.nameSpan)))
1042+
tree.qualifier match
1043+
case qual @ Ident(name) => tryAlternatively(tryQual(qual))(tryQualFallback(qual, name))
1044+
case qual => tryQual(qual)
10281045
else
1046+
val qual = typedExpr(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
10291047
typedSelectWithAdapt(tree, pt, qual).withSpan(tree.span).computeNullable()
10301048

10311049
def javaSelection(qual: Tree)(using Context) =
1032-
val tree1 = assignType(cpy.Select(tree)(qual, tree.name), qual)
1033-
tree1.tpe match
1034-
case moduleRef: TypeRef if moduleRef.symbol.is(ModuleClass, butNot = JavaDefined) =>
1035-
// handle unmangling of module names (Foo$ -> Foo[ModuleClass])
1036-
cpy.Select(tree)(qual, tree.name.unmangleClassName).withType(moduleRef)
1037-
case _ =>
1038-
tree1
1050+
qual match
1051+
case id @ Ident(name) if id.symbol.is(Package) && !id.symbol.owner.isRoot =>
1052+
val rooted = defn.RootPackage.info.member(name)
1053+
val qual1 = if rooted.exists then assignType(cpy.Ident(id)(name), rooted.info) else qual
1054+
assignType(cpy.Select(tree)(qual1, tree.name), qual1)
1055+
case _ =>
1056+
val tree1 = assignType(cpy.Select(tree)(qual, tree.name), qual)
1057+
tree1.tpe match
1058+
case moduleRef: TypeRef if moduleRef.symbol.is(ModuleClass, butNot = JavaDefined) =>
1059+
// handle unmangling of module names (Foo$ -> Foo[ModuleClass])
1060+
cpy.Select(tree)(qual, tree.name.unmangleClassName).withType(moduleRef)
1061+
case _ =>
1062+
tree1
10391063

10401064
def tryJavaSelectOnType(using Context): Tree = tree.qualifier match {
10411065
case sel @ Select(qual, name) =>
@@ -1052,17 +1076,14 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
10521076
errorTree(tree, em"cannot convert to type selection") // will never be printed due to fallback
10531077
}
10541078

1055-
def selectWithFallback(fallBack: Context ?=> Tree) =
1056-
tryAlternatively(typeSelectOnTerm)(fallBack)
1057-
10581079
if (tree.qualifier.isType) {
10591080
val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
10601081
assignType(cpy.Select(tree)(qual1, tree.name), qual1)
10611082
}
10621083
else if (ctx.isJava && tree.name.isTypeName)
1063-
// SI-3120 Java uses the same syntax, A.B, to express selection from the
1064-
// value A and from the type A. We have to try both.
1065-
selectWithFallback(tryJavaSelectOnType) // !!! possibly exponential bcs of qualifier retyping
1084+
// scala/bug#3120 Java uses the same syntax, A.B, to express selection from the
1085+
// value A and from the type A. We have to try both. (possibly exponential bc of qualifier retyping)
1086+
tryAlternatively(typeSelectOnTerm)(tryJavaSelectOnType)
10661087
else
10671088
typeSelectOnTerm
10681089
}

tests/pos/t10350/Bar.scala

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
package bar
3+
4+
object Bar {
5+
def xxx(s: String): foo.Foo = foo.Foo.create(s)
6+
}

tests/pos/t10350/Baz.java

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
2+
package foo.java;
3+
4+
interface Baz {
5+
}

tests/pos/t10350/Foo.java

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
package foo;
3+
4+
public interface Foo {
5+
static Foo create(java.lang.String v) {
6+
return null;
7+
}
8+
}
9+
10+
/*
11+
5 | static Foo create(java.lang.String v) {
12+
| ^^^^^^^^^
13+
| value lang is not a member of foo.java
14+
*/

tests/pos/t11788/Bar.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package p
2+
3+
object Bar extends App {
4+
println(new Foo().test())
5+
}

tests/pos/t11788/Foo.java

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package p;
2+
3+
public class Foo {
4+
private String java;
5+
6+
// java is the rooted package, not the field
7+
public java.lang.Integer test() {
8+
//return Integer.valueOf(42);
9+
throw null;
10+
}
11+
}

tests/pos/t11788b/Bar.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package p
2+
3+
object Bar extends App {
4+
println(new Foo().test())
5+
}

tests/pos/t11788b/Foo.java

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package p;
2+
3+
public class Foo {
4+
private String java;
5+
6+
public java.lang.Integer test() {
7+
//return Integer.valueOf(42);
8+
throw null;
9+
}
10+
}

tests/pos/t11788b/java.java

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package p;
2+
3+
public class java {
4+
public static class lang {
5+
public static class Integer {
6+
}
7+
}
8+
}

tests/pos/t11788c/Bar.scala

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package p
2+
3+
object Bar extends App {
4+
println(new Foo().test())
5+
}

tests/pos/t11788c/Foo.java

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package p;
2+
3+
public class Foo {
4+
private String java;
5+
6+
// java is class in scope, not the term member or package
7+
public java.lang.Integer.Inner test() {
8+
throw null;
9+
}
10+
}

tests/pos/t11788c/java.java

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package p;
2+
3+
public class java {
4+
public static class lang {
5+
public static class Integer {
6+
public static class Inner {
7+
}
8+
}
9+
}
10+
}

0 commit comments

Comments
 (0)