Skip to content
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

Fix the tparam bounds of exported inherited classes #18647

Merged
merged 6 commits into from
Feb 26, 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
55 changes: 40 additions & 15 deletions compiler/src/dotty/tools/dotc/core/TypeApplications.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
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)
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
}
}

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
Expand Down Expand Up @@ -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 _ =>
Expand Down Expand Up @@ -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)))),
dwijnand marked this conversation as resolved.
Show resolved Hide resolved
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
}
}
Expand Down
6 changes: 3 additions & 3 deletions compiler/src/dotty/tools/dotc/core/TypeComparer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Deriving.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Namer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/RefChecks.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion compiler/src/dotty/tools/dotc/typer/Typer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 34 additions & 0 deletions tests/neg/i7820.check
Original file line number Diff line number Diff line change
@@ -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.
4 changes: 2 additions & 2 deletions tests/neg/i7820.scala
Original file line number Diff line number Diff line change
@@ -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
11 changes: 11 additions & 0 deletions tests/pos/i18569.reg1.scala
Original file line number Diff line number Diff line change
@@ -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()
11 changes: 11 additions & 0 deletions tests/pos/i18569.scala
Original file line number Diff line number Diff line change
@@ -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]
Loading