diff --git a/compiler/src/dotty/tools/dotc/core/ContextOps.scala b/compiler/src/dotty/tools/dotc/core/ContextOps.scala index 920da377f9b4..55fb31fd1916 100644 --- a/compiler/src/dotty/tools/dotc/core/ContextOps.scala +++ b/compiler/src/dotty/tools/dotc/core/ContextOps.scala @@ -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. @@ -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) { @@ -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 diff --git a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala index a8a1be309b0c..d18f88dabfd8 100644 --- a/compiler/src/dotty/tools/dotc/core/SymDenotations.scala +++ b/compiler/src/dotty/tools/dotc/core/SymDenotations.scala @@ -1501,6 +1501,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`. * diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index 9e4768922ad3..e9b00cf95cac 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1539,17 +1539,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 => @@ -1618,7 +1620,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))) ) ) ) diff --git a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala index 0896d78c7229..02d8c2f51bf4 100644 --- a/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala +++ b/compiler/src/dotty/tools/dotc/typer/TypeAssigner.scala @@ -150,7 +150,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) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 55adda0505ae..43e5d52d397a 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -453,6 +453,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) @@ -4210,14 +4214,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.is(Mode.QuotedPattern) || tree1.tpe <:< pt) tree1 else err.typeMismatch(tree1, pt) diff --git a/tests/neg/i19619/InnerClass.java b/tests/neg/i19619/InnerClass.java new file mode 100644 index 000000000000..32bb1642a232 --- /dev/null +++ b/tests/neg/i19619/InnerClass.java @@ -0,0 +1,67 @@ +// InnerClass.java + +package lib; + +public class InnerClass { + + public class Inner { + public U innerField; + + public Inner(U innerField) { + this.innerField = innerField; + } + + public U getInnerField() { + return innerField; + } + } + + public class Outer { + + public class Nested { + + 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 Inner createInner(U innerField) { + return new Inner<>(innerField); + } + + public Outer.Nested createNested(U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static InnerClass.Inner createInnerStatic(U innerField) { + InnerClass innerClass = new InnerClass(); + return innerClass.new Inner<>(innerField); + } + + public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { + InnerClass innerClass = new InnerClass(); + InnerClass.Outer outer = innerClass.new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { + } + + public static void consumeNestedStatic2(Outer.Nested nested) { + } + +} diff --git a/tests/neg/i19619/InnerClassGen.java b/tests/neg/i19619/InnerClassGen.java new file mode 100644 index 000000000000..3a691aa0608f --- /dev/null +++ b/tests/neg/i19619/InnerClassGen.java @@ -0,0 +1,80 @@ +// InnerClassGen.java + +package lib; + +public class InnerClassGen { + + public class Inner { + 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 { + + public class Nested { + 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 { + public static class NestedStatic { + } + } + + public Inner createInner(T rootField, U innerField) { + return new Inner<>(rootField, innerField); + } + + public Outer.Nested createNested(T rootField, U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static InnerClassGen.Inner createInnerStatic(T rootField, U innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + return innerClassGen.new Inner<>(rootField, innerField); + } + + public static InnerClassGen.Outer.Nested createNestedStatic(T rootField, U outerField, V innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + InnerClassGen.Outer outer = innerClassGen.new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static void consumeNestedStatic(InnerClassGen.Outer.Nested nested) { + } + +} diff --git a/tests/neg/i19619/Test.scala b/tests/neg/i19619/Test.scala new file mode 100644 index 000000000000..85f9b3e529ac --- /dev/null +++ b/tests/neg/i19619/Test.scala @@ -0,0 +1,12 @@ +import lib.InnerClass +import lib.InnerClassGen + +@main def Test = + + locally: + val ici: InnerClass = new InnerClass() + val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error + + locally: + val ici: InnerClassGen[String] = new InnerClassGen() + val ici_inner1: ici.Inner[Long] = ici.createInner[Long]("Hello", 47L) // error diff --git a/tests/run/i19619/InnerClass.java b/tests/run/i19619/InnerClass.java new file mode 100644 index 000000000000..32bb1642a232 --- /dev/null +++ b/tests/run/i19619/InnerClass.java @@ -0,0 +1,67 @@ +// InnerClass.java + +package lib; + +public class InnerClass { + + public class Inner { + public U innerField; + + public Inner(U innerField) { + this.innerField = innerField; + } + + public U getInnerField() { + return innerField; + } + } + + public class Outer { + + public class Nested { + + 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 Inner createInner(U innerField) { + return new Inner<>(innerField); + } + + public Outer.Nested createNested(U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static InnerClass.Inner createInnerStatic(U innerField) { + InnerClass innerClass = new InnerClass(); + return innerClass.new Inner<>(innerField); + } + + public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { + InnerClass innerClass = new InnerClass(); + InnerClass.Outer outer = innerClass.new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { + } + + public static void consumeNestedStatic2(Outer.Nested nested) { + } + +} diff --git a/tests/run/i19619/InnerClassGen.java b/tests/run/i19619/InnerClassGen.java new file mode 100644 index 000000000000..3a691aa0608f --- /dev/null +++ b/tests/run/i19619/InnerClassGen.java @@ -0,0 +1,80 @@ +// InnerClassGen.java + +package lib; + +public class InnerClassGen { + + public class Inner { + 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 { + + public class Nested { + 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 { + public static class NestedStatic { + } + } + + public Inner createInner(T rootField, U innerField) { + return new Inner<>(rootField, innerField); + } + + public Outer.Nested createNested(T rootField, U outerField, V innerField) { + Outer outer = new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static InnerClassGen.Inner createInnerStatic(T rootField, U innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + return innerClassGen.new Inner<>(rootField, innerField); + } + + public static InnerClassGen.Outer.Nested createNestedStatic(T rootField, U outerField, V innerField) { + InnerClassGen innerClassGen = new InnerClassGen<>(); + InnerClassGen.Outer outer = innerClassGen.new Outer<>(); + return outer.new Nested<>(rootField, outerField, innerField); + } + + public static void consumeNestedStatic(InnerClassGen.Outer.Nested nested) { + } + +} diff --git a/tests/run/i19619/InnerClassSub.java b/tests/run/i19619/InnerClassSub.java new file mode 100644 index 000000000000..5a1d1f4d3857 --- /dev/null +++ b/tests/run/i19619/InnerClassSub.java @@ -0,0 +1,54 @@ +// InnerClass.java + +package lib; + +public class InnerClassSub extends InnerClass { + + public class InnerSub extends Inner { + public InnerSub(U innerField) { + super(innerField); + } + } + + public class OuterSub extends Outer { + public OuterSub() { + super(); + } + } + + public Inner createInnerSub(U innerField) { + return new InnerSub<>(innerField); + } + + public Outer.Nested createNestedSub(U outerField, V innerField) { + OuterSub outer = new OuterSub<>(); + return outer.new Nested<>(outerField, innerField); + } + + public InnerClass.Inner createInnerSub2(U innerField) { + return new InnerSub<>(innerField); + } + + public InnerClass.Outer.Nested createNestedSub2(U outerField, V innerField) { + OuterSub outer = new OuterSub<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static InnerClass.Inner createInnerStatic(U innerField) { + InnerClassSub innerClass = new InnerClassSub(); + return innerClass.new Inner<>(innerField); + } + + public static InnerClass.Outer.Nested createNestedStatic(U outerField, V innerField) { + InnerClassSub innerClass = new InnerClassSub(); + InnerClassSub.Outer outer = innerClass.new Outer<>(); + return outer.new Nested<>(outerField, innerField); + } + + public static void consumeNestedStatic(InnerClass.Outer.Nested nested) { + } + + public static void consumeNestedStatic2(Outer.Nested nested) { + } + +} diff --git a/tests/run/i19619/RawTypes.java b/tests/run/i19619/RawTypes.java new file mode 100644 index 000000000000..4373a04093eb --- /dev/null +++ b/tests/run/i19619/RawTypes.java @@ -0,0 +1,18 @@ +// RawTypes.java + +package lib; + +public class RawTypes { + + public class C { + public class D { + } + } + + public static void mii_Raw_Raw(RawTypes.C.D d) { + } + + public static void mii_Raw_Raw2(C.D d) { + } + +} diff --git a/tests/run/i19619/Test.scala b/tests/run/i19619/Test.scala new file mode 100644 index 000000000000..871e7a490ea0 --- /dev/null +++ b/tests/run/i19619/Test.scala @@ -0,0 +1,61 @@ +// scalajs: --skip + +import lib.InnerClass +import lib.InnerClassGen +import lib.RawTypes +import lib.InnerClassSub + +@main def Test = + + locally: + val ici: InnerClass = new InnerClass() + // val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error + val ici_inner2: InnerClass#Inner[Long] = ici.createInner[Long](47L) + val ici_inner3: InnerClass#Inner[Long] = InnerClass.createInnerStatic[Long](47L) + + val ici_outer: InnerClass#Outer[Long] = new ici.Outer[Long]() + val ici_nested1: InnerClass#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int](47L, 23) + val ici_nested2: InnerClass#Outer[Long]#Nested[Int] = ici.createNested[Long, Int](47L, 23) + val ici_nested3: InnerClass#Outer[Long]#Nested[Int] = InnerClass.createNestedStatic[Long, Int](47L, 23) + + InnerClass.consumeNestedStatic(ici_nested3) + InnerClass.consumeNestedStatic2(ici_nested3) + + locally: + val ici: InnerClassGen[String] = new InnerClassGen() + // val ici_inner1: ici.Inner[Long] = ici.createInner[Long]("Hello", 47L) // error + val ici_inner2: InnerClassGen[String]#Inner[Long] = ici.createInner[Long]("Hello", 47L) + val ici_inner3: InnerClassGen[String]#Inner[Long] = InnerClassGen.createInnerStatic[String, Long]("Hello", 47L) + + val ici_outer: InnerClassGen[String]#Outer[Long] = new ici.Outer[Long]() + val ici_nested1: InnerClassGen[String]#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int]("Hello", 47L, 23) + val ici_nested2: InnerClassGen[String]#Outer[Long]#Nested[Int] = ici.createNested[Long, Int]("Hello", 47L, 23) + val ici_nested3: InnerClassGen[String]#Outer[Long]#Nested[Int] = InnerClassGen.createNestedStatic[String, Long, Int]("Hello", 47L, 23) + + InnerClassGen.consumeNestedStatic(ici_nested3) + + locally: + val rt: RawTypes = new RawTypes() + val c: RawTypes#C[String] = new rt.C[String]() + + val cd_ii: RawTypes#C[String]#D[String] = new c.D[String]() + val cd_ii_Raw: RawTypes#C[?]#D[?] = cd_ii + + RawTypes.mii_Raw_Raw(cd_ii_Raw) + RawTypes.mii_Raw_Raw2(cd_ii_Raw) + + locally: + val ici: InnerClassSub = new InnerClassSub() + // val ici_inner1: ici.Inner[Long] = ici.createInner[Long](47L) // error + val ici_inner2: InnerClass#Inner[Long] = ici.createInnerSub[Long](47L) + val ici_inner2_2: InnerClass#Inner[Long] = ici.createInnerSub2[Long](47L) + val ici_inner3: InnerClass#Inner[Long] = InnerClassSub.createInnerStatic[Long](47L) + + val ici_outer: InnerClassSub#Outer[Long] = new ici.Outer[Long]() + val ici_nested1: InnerClassSub#Outer[Long]#Nested[Int] = new ici_outer.Nested[Int](47L, 23) + val ici_nested2: InnerClass#Outer[Long]#Nested[Int] = ici.createNestedSub[Long, Int](47L, 23) + val ici_nested2_2: InnerClass#Outer[Long]#Nested[Int] = ici.createNestedSub2[Long, Int](47L, 23) + val ici_nested3: InnerClass#Outer[Long]#Nested[Int] = InnerClassSub.createNestedStatic[Long, Int](47L, 23) + + InnerClass.consumeNestedStatic(ici_nested3) + InnerClass.consumeNestedStatic2(ici_nested3)