Skip to content

Fix java typer problems with inner class references and raw types #19747

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

Merged
merged 6 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 12 additions & 3 deletions compiler/src/dotty/tools/dotc/core/ContextOps.scala
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ object ContextOps:
if (elem.name == name) return elem.sym.denot // return self
}
val pre = ctx.owner.thisType
if ctx.isJava then javaFindMember(name, pre, required, excluded)
if ctx.isJava then
// Note: I didn't verify if there exists a code path that would require `lookInCompanion = true`,
// it is just to preserve the original behavior.
javaFindMember(name, pre, lookInCompanion = true, required, excluded)
else pre.findMember(name, pre, required, excluded)
}
else // we are in the outermost context belonging to a class; self is invisible here. See inClassContext.
Expand All @@ -43,7 +46,13 @@ object ContextOps:
ctx.scope.denotsNamed(name).filterWithFlags(required, excluded).toDenot(NoPrefix)
}

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

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

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

Expand Down
7 changes: 7 additions & 0 deletions compiler/src/dotty/tools/dotc/core/SymDenotations.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1512,6 +1512,13 @@ object SymDenotations {
def namedType(using Context): NamedType =
if (isType) typeRef else termRef

/** Like typeRef, but the prefix is widened.
*
* See tests/neg/i19619/Test.scala
*/
def javaTypeRef(using Context) =
TypeRef(maybeOwner.reachablePrefix.widen, symbol)

/** Like typeRef, but objects in the prefix are represented by their singleton type,
* this means we output `pre.O.member` rather than `pre.O$.this.member`.
*
Expand Down
12 changes: 8 additions & 4 deletions compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -1542,17 +1542,19 @@ class Namer { typer: Typer =>
end parentType

/** Check parent type tree `parent` for the following well-formedness conditions:
* (1) It must be a class type with a stable prefix (@see checkClassTypeWithStablePrefix)
* (1) It must be a class type with a stable prefix (unless `isJava`) (@see checkClassTypeWithStablePrefix)
* (2) If may not derive from itself
* (3) The class is not final
* (4) If the class is sealed, it is defined in the same compilation unit as the current class
*
* @param isJava If true, the parent type is in Java mode, and we do not require a stable prefix
*/
def checkedParentType(parent: untpd.Tree): Type = {
def checkedParentType(parent: untpd.Tree, isJava: Boolean): Type = {
val ptype = parentType(parent)(using completerCtx.superCallContext).dealiasKeepAnnots
if (cls.isRefinementClass) ptype
else {
val pt = checkClassType(ptype, parent.srcPos,
traitReq = parent ne parents.head, stablePrefixReq = true)
traitReq = parent ne parents.head, stablePrefixReq = !isJava)
if (pt.derivesFrom(cls)) {
val addendum = parent match {
case Select(qual: Super, _) if Feature.migrateTo3 =>
Expand Down Expand Up @@ -1621,7 +1623,9 @@ class Namer { typer: Typer =>
val parentTypes = defn.adjustForTuple(cls, cls.typeParams,
defn.adjustForBoxedUnit(cls,
addUsingTraits(
ensureFirstIsClass(cls, parents.map(checkedParentType(_)))
locally:
val isJava = ctx.isJava
ensureFirstIsClass(cls, parents.map(checkedParentType(_, isJava)))
)
)
)
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,10 @@ trait TypeAssigner {
val pre = maybeSkolemizePrefix(qualType, name)
val mbr =
if ctx.isJava then
ctx.javaFindMember(name, pre)
// don't look in the companion class here if qual is a module,
// we use backtracking to instead change the qual to the companion class
// if this fails.
ctx.javaFindMember(name, pre, lookInCompanion = false)
else
qualType.findMember(name, pre)

Expand Down
22 changes: 14 additions & 8 deletions compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,10 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
if (isSelfDenot(defDenot)) curOwner.enclosingClass.thisType
else if (ctx.isJava && defDenot.symbol.isStatic) {
defDenot.symbol.namedType
}
else if (ctx.isJava && defDenot.symbol.isClass) {
// in a java context a raw identifier to a class should have a widened prefix.
defDenot.symbol.javaTypeRef
} else {
val effectiveOwner =
if (curOwner.isTerm && defDenot.symbol.maybeOwner.isType)
Expand Down Expand Up @@ -4271,14 +4275,16 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
val tree1 =
if ((pt eq AnyTypeConstructorProto) || tp.typeParamSymbols.isEmpty) tree
else {
val tp1 =
if (ctx.isJava)
// Cook raw type
AppliedType(tree.tpe, tp.typeParams.map(Function.const(TypeBounds.empty)))
else
// Eta-expand higher-kinded type
tree.tpe.etaExpand(tp.typeParamSymbols)
tree.withType(tp1)
if (ctx.isJava)
// Cook raw type
val typeArgs = tp.typeParams.map(Function.const(TypeBounds.empty))
val tree1 = AppliedTypeTree(tree, typeArgs.map(TypeTree(_)))
val tp1 = AppliedType(tree.tpe, typeArgs)
tree1.withType(tp1)
else
// Eta-expand higher-kinded type
val tp1 = tree.tpe.etaExpand(tp.typeParamSymbols)
tree.withType(tp1)
}
if (ctx.mode.is(Mode.Pattern) || ctx.mode.isQuotedPattern || tree1.tpe <:< pt) tree1
else err.typeMismatch(tree1, pt)
Expand Down
65 changes: 65 additions & 0 deletions sbt-test/pipelining/Yjava-tasty-paths/a/InnerClass.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package a;

public class InnerClass {

public class Inner<U> {
public U innerField;

public Inner(U innerField) {
this.innerField = innerField;
}

public U getInnerField() {
return innerField;
}
}

public class Outer<U> {

public class Nested<V> {

public U outerField;
public V innerField;

public Nested(U outerField, V innerField) {
this.outerField = outerField;
this.innerField = innerField;
}

public U getOuterField() {
return outerField;
}

public V getInnerField() {
return innerField;
}
}
}

public <U> Inner<U> createInner(U innerField) {
return new Inner<>(innerField);
}

public <U, V> Outer<U>.Nested<V> createNested(U outerField, V innerField) {
Outer<U> outer = new Outer<>();
return outer.new Nested<>(outerField, innerField);
}

public static <U> InnerClass.Inner<U> createInnerStatic(U innerField) {
InnerClass innerClass = new InnerClass();
return innerClass.new Inner<>(innerField);
}

public static <U, V> InnerClass.Outer<U>.Nested<V> createNestedStatic(U outerField, V innerField) {
InnerClass innerClass = new InnerClass();
InnerClass.Outer<U> outer = innerClass.new Outer<>();
return outer.new Nested<>(outerField, innerField);
}

public static <U, V> void consumeNestedStatic(InnerClass.Outer<U>.Nested<V> nested) {
}

public static <U, V> void consumeNestedStatic2(Outer<U>.Nested<V> nested) {
}

}
78 changes: 78 additions & 0 deletions sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassGen.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package a;

public class InnerClassGen<T> {

public class Inner<U> {
public T rootField;
public U innerField;

public Inner(T rootField, U innerField) {
this.rootField = rootField;
this.innerField = innerField;
}

public T getRootField() {
return rootField;
}

public U getInnerField() {
return innerField;
}
}

public class Outer<U> {

public class Nested<V> {
public T rootField;
public U outerField;
public V innerField;

public Nested(T rootField, U outerField, V innerField) {
this.rootField = rootField;
this.outerField = outerField;
this.innerField = innerField;
}

public T getRootField() {
return rootField;
}

public U getOuterField() {
return outerField;
}

public V getInnerField() {
return innerField;
}
}
}

public static class OuterStatic<U> {
public static class NestedStatic<V> {
}
}

public <U> Inner<U> createInner(T rootField, U innerField) {
return new Inner<>(rootField, innerField);
}

public <U, V> Outer<U>.Nested<V> createNested(T rootField, U outerField, V innerField) {
Outer<U> outer = new Outer<>();
return outer.new Nested<>(rootField, outerField, innerField);
}

public static <T, U> InnerClassGen<T>.Inner<U> createInnerStatic(T rootField, U innerField) {
InnerClassGen<T> innerClassGen = new InnerClassGen<>();
return innerClassGen.new Inner<>(rootField, innerField);
}

public static <T, U, V> InnerClassGen<T>.Outer<U>.Nested<V> createNestedStatic(T rootField, U outerField, V innerField) {
InnerClassGen<T> innerClassGen = new InnerClassGen<>();
InnerClassGen<T>.Outer<U> outer = innerClassGen.new Outer<>();
return outer.new Nested<>(rootField, outerField, innerField);
}

public static <T, U, V> void consumeNestedStatic(InnerClassGen<T>.Outer<U>.Nested<V> nested) {
}

}
52 changes: 52 additions & 0 deletions sbt-test/pipelining/Yjava-tasty-paths/a/InnerClassSub.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package a;

public class InnerClassSub extends InnerClass {

public class InnerSub<U> extends Inner<U> {
public InnerSub(U innerField) {
super(innerField);
}
}

public class OuterSub<U> extends Outer<U> {
public OuterSub() {
super();
}
}

public <U> Inner<U> createInnerSub(U innerField) {
return new InnerSub<>(innerField);
}

public <U, V> Outer<U>.Nested<V> createNestedSub(U outerField, V innerField) {
OuterSub<U> outer = new OuterSub<>();
return outer.new Nested<>(outerField, innerField);
}

public <U> InnerClass.Inner<U> createInnerSub2(U innerField) {
return new InnerSub<>(innerField);
}

public <U, V> InnerClass.Outer<U>.Nested<V> createNestedSub2(U outerField, V innerField) {
OuterSub<U> outer = new OuterSub<>();
return outer.new Nested<>(outerField, innerField);
}

public static <U> InnerClass.Inner<U> createInnerStatic(U innerField) {
InnerClassSub innerClass = new InnerClassSub();
return innerClass.new Inner<>(innerField);
}

public static <U, V> InnerClass.Outer<U>.Nested<V> createNestedStatic(U outerField, V innerField) {
InnerClassSub innerClass = new InnerClassSub();
InnerClassSub.Outer<U> outer = innerClass.new Outer<>();
return outer.new Nested<>(outerField, innerField);
}

public static <U, V> void consumeNestedStatic(InnerClass.Outer<U>.Nested<V> nested) {
}

public static <U, V> void consumeNestedStatic2(Outer<U>.Nested<V> nested) {
}

}
16 changes: 16 additions & 0 deletions sbt-test/pipelining/Yjava-tasty-paths/a/RawTypes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package a;

public class RawTypes {

public class C<T> {
public class D<U> {
}
}

public static void mii_Raw_Raw(RawTypes.C.D d) {
}

public static void mii_Raw_Raw2(C.D d) {
}

}
2 changes: 2 additions & 0 deletions sbt-test/pipelining/Yjava-tasty-paths/a/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// THIS FILE EXISTS SO THAT `A.java` WILL BE COMPILED BY SCALAC
package a
Loading