@@ -849,6 +849,85 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi
849849 case _ =>
850850 isSubType(pre1, pre2)
851851
852+ /** Compare `tycon[args]` with `other := otherTycon[otherArgs]`, via `>:>` if fromBelow is true, `<:<` otherwise
853+ * (we call this relationship `~:~` in the rest of this comment).
854+ *
855+ * This method works by:
856+ *
857+ * 1. Choosing an appropriate type constructor `adaptedTycon`
858+ * 2. Constraining `tycon` such that `tycon ~:~ adaptedTycon`
859+ * 3. Recursing on `adaptedTycon[args] ~:~ other`
860+ *
861+ * So, how do we pick `adaptedTycon`? When `args` and `otherArgs` have the
862+ * same length the answer is simply:
863+ *
864+ * adaptedTycon := otherTycon
865+ *
866+ * But we also handle having `args.length < otherArgs.length`, in which
867+ * case we need to make up a type constructor of the right kind. For
868+ * example, if `fromBelow = false` and we're comparing:
869+ *
870+ * ?F[A] <:< Either[String, B] where `?F <: [X] =>> Any`
871+ *
872+ * we will choose:
873+ *
874+ * adaptedTycon := [X] =>> Either[String, X]
875+ *
876+ * this allows us to constrain:
877+ *
878+ * ?F <: adaptedTycon
879+ *
880+ * and then recurse on:
881+ *
882+ * adaptedTycon[A] <:< Either[String, B]
883+ *
884+ * In general, given:
885+ *
886+ * - k := args.length
887+ * - d := otherArgs.length - k
888+ *
889+ * `adaptedTycon` will be:
890+ *
891+ * [T_0, ..., T_k-1] =>> otherTycon[otherArgs(0), ..., otherArgs(d-1), T_0, ..., T_k-1]
892+ *
893+ * where `T_n` has the same bounds as `otherTycon.typeParams(d+n)`
894+ *
895+ * Historical note: this strategy is known in Scala as "partial unification"
896+ * (even though the type constructor variable isn't actually unified but only
897+ * has one of its bounds constrained), for background see:
898+ * - The infamous SI-2712: https://github.com/scala/bug/issues/2712
899+ * - The PR against Scala 2.12 implementing -Ypartial-unification: https://github.com/scala/scala/pull/5102
900+ * - Some explanations on how this impacts API design: https://gist.github.com/djspiewak/7a81a395c461fd3a09a6941d4cd040f2
901+ */
902+ def compareAppliedTypeParamRef (tycon : TypeParamRef , args : List [Type ], other : AppliedType , fromBelow : Boolean ): Boolean =
903+ def directionalIsSubType (tp1 : Type , tp2 : Type ): Boolean =
904+ if fromBelow then isSubType(tp2, tp1) else isSubType(tp1, tp2)
905+ def directionalRecur (tp1 : Type , tp2 : Type ): Boolean =
906+ if fromBelow then recur(tp2, tp1) else recur(tp1, tp2)
907+
908+ val otherTycon = other.tycon
909+ val otherArgs = other.args
910+
911+ val d = otherArgs.length - args.length
912+ d >= 0 && {
913+ val tparams = tycon.typeParams
914+ val remainingTparams = otherTycon.typeParams.drop(d)
915+ variancesConform(remainingTparams, tparams) && {
916+ val adaptedTycon =
917+ if d > 0 then
918+ HKTypeLambda (remainingTparams.map(_.paramName))(
919+ tl => remainingTparams.map(remainingTparam =>
920+ tl.integrate(remainingTparams, remainingTparam.paramInfo).bounds),
921+ tl => otherTycon.appliedTo(
922+ otherArgs.take(d) ++ tl.paramRefs))
923+ else
924+ otherTycon
925+ (assumedTrue(tycon) || directionalIsSubType(tycon, adaptedTycon.ensureLambdaSub)) &&
926+ directionalRecur(adaptedTycon.appliedTo(args), other)
927+ }
928+ }
929+ end compareAppliedTypeParamRef
930+
852931 /** Subtype test for the hk application `tp2 = tycon2[args2]`.
853932 */
854933 def compareAppliedType2 (tp2 : AppliedType , tycon2 : Type , args2 : List [Type ]): Boolean = {
@@ -952,38 +1031,9 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi
9521031 * or fallback to fourthTry.
9531032 */
9541033 def canInstantiate (tycon2 : TypeParamRef ): Boolean = {
955-
956- /** Let
957- *
958- * `tparams_1, ..., tparams_k-1` be the type parameters of the rhs
959- * `tparams1_1, ..., tparams1_n-1` be the type parameters of the constructor of the lhs
960- * `args1_1, ..., args1_n-1` be the type arguments of the lhs
961- * `d = n - k`
962- *
963- * Returns `true` iff `d >= 0` and `tycon2` can be instantiated to
964- *
965- * [tparams1_d, ... tparams1_n-1] -> tycon1[args_1, ..., args_d-1, tparams_d, ... tparams_n-1]
966- *
967- * such that the resulting type application is a supertype of `tp1`.
968- */
9691034 def appOK (tp1base : Type ) = tp1base match {
9701035 case tp1base : AppliedType =>
971- var tycon1 = tp1base.tycon
972- val args1 = tp1base.args
973- val tparams1all = tycon1.typeParams
974- val lengthDiff = tparams1all.length - tparams.length
975- lengthDiff >= 0 && {
976- val tparams1 = tparams1all.drop(lengthDiff)
977- variancesConform(tparams1, tparams) && {
978- if (lengthDiff > 0 )
979- tycon1 = HKTypeLambda (tparams1.map(_.paramName))(
980- tl => tparams1.map(tparam => tl.integrate(tparams, tparam.paramInfo).bounds),
981- tl => tp1base.tycon.appliedTo(args1.take(lengthDiff) ++
982- tparams1.indices.toList.map(tl.paramRefs(_))))
983- (assumedTrue(tycon2) || isSubType(tycon1.ensureLambdaSub, tycon2)) &&
984- recur(tp1, tycon1.appliedTo(args2))
985- }
986- }
1036+ compareAppliedTypeParamRef(tycon2, args2, tp1base, fromBelow = true )
9871037 case _ => false
9881038 }
9891039
@@ -1065,8 +1115,8 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi
10651115 tycon1 match {
10661116 case param1 : TypeParamRef =>
10671117 def canInstantiate = tp2 match {
1068- case AppliedType (tycon2, args2) =>
1069- isSubType (param1, tycon2.ensureLambdaSub) && isSubArgs( args1, args2, tp1, tycon2.typeParams )
1118+ case tp2base : AppliedType =>
1119+ compareAppliedTypeParamRef (param1, args1, tp2base, fromBelow = false )
10701120 case _ =>
10711121 false
10721122 }
0 commit comments