diff --git a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala index f1edd7cd8f8b..223d3bb11515 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeApplications.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeApplications.scala @@ -33,15 +33,11 @@ object TypeApplications { */ object EtaExpansion: - def apply(tycon: Type)(using Context): Type = - assert(tycon.typeParams.nonEmpty, tycon) - tycon.etaExpand(tycon.typeParamSymbols) - /** Test that the parameter bounds in a hk type lambda `[X1,...,Xn] => C[X1, ..., Xn]` * contain the bounds of the type parameters of `C`. This is necessary to be able to * contract the hk lambda to `C`. */ - private def weakerBounds(tp: HKTypeLambda, tparams: List[ParamInfo])(using Context): Boolean = + private def weakerBounds(tp: HKTypeLambda, fn: Type)(using Context): Boolean = val onlyEmptyBounds = tp.typeParams.forall(_.paramInfo == TypeBounds.empty) onlyEmptyBounds // Note: this pre-test helps efficiency. It is also necessary to workaround #9965 since in some cases @@ -50,18 +46,24 @@ object TypeApplications { // In this case, we can still return true if we know that the hk lambda bounds // are empty anyway. || { + val tparams = fn.typeParams val paramRefs = tparams.map(_.paramRef) + val prefix = fn.normalizedPrefix + val owner = fn.typeSymbol.maybeOwner tp.typeParams.corresponds(tparams) { (param1, param2) => - param2.paramInfo frozen_<:< param1.paramInfo.substParams(tp, paramRefs) + // see tests/neg/variances-constr.scala + // its B parameter should have info <: Any, using class C as the owner + // rather than info <: A, using class Inner2 as the owner + param2.paramInfo.asSeenFrom(prefix, owner) frozen_<:< param1.paramInfo.substParams(tp, paramRefs) } } def unapply(tp: Type)(using Context): Option[Type] = tp match - case tp @ HKTypeLambda(tparams, AppliedType(fn: Type, args)) + case tp @ HKTypeLambda(tparams, AppliedType(fn, args)) if fn.typeSymbol.isClass && tparams.hasSameLengthAs(args) && args.lazyZip(tparams).forall((arg, tparam) => arg == tparam.paramRef) - && weakerBounds(tp, fn.typeParams) => Some(fn) + && weakerBounds(tp, fn) => Some(fn) case _ => None end EtaExpansion @@ -244,7 +246,7 @@ class TypeApplications(val self: Type) extends AnyVal { def topType(using Context): Type = if self.hasSimpleKind then defn.AnyType - else etaExpand(self.typeParams) match + else self.etaExpand match case tp: HKTypeLambda => tp.derivedLambdaType(resType = tp.resultType.topType) case _ => @@ -301,21 +303,44 @@ class TypeApplications(val self: Type) extends AnyVal { /** Convert a type constructor `TC` which has type parameters `X1, ..., Xn` * to `[X1, ..., Xn] -> TC[X1, ..., Xn]`. */ - def etaExpand(tparams: List[TypeParamInfo])(using Context): Type = - HKTypeLambda.fromParams(tparams, self.appliedTo(tparams.map(_.paramRef))) - //.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}") + def etaExpand(using Context): Type = + val tparams = self.typeParams + val resType = self.appliedTo(tparams.map(_.paramRef)) + self.dealias match + case self: TypeRef if tparams.nonEmpty && self.symbol.isClass => + val owner = self.symbol.owner + // Calling asSeenFrom on the type parameter infos is important + // so that class type references within another prefix have + // their type parameters' info fixed. + // e.g. from pos/i18569: + // trait M1: + // trait A + // trait F[T <: A] + // object M2 extends M1 + // Type parameter T in M1.F has an upper bound of M1#A + // But eta-expanding M2.F should have type parameters with an upper-bound of M2.A. + // So we take the prefix M2.type and the F symbol's owner, M1, + // to call asSeenFrom on T's info. + HKTypeLambda(tparams.map(_.paramName))( + tl => tparams.map(p => HKTypeLambda.toPInfo(tl.integrate(tparams, p.paramInfo.asSeenFrom(self.prefix, owner)))), + tl => tl.integrate(tparams, resType)) + case _ => + HKTypeLambda.fromParams(tparams, resType) /** If self is not lambda-bound, eta expand it. */ def ensureLambdaSub(using Context): Type = - if (isLambdaSub) self else EtaExpansion(self) + if isLambdaSub then self + else + assert(self.typeParams.nonEmpty, self) + self.etaExpand /** Eta expand if `self` is a (non-lambda) class reference and `bound` is a higher-kinded type */ def etaExpandIfHK(bound: Type)(using Context): Type = { val hkParams = bound.hkTypeParams if (hkParams.isEmpty) self else self match { - case self: TypeRef if self.symbol.isClass && self.typeParams.length == hkParams.length => - EtaExpansion(self) + case self: TypeRef if self.symbol.isClass && self.typeParams.hasSameLengthAs(hkParams) => + etaExpand case _ => self } } diff --git a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala index b23bfe9fe14b..9a6ee7296239 100644 --- a/compiler/src/dotty/tools/dotc/core/TypeComparer.scala +++ b/compiler/src/dotty/tools/dotc/core/TypeComparer.scala @@ -594,7 +594,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling if (base.typeSymbol == cls2) return true } else if tp1.typeParams.nonEmpty && !tp1.isAnyKind then - return recur(tp1, EtaExpansion(tp2)) + return recur(tp1, tp2.etaExpand) fourthTry } @@ -734,7 +734,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling case _ => val tparams1 = tp1.typeParams if (tparams1.nonEmpty) - return recur(tp1.etaExpand(tparams1), tp2) || fourthTry + return recur(tp1.etaExpand, tp2) || fourthTry tp2 match { case EtaExpansion(tycon2: TypeRef) if tycon2.symbol.isClass && tycon2.symbol.is(JavaDefined) => recur(tp1, tycon2) || fourthTry @@ -2820,7 +2820,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling tp.symbol match case cls: ClassSymbol => if cls == defn.SingletonClass then defn.AnyType - else if cls.typeParams.nonEmpty then EtaExpansion(tp) + else if cls.typeParams.nonEmpty then tp.etaExpand else tp case sym => if !ctx.erasedTypes && sym == defn.FromJavaObjectSymbol then defn.AnyType diff --git a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala index 806f39ee0425..521306eb73fc 100644 --- a/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala +++ b/compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala @@ -850,7 +850,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas } else if args.nonEmpty then tycon.safeAppliedTo(EtaExpandIfHK(sym.typeParams, args.map(translateTempPoly))) - else if (sym.typeParams.nonEmpty) tycon.etaExpand(sym.typeParams) + else if (sym.typeParams.nonEmpty) tycon.etaExpand else tycon case TYPEBOUNDStpe => val lo = readTypeRef() diff --git a/compiler/src/dotty/tools/dotc/typer/Deriving.scala b/compiler/src/dotty/tools/dotc/typer/Deriving.scala index 6def1ecc30a8..f3be1dcff766 100644 --- a/compiler/src/dotty/tools/dotc/typer/Deriving.scala +++ b/compiler/src/dotty/tools/dotc/typer/Deriving.scala @@ -165,7 +165,7 @@ trait Deriving { // case (a) ... see description above val derivedParams = clsParams.dropRight(instanceArity) val instanceType = - if (instanceArity == clsArity) clsType.etaExpand(clsParams) + if (instanceArity == clsArity) clsType.etaExpand else { val derivedParamTypes = derivedParams.map(_.typeRef) diff --git a/compiler/src/dotty/tools/dotc/typer/Namer.scala b/compiler/src/dotty/tools/dotc/typer/Namer.scala index a48026063415..868fcb99dc38 100644 --- a/compiler/src/dotty/tools/dotc/typer/Namer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Namer.scala @@ -1200,7 +1200,7 @@ class Namer { typer: Typer => val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span) var target = pathType.select(sym) if target.typeParams.nonEmpty then - target = target.etaExpand(target.typeParams) + target = target.etaExpand newSymbol( cls, forwarderName, MandatoryExportTypeFlags | (sym.flags & RetainedExportTypeFlags), diff --git a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala index f0914a9f6664..cf30945a7751 100644 --- a/compiler/src/dotty/tools/dotc/typer/RefChecks.scala +++ b/compiler/src/dotty/tools/dotc/typer/RefChecks.scala @@ -377,7 +377,7 @@ object RefChecks { */ def checkOverride(checkSubType: (Type, Type) => Context ?=> Boolean, member: Symbol, other: Symbol): Unit = def memberTp(self: Type) = - if (member.isClass) TypeAlias(member.typeRef.etaExpand(member.typeParams)) + if (member.isClass) TypeAlias(member.typeRef.etaExpand) else self.memberInfo(member) def otherTp(self: Type) = self.memberInfo(other) diff --git a/compiler/src/dotty/tools/dotc/typer/Typer.scala b/compiler/src/dotty/tools/dotc/typer/Typer.scala index 165484091260..38e030ede0a9 100644 --- a/compiler/src/dotty/tools/dotc/typer/Typer.scala +++ b/compiler/src/dotty/tools/dotc/typer/Typer.scala @@ -4283,7 +4283,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer tree1.withType(tp1) else // Eta-expand higher-kinded type - val tp1 = tree.tpe.etaExpand(tp.typeParamSymbols) + val tp1 = tree.tpe.etaExpand tree.withType(tp1) } if (ctx.mode.is(Mode.Pattern) || ctx.mode.isQuotedPattern || tree1.tpe <:< pt) tree1 diff --git a/tests/neg/i7820.check b/tests/neg/i7820.check new file mode 100644 index 000000000000..14dfc910ae6d --- /dev/null +++ b/tests/neg/i7820.check @@ -0,0 +1,34 @@ +-- [E046] Cyclic Error: tests/neg/i7820.scala:1:23 --------------------------------------------------------------------- +1 |trait A1 { type F[X <: F[_, _], Y] } // error: cyclic reference involving type F + | ^ + | Cyclic reference involving type F + | + | Run with -explain-cyclic for more details. + | + | longer explanation available when compiling with `-explain` +-- [E046] Cyclic Error: tests/neg/i7820.scala:2:23 --------------------------------------------------------------------- +2 |trait A2 { type F[X <: F, Y] } // error: cyclic reference involving type F + | ^ + | Cyclic reference involving type F + | + | Run with -explain-cyclic for more details. + | + | longer explanation available when compiling with `-explain` +-- [E046] Cyclic Error: tests/neg/i7820.scala:3:23 --------------------------------------------------------------------- +3 |trait A3 { type F[X >: F, Y] } // error: cyclic reference involving type F + | ^ + | Cyclic reference involving type F + | + | Run with -explain-cyclic for more details. + | + | longer explanation available when compiling with `-explain` +-- Warning: tests/neg/i7820.scala:1:25 --------------------------------------------------------------------------------- +1 |trait A1 { type F[X <: F[_, _], Y] } // error: cyclic reference involving type F + | ^ + | `_` is deprecated for wildcard arguments of types: use `?` instead + | This construct can be rewritten automatically under -rewrite -source 3.4-migration. +-- Warning: tests/neg/i7820.scala:1:28 --------------------------------------------------------------------------------- +1 |trait A1 { type F[X <: F[_, _], Y] } // error: cyclic reference involving type F + | ^ + | `_` is deprecated for wildcard arguments of types: use `?` instead + | This construct can be rewritten automatically under -rewrite -source 3.4-migration. diff --git a/tests/neg/i7820.scala b/tests/neg/i7820.scala index 22ec8b7ac8b1..cc803aa1f3d6 100644 --- a/tests/neg/i7820.scala +++ b/tests/neg/i7820.scala @@ -1,3 +1,3 @@ trait A1 { type F[X <: F[_, _], Y] } // error: cyclic reference involving type F -trait A2 { type F[X <: F, Y] } // error: cyclic reference involving type F // error -trait A3 { type F[X >: F, Y] } // error: cyclic reference involving type F // error +trait A2 { type F[X <: F, Y] } // error: cyclic reference involving type F +trait A3 { type F[X >: F, Y] } // error: cyclic reference involving type F diff --git a/tests/pos/i18569.reg1.scala b/tests/pos/i18569.reg1.scala new file mode 100644 index 000000000000..188ee36a02b2 --- /dev/null +++ b/tests/pos/i18569.reg1.scala @@ -0,0 +1,11 @@ +// Minimisation of the CI failure +// in scala-parallel-collections +// to do with how EtaExpansion is used +// by typeOfNew when typing a New tree + +trait Foo[+A]: + class Bar[B >: A] + +class Test: + def t1[X](foo: Foo[X]): Unit = + val bar = new foo.Bar() diff --git a/tests/pos/i18569.scala b/tests/pos/i18569.scala new file mode 100644 index 000000000000..1457f6fbe5b6 --- /dev/null +++ b/tests/pos/i18569.scala @@ -0,0 +1,11 @@ +trait M1: + trait A + trait F[T <: A] + type G[T <: A] = F[T] + +object M2 extends M1 + +trait Test: + export M2.* + def y: F[A] + def z: G[A]