Skip to content

Commit 5a884bc

Browse files
authored
Fix the tparam bounds of exported inherited classes (#18647)
2 parents b701542 + 000de84 commit 5a884bc

File tree

11 files changed

+106
-25
lines changed

11 files changed

+106
-25
lines changed

compiler/src/dotty/tools/dotc/core/TypeApplications.scala

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,11 @@ object TypeApplications {
3333
*/
3434
object EtaExpansion:
3535

36-
def apply(tycon: Type)(using Context): Type =
37-
assert(tycon.typeParams.nonEmpty, tycon)
38-
tycon.etaExpand(tycon.typeParamSymbols)
39-
4036
/** Test that the parameter bounds in a hk type lambda `[X1,...,Xn] => C[X1, ..., Xn]`
4137
* contain the bounds of the type parameters of `C`. This is necessary to be able to
4238
* contract the hk lambda to `C`.
4339
*/
44-
private def weakerBounds(tp: HKTypeLambda, tparams: List[ParamInfo])(using Context): Boolean =
40+
private def weakerBounds(tp: HKTypeLambda, fn: Type)(using Context): Boolean =
4541
val onlyEmptyBounds = tp.typeParams.forall(_.paramInfo == TypeBounds.empty)
4642
onlyEmptyBounds
4743
// Note: this pre-test helps efficiency. It is also necessary to workaround #9965 since in some cases
@@ -50,18 +46,24 @@ object TypeApplications {
5046
// In this case, we can still return true if we know that the hk lambda bounds
5147
// are empty anyway.
5248
|| {
49+
val tparams = fn.typeParams
5350
val paramRefs = tparams.map(_.paramRef)
51+
val prefix = fn.normalizedPrefix
52+
val owner = fn.typeSymbol.maybeOwner
5453
tp.typeParams.corresponds(tparams) { (param1, param2) =>
55-
param2.paramInfo frozen_<:< param1.paramInfo.substParams(tp, paramRefs)
54+
// see tests/neg/variances-constr.scala
55+
// its B parameter should have info <: Any, using class C as the owner
56+
// rather than info <: A, using class Inner2 as the owner
57+
param2.paramInfo.asSeenFrom(prefix, owner) frozen_<:< param1.paramInfo.substParams(tp, paramRefs)
5658
}
5759
}
5860

5961
def unapply(tp: Type)(using Context): Option[Type] = tp match
60-
case tp @ HKTypeLambda(tparams, AppliedType(fn: Type, args))
62+
case tp @ HKTypeLambda(tparams, AppliedType(fn, args))
6163
if fn.typeSymbol.isClass
6264
&& tparams.hasSameLengthAs(args)
6365
&& args.lazyZip(tparams).forall((arg, tparam) => arg == tparam.paramRef)
64-
&& weakerBounds(tp, fn.typeParams) => Some(fn)
66+
&& weakerBounds(tp, fn) => Some(fn)
6567
case _ => None
6668

6769
end EtaExpansion
@@ -244,7 +246,7 @@ class TypeApplications(val self: Type) extends AnyVal {
244246
def topType(using Context): Type =
245247
if self.hasSimpleKind then
246248
defn.AnyType
247-
else etaExpand(self.typeParams) match
249+
else self.etaExpand match
248250
case tp: HKTypeLambda =>
249251
tp.derivedLambdaType(resType = tp.resultType.topType)
250252
case _ =>
@@ -301,21 +303,44 @@ class TypeApplications(val self: Type) extends AnyVal {
301303
/** Convert a type constructor `TC` which has type parameters `X1, ..., Xn`
302304
* to `[X1, ..., Xn] -> TC[X1, ..., Xn]`.
303305
*/
304-
def etaExpand(tparams: List[TypeParamInfo])(using Context): Type =
305-
HKTypeLambda.fromParams(tparams, self.appliedTo(tparams.map(_.paramRef)))
306-
//.ensuring(res => res.EtaReduce =:= self, s"res = $res, core = ${res.EtaReduce}, self = $self, hc = ${res.hashCode}")
306+
def etaExpand(using Context): Type =
307+
val tparams = self.typeParams
308+
val resType = self.appliedTo(tparams.map(_.paramRef))
309+
self.dealias match
310+
case self: TypeRef if tparams.nonEmpty && self.symbol.isClass =>
311+
val owner = self.symbol.owner
312+
// Calling asSeenFrom on the type parameter infos is important
313+
// so that class type references within another prefix have
314+
// their type parameters' info fixed.
315+
// e.g. from pos/i18569:
316+
// trait M1:
317+
// trait A
318+
// trait F[T <: A]
319+
// object M2 extends M1
320+
// Type parameter T in M1.F has an upper bound of M1#A
321+
// But eta-expanding M2.F should have type parameters with an upper-bound of M2.A.
322+
// So we take the prefix M2.type and the F symbol's owner, M1,
323+
// to call asSeenFrom on T's info.
324+
HKTypeLambda(tparams.map(_.paramName))(
325+
tl => tparams.map(p => HKTypeLambda.toPInfo(tl.integrate(tparams, p.paramInfo.asSeenFrom(self.prefix, owner)))),
326+
tl => tl.integrate(tparams, resType))
327+
case _ =>
328+
HKTypeLambda.fromParams(tparams, resType)
307329

308330
/** If self is not lambda-bound, eta expand it. */
309331
def ensureLambdaSub(using Context): Type =
310-
if (isLambdaSub) self else EtaExpansion(self)
332+
if isLambdaSub then self
333+
else
334+
assert(self.typeParams.nonEmpty, self)
335+
self.etaExpand
311336

312337
/** Eta expand if `self` is a (non-lambda) class reference and `bound` is a higher-kinded type */
313338
def etaExpandIfHK(bound: Type)(using Context): Type = {
314339
val hkParams = bound.hkTypeParams
315340
if (hkParams.isEmpty) self
316341
else self match {
317-
case self: TypeRef if self.symbol.isClass && self.typeParams.length == hkParams.length =>
318-
EtaExpansion(self)
342+
case self: TypeRef if self.symbol.isClass && self.typeParams.hasSameLengthAs(hkParams) =>
343+
etaExpand
319344
case _ => self
320345
}
321346
}

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -594,7 +594,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
594594
if (base.typeSymbol == cls2) return true
595595
}
596596
else if tp1.typeParams.nonEmpty && !tp1.isAnyKind then
597-
return recur(tp1, EtaExpansion(tp2))
597+
return recur(tp1, tp2.etaExpand)
598598
fourthTry
599599
}
600600

@@ -734,7 +734,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
734734
case _ =>
735735
val tparams1 = tp1.typeParams
736736
if (tparams1.nonEmpty)
737-
return recur(tp1.etaExpand(tparams1), tp2) || fourthTry
737+
return recur(tp1.etaExpand, tp2) || fourthTry
738738
tp2 match {
739739
case EtaExpansion(tycon2: TypeRef) if tycon2.symbol.isClass && tycon2.symbol.is(JavaDefined) =>
740740
recur(tp1, tycon2) || fourthTry
@@ -2820,7 +2820,7 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
28202820
tp.symbol match
28212821
case cls: ClassSymbol =>
28222822
if cls == defn.SingletonClass then defn.AnyType
2823-
else if cls.typeParams.nonEmpty then EtaExpansion(tp)
2823+
else if cls.typeParams.nonEmpty then tp.etaExpand
28242824
else tp
28252825
case sym =>
28262826
if !ctx.erasedTypes && sym == defn.FromJavaObjectSymbol then defn.AnyType

compiler/src/dotty/tools/dotc/core/unpickleScala2/Scala2Unpickler.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -850,7 +850,7 @@ class Scala2Unpickler(bytes: Array[Byte], classRoot: ClassDenotation, moduleClas
850850
}
851851
else if args.nonEmpty then
852852
tycon.safeAppliedTo(EtaExpandIfHK(sym.typeParams, args.map(translateTempPoly)))
853-
else if (sym.typeParams.nonEmpty) tycon.etaExpand(sym.typeParams)
853+
else if (sym.typeParams.nonEmpty) tycon.etaExpand
854854
else tycon
855855
case TYPEBOUNDStpe =>
856856
val lo = readTypeRef()

compiler/src/dotty/tools/dotc/typer/Deriving.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -165,7 +165,7 @@ trait Deriving {
165165
// case (a) ... see description above
166166
val derivedParams = clsParams.dropRight(instanceArity)
167167
val instanceType =
168-
if (instanceArity == clsArity) clsType.etaExpand(clsParams)
168+
if (instanceArity == clsArity) clsType.etaExpand
169169
else {
170170
val derivedParamTypes = derivedParams.map(_.typeRef)
171171

compiler/src/dotty/tools/dotc/typer/Namer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1200,7 +1200,7 @@ class Namer { typer: Typer =>
12001200
val forwarderName = checkNoConflict(alias.toTypeName, isPrivate = false, span)
12011201
var target = pathType.select(sym)
12021202
if target.typeParams.nonEmpty then
1203-
target = target.etaExpand(target.typeParams)
1203+
target = target.etaExpand
12041204
newSymbol(
12051205
cls, forwarderName,
12061206
MandatoryExportTypeFlags | (sym.flags & RetainedExportTypeFlags),

compiler/src/dotty/tools/dotc/typer/RefChecks.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ object RefChecks {
377377
*/
378378
def checkOverride(checkSubType: (Type, Type) => Context ?=> Boolean, member: Symbol, other: Symbol): Unit =
379379
def memberTp(self: Type) =
380-
if (member.isClass) TypeAlias(member.typeRef.etaExpand(member.typeParams))
380+
if (member.isClass) TypeAlias(member.typeRef.etaExpand)
381381
else self.memberInfo(member)
382382
def otherTp(self: Type) =
383383
self.memberInfo(other)

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4284,7 +4284,7 @@ class Typer(@constructorOnly nestingLevel: Int = 0) extends Namer
42844284
tree1.withType(tp1)
42854285
else
42864286
// Eta-expand higher-kinded type
4287-
val tp1 = tree.tpe.etaExpand(tp.typeParamSymbols)
4287+
val tp1 = tree.tpe.etaExpand
42884288
tree.withType(tp1)
42894289
}
42904290
if (ctx.mode.is(Mode.Pattern) || ctx.mode.isQuotedPattern || tree1.tpe <:< pt) tree1

tests/neg/i7820.check

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
-- [E046] Cyclic Error: tests/neg/i7820.scala:1:23 ---------------------------------------------------------------------
2+
1 |trait A1 { type F[X <: F[_, _], Y] } // error: cyclic reference involving type F
3+
| ^
4+
| Cyclic reference involving type F
5+
|
6+
| Run with -explain-cyclic for more details.
7+
|
8+
| longer explanation available when compiling with `-explain`
9+
-- [E046] Cyclic Error: tests/neg/i7820.scala:2:23 ---------------------------------------------------------------------
10+
2 |trait A2 { type F[X <: F, Y] } // error: cyclic reference involving type F
11+
| ^
12+
| Cyclic reference involving type F
13+
|
14+
| Run with -explain-cyclic for more details.
15+
|
16+
| longer explanation available when compiling with `-explain`
17+
-- [E046] Cyclic Error: tests/neg/i7820.scala:3:23 ---------------------------------------------------------------------
18+
3 |trait A3 { type F[X >: F, Y] } // error: cyclic reference involving type F
19+
| ^
20+
| Cyclic reference involving type F
21+
|
22+
| Run with -explain-cyclic for more details.
23+
|
24+
| longer explanation available when compiling with `-explain`
25+
-- Warning: tests/neg/i7820.scala:1:25 ---------------------------------------------------------------------------------
26+
1 |trait A1 { type F[X <: F[_, _], Y] } // error: cyclic reference involving type F
27+
| ^
28+
| `_` is deprecated for wildcard arguments of types: use `?` instead
29+
| This construct can be rewritten automatically under -rewrite -source 3.4-migration.
30+
-- Warning: tests/neg/i7820.scala:1:28 ---------------------------------------------------------------------------------
31+
1 |trait A1 { type F[X <: F[_, _], Y] } // error: cyclic reference involving type F
32+
| ^
33+
| `_` is deprecated for wildcard arguments of types: use `?` instead
34+
| This construct can be rewritten automatically under -rewrite -source 3.4-migration.

tests/neg/i7820.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
trait A1 { type F[X <: F[_, _], Y] } // error: cyclic reference involving type F
2-
trait A2 { type F[X <: F, Y] } // error: cyclic reference involving type F // error
3-
trait A3 { type F[X >: F, Y] } // error: cyclic reference involving type F // error
2+
trait A2 { type F[X <: F, Y] } // error: cyclic reference involving type F
3+
trait A3 { type F[X >: F, Y] } // error: cyclic reference involving type F

tests/pos/i18569.reg1.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Minimisation of the CI failure
2+
// in scala-parallel-collections
3+
// to do with how EtaExpansion is used
4+
// by typeOfNew when typing a New tree
5+
6+
trait Foo[+A]:
7+
class Bar[B >: A]
8+
9+
class Test:
10+
def t1[X](foo: Foo[X]): Unit =
11+
val bar = new foo.Bar()

tests/pos/i18569.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
trait M1:
2+
trait A
3+
trait F[T <: A]
4+
type G[T <: A] = F[T]
5+
6+
object M2 extends M1
7+
8+
trait Test:
9+
export M2.*
10+
def y: F[A]
11+
def z: G[A]

0 commit comments

Comments
 (0)