@@ -849,6 +849,85 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi
849
849
case _ =>
850
850
isSubType(pre1, pre2)
851
851
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
+
852
931
/** Subtype test for the hk application `tp2 = tycon2[args2]`.
853
932
*/
854
933
def compareAppliedType2 (tp2 : AppliedType , tycon2 : Type , args2 : List [Type ]): Boolean = {
@@ -952,38 +1031,9 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi
952
1031
* or fallback to fourthTry.
953
1032
*/
954
1033
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
- */
969
1034
def appOK (tp1base : Type ) = tp1base match {
970
1035
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 )
987
1037
case _ => false
988
1038
}
989
1039
@@ -1065,8 +1115,8 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi
1065
1115
tycon1 match {
1066
1116
case param1 : TypeParamRef =>
1067
1117
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 )
1070
1120
case _ =>
1071
1121
false
1072
1122
}
0 commit comments