Skip to content

Commit 4c46cee

Browse files
authored
Fix higher-order unification incorrectly substituting tparams (#16181)
When creating a fresh type lambda for the purpose of higher-order type inference, we incorrectly substituted references to type parameters before this commit. We want to construct: bodyArgs := otherArgs.take(d), T_0, ..., T_k-1 [T_0, ..., T_k-1] =>> otherTycon[bodyArgs] For this type to be valid, we need the bounds of `T_i` to be the bounds of the (d+i) type parameter of `otherTycon` after substituting references to each type parameter of `otherTycon` by the corresponding argument in `bodyArgs`. The previous implementation incorrectly substituted only the last `k` type parameters, this was not enough for correctness. It could also lead to a crash because it called `integrate` which implicitly assumes it is passed a full list of type parameters (this is now documented). Fixes #15983.
2 parents 7a0165b + 48c7964 commit 4c46cee

File tree

5 files changed

+51
-8
lines changed

5 files changed

+51
-8
lines changed

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,12 @@ class TypeApplications(val self: Type) extends AnyVal {
204204
}
205205
}
206206

207+
/** Substitute in `self` the type parameters of `tycon` by some other types. */
208+
final def substTypeParams(tycon: Type, to: List[Type])(using Context): Type =
209+
(tycon.typeParams: @unchecked) match
210+
case LambdaParam(lam, _) :: _ => self.substParams(lam, to)
211+
case params: List[Symbol @unchecked] => self.subst(params, to)
212+
207213
/** If `self` is a higher-kinded type, its type parameters, otherwise Nil */
208214
final def hkTypeParams(using Context): List[TypeParamInfo] =
209215
if (isLambdaSub) typeParams else Nil

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

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1066,12 +1066,16 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
10661066
*
10671067
* - k := args.length
10681068
* - d := otherArgs.length - k
1069+
* - T_0, ..., T_k-1 fresh type parameters
1070+
* - bodyArgs := otherArgs.take(d), T_0, ..., T_k-1
10691071
*
1070-
* `adaptedTycon` will be:
1072+
* Then,
10711073
*
1072-
* [T_0, ..., T_k-1] =>> otherTycon[otherArgs(0), ..., otherArgs(d-1), T_0, ..., T_k-1]
1074+
* adaptedTycon := [T_0, ..., T_k-1] =>> otherTycon[bodyArgs]
10731075
*
1074-
* where `T_n` has the same bounds as `otherTycon.typeParams(d+n)`
1076+
* where the bounds of `T_i` are set based on the bounds of `otherTycon.typeParams(d+i)`
1077+
* after substituting type parameter references by the corresponding argument
1078+
* in `bodyArgs` (see `adaptedBounds` in the implementation).
10751079
*
10761080
* Historical note: this strategy is known in Scala as "partial unification"
10771081
* (even though the type constructor variable isn't actually unified but only
@@ -1096,11 +1100,18 @@ class TypeComparer(@constructorOnly initctx: Context) extends ConstraintHandling
10961100
variancesConform(remainingTparams, tparams) && {
10971101
val adaptedTycon =
10981102
if d > 0 then
1103+
val initialArgs = otherArgs.take(d)
1104+
/** The arguments passed to `otherTycon` in the body of `tl` */
1105+
def bodyArgs(tl: HKTypeLambda) = initialArgs ++ tl.paramRefs
1106+
/** The bounds of the type parameters of `tl` */
1107+
def adaptedBounds(tl: HKTypeLambda) =
1108+
val bodyArgsComputed = bodyArgs(tl)
1109+
remainingTparams.map(_.paramInfo)
1110+
.mapconserve(_.substTypeParams(otherTycon, bodyArgsComputed).bounds)
1111+
10991112
HKTypeLambda(remainingTparams.map(_.paramName))(
1100-
tl => remainingTparams.map(remainingTparam =>
1101-
tl.integrate(remainingTparams, remainingTparam.paramInfo).bounds),
1102-
tl => otherTycon.appliedTo(
1103-
otherArgs.take(d) ++ tl.paramRefs))
1113+
adaptedBounds,
1114+
tl => otherTycon.appliedTo(bodyArgs(tl)))
11041115
else
11051116
otherTycon
11061117
(assumedTrue(tycon) || directionalIsSubType(tycon, adaptedTycon)) &&

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3600,10 +3600,14 @@ object Types {
36003600

36013601
/** The type `[tparams := paramRefs] tp`, where `tparams` can be
36023602
* either a list of type parameter symbols or a list of lambda parameters
3603+
*
3604+
* @pre If `tparams` is a list of lambda parameters, then it must be the
3605+
* full, in-order list of type parameters of some type constructor, as
3606+
* can be obtained using `TypeApplications#typeParams`.
36033607
*/
36043608
def integrate(tparams: List[ParamInfo], tp: Type)(using Context): Type =
36053609
(tparams: @unchecked) match {
3606-
case LambdaParam(lam, _) :: _ => tp.subst(lam, this)
3610+
case LambdaParam(lam, _) :: _ => tp.subst(lam, this) // This is where the precondition is necessary.
36073611
case params: List[Symbol @unchecked] => tp.subst(params, paramRefs)
36083612
}
36093613

tests/pos/i15983a.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class OtherC[A, B, C <: B]
2+
3+
trait crash {
4+
type OtherT[A, B, C <: B]
5+
6+
def indexK[F[_]]: F[Any] = ???
7+
8+
def res: OtherT[Any, Any, Any] = indexK
9+
10+
def res2: OtherC[Any, Any, Any] = indexK
11+
}

tests/pos/i15983b.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
class OtherC[A, B, C <: B, D <: C]
2+
3+
trait crash {
4+
type OtherT[A, B, C <: B, D <: C]
5+
6+
def indexK[F[X, Y <: X]]: F[Any, Any] = ???
7+
8+
def res: OtherT[Any, Any, Any, Any] = indexK
9+
10+
def res2: OtherC[Any, Any, Any, Any] = indexK
11+
}

0 commit comments

Comments
 (0)