Skip to content

Commit

Permalink
Root of Java select must be class or rooted package (#21800)
Browse files Browse the repository at this point in the history
Fixes #21333 

Edge case in Java interop.
  • Loading branch information
noti0na1 authored Feb 27, 2025
2 parents 399d008 + d0f05ba commit abaae76
Show file tree
Hide file tree
Showing 14 changed files with 128 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ trait MessageRendering {
}

val syntax =
if (ctx.settings.color.value != "never")
if (ctx.settings.color.value != "never" && !ctx.isJava)
SyntaxHighlighting.highlight(new String(pos.linesSlice)).toCharArray
else pos.linesSlice
val lines = linesFrom(syntax)
Expand Down
4 changes: 2 additions & 2 deletions compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ trait TypeAssigner {

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

/** Type assignment method. Each method takes as parameters
* - an untpd.Tree to which it assigns a type,
* - typed child trees it needs to access to cpmpute that type,
* - typed child trees it needs to access to compute that type,
* - any further information it needs to access to compute that type.
*/
def assignType(tree: untpd.Ident, tp: Type)(using Context): Ident =
Expand Down
51 changes: 36 additions & 15 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1022,20 +1022,44 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
record("typedSelect")

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

def javaSelection(qual: Tree)(using Context) =
val tree1 = assignType(cpy.Select(tree)(qual, tree.name), qual)
tree1.tpe match
case moduleRef: TypeRef if moduleRef.symbol.is(ModuleClass, butNot = JavaDefined) =>
// handle unmangling of module names (Foo$ -> Foo[ModuleClass])
cpy.Select(tree)(qual, tree.name.unmangleClassName).withType(moduleRef)
case _ =>
tree1
qual match
case id @ Ident(name) if id.symbol.is(Package) && !id.symbol.owner.isRoot =>
val rooted = defn.RootPackage.info.member(name)
val qual1 = if rooted.exists then assignType(cpy.Ident(id)(name), rooted.info) else qual
assignType(cpy.Select(tree)(qual1, tree.name), qual1)
case _ =>
val tree1 = assignType(cpy.Select(tree)(qual, tree.name), qual)
tree1.tpe match
case moduleRef: TypeRef if moduleRef.symbol.is(ModuleClass, butNot = JavaDefined) =>
// handle unmangling of module names (Foo$ -> Foo[ModuleClass])
cpy.Select(tree)(qual, tree.name.unmangleClassName).withType(moduleRef)
case _ =>
tree1

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

def selectWithFallback(fallBack: Context ?=> Tree) =
tryAlternatively(typeSelectOnTerm)(fallBack)

if (tree.qualifier.isType) {
val qual1 = typedType(tree.qualifier, shallowSelectionProto(tree.name, pt, this, tree.nameSpan))
assignType(cpy.Select(tree)(qual1, tree.name), qual1)
}
else if (ctx.isJava && tree.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.
selectWithFallback(tryJavaSelectOnType) // !!! possibly exponential bcs of qualifier retyping
// scala/bug#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. (possibly exponential bc of qualifier retyping)
tryAlternatively(typeSelectOnTerm)(tryJavaSelectOnType)
else
typeSelectOnTerm
}
Expand Down
6 changes: 6 additions & 0 deletions tests/pos/t10350/Bar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@

package bar

object Bar {
def xxx(s: String): foo.Foo = foo.Foo.create(s)
}
5 changes: 5 additions & 0 deletions tests/pos/t10350/Baz.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@

package foo.java;

interface Baz {
}
14 changes: 14 additions & 0 deletions tests/pos/t10350/Foo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@

package foo;

public interface Foo {
static Foo create(java.lang.String v) {
return null;
}
}

/*
5 | static Foo create(java.lang.String v) {
| ^^^^^^^^^
| value lang is not a member of foo.java
*/
5 changes: 5 additions & 0 deletions tests/pos/t11788/Bar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package p

object Bar extends App {
println(new Foo().test())
}
11 changes: 11 additions & 0 deletions tests/pos/t11788/Foo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package p;

public class Foo {
private String java;

// java is the rooted package, not the field
public java.lang.Integer test() {
//return Integer.valueOf(42);
throw null;
}
}
5 changes: 5 additions & 0 deletions tests/pos/t11788b/Bar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package p

object Bar extends App {
println(new Foo().test())
}
10 changes: 10 additions & 0 deletions tests/pos/t11788b/Foo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package p;

public class Foo {
private String java;

public java.lang.Integer test() {
//return Integer.valueOf(42);
throw null;
}
}
8 changes: 8 additions & 0 deletions tests/pos/t11788b/java.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package p;

public class java {
public static class lang {
public static class Integer {
}
}
}
5 changes: 5 additions & 0 deletions tests/pos/t11788c/Bar.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package p

object Bar extends App {
println(new Foo().test())
}
10 changes: 10 additions & 0 deletions tests/pos/t11788c/Foo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package p;

public class Foo {
private String java;

// java is class in scope, not the term member or package
public java.lang.Integer.Inner test() {
throw null;
}
}
10 changes: 10 additions & 0 deletions tests/pos/t11788c/java.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package p;

public class java {
public static class lang {
public static class Integer {
public static class Inner {
}
}
}
}

0 comments on commit abaae76

Please sign in to comment.