@@ -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 = {
@@ -860,13 +939,35 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi
860
939
*/
861
940
def isMatchingApply (tp1 : Type ): Boolean = tp1 match {
862
941
case AppliedType (tycon1, args1) =>
863
- def loop (tycon1 : Type , args1 : List [Type ]): Boolean = tycon1.dealiasKeepRefiningAnnots match {
942
+ // We intentionally do not dealias `tycon1` or `tycon2` here.
943
+ // `TypeApplications#appliedTo` already takes care of dealiasing type
944
+ // constructors when this can be done without affecting type
945
+ // inference, doing it here would not only prevent code from compiling
946
+ // but could also result in the wrong thing being inferred later, for example
947
+ // in `tests/run/hk-alias-unification.scala` we end up checking:
948
+ //
949
+ // Foo[?F, ?T] <:< Foo[[X] =>> (X, String), Int]
950
+ //
951
+ // Naturally, we'd like to infer:
952
+ //
953
+ // ?F := [X] => (X, String)
954
+ //
955
+ // but if we dealias `Foo` then we'll end up trying to check:
956
+ //
957
+ // ErasedFoo[?F[?T]] <:< ErasedFoo[(Int, String)]
958
+ //
959
+ // Because of partial unification, this will succeed, but will produce the constraint:
960
+ //
961
+ // ?F := [X] =>> (Int, X)
962
+ //
963
+ // Which is not what we wanted!
964
+ def loop (tycon1 : Type , args1 : List [Type ]): Boolean = tycon1 match {
864
965
case tycon1 : TypeParamRef =>
865
966
(tycon1 == tycon2 ||
866
967
canConstrain(tycon1) && isSubType(tycon1, tycon2)) &&
867
968
isSubArgs(args1, args2, tp1, tparams)
868
969
case tycon1 : TypeRef =>
869
- tycon2.dealiasKeepRefiningAnnots match {
970
+ tycon2 match {
870
971
case tycon2 : TypeRef =>
871
972
val tycon1sym = tycon1.symbol
872
973
val tycon2sym = tycon2.symbol
@@ -926,60 +1027,26 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi
926
1027
927
1028
/** `param2` can be instantiated to a type application prefix of the LHS
928
1029
* or to a type application prefix of one of the LHS base class instances
929
- * and the resulting type application is a supertype of `tp1`,
930
- * or fallback to fourthTry.
1030
+ * and the resulting type application is a supertype of `tp1`.
931
1031
*/
932
1032
def canInstantiate (tycon2 : TypeParamRef ): Boolean = {
933
-
934
- /** Let
935
- *
936
- * `tparams_1, ..., tparams_k-1` be the type parameters of the rhs
937
- * `tparams1_1, ..., tparams1_n-1` be the type parameters of the constructor of the lhs
938
- * `args1_1, ..., args1_n-1` be the type arguments of the lhs
939
- * `d = n - k`
940
- *
941
- * Returns `true` iff `d >= 0` and `tycon2` can be instantiated to
942
- *
943
- * [tparams1_d, ... tparams1_n-1] -> tycon1[args_1, ..., args_d-1, tparams_d, ... tparams_n-1]
944
- *
945
- * such that the resulting type application is a supertype of `tp1`.
946
- */
947
1033
def appOK (tp1base : Type ) = tp1base match {
948
1034
case tp1base : AppliedType =>
949
- var tycon1 = tp1base.tycon
950
- val args1 = tp1base.args
951
- val tparams1all = tycon1.typeParams
952
- val lengthDiff = tparams1all.length - tparams.length
953
- lengthDiff >= 0 && {
954
- val tparams1 = tparams1all.drop(lengthDiff)
955
- variancesConform(tparams1, tparams) && {
956
- if (lengthDiff > 0 )
957
- tycon1 = HKTypeLambda (tparams1.map(_.paramName))(
958
- tl => tparams1.map(tparam => tl.integrate(tparams, tparam.paramInfo).bounds),
959
- tl => tp1base.tycon.appliedTo(args1.take(lengthDiff) ++
960
- tparams1.indices.toList.map(tl.paramRefs(_))))
961
- (assumedTrue(tycon2) || isSubType(tycon1.ensureLambdaSub, tycon2)) &&
962
- recur(tp1, tycon1.appliedTo(args2))
963
- }
964
- }
1035
+ compareAppliedTypeParamRef(tycon2, args2, tp1base, fromBelow = true )
965
1036
case _ => false
966
1037
}
967
1038
968
- tp1.widen match {
969
- case tp1w : AppliedType => appOK(tp1w)
970
- case tp1w =>
971
- tp1w.typeSymbol.isClass && {
972
- val classBounds = tycon2.classSymbols
973
- def liftToBase (bcs : List [ClassSymbol ]): Boolean = bcs match {
974
- case bc :: bcs1 =>
975
- classBounds.exists(bc.derivesFrom) && appOK(nonExprBaseType(tp1, bc))
976
- || liftToBase(bcs1)
977
- case _ =>
978
- false
979
- }
980
- liftToBase(tp1w.baseClasses)
981
- } ||
982
- fourthTry
1039
+ val tp1w = tp1.widen
1040
+ appOK(tp1w) || tp1w.typeSymbol.isClass && {
1041
+ val classBounds = tycon2.classSymbols
1042
+ def liftToBase (bcs : List [ClassSymbol ]): Boolean = bcs match {
1043
+ case bc :: bcs1 =>
1044
+ classBounds.exists(bc.derivesFrom) && appOK(nonExprBaseType(tp1, bc))
1045
+ || liftToBase(bcs1)
1046
+ case _ =>
1047
+ false
1048
+ }
1049
+ liftToBase(tp1w.baseClasses)
983
1050
}
984
1051
}
985
1052
@@ -1043,8 +1110,8 @@ class TypeComparer(using val comparerCtx: Context) extends ConstraintHandling wi
1043
1110
tycon1 match {
1044
1111
case param1 : TypeParamRef =>
1045
1112
def canInstantiate = tp2 match {
1046
- case AppliedType (tycon2, args2) =>
1047
- isSubType (param1, tycon2.ensureLambdaSub) && isSubArgs( args1, args2, tp1, tycon2.typeParams )
1113
+ case tp2base : AppliedType =>
1114
+ compareAppliedTypeParamRef (param1, args1, tp2base, fromBelow = false )
1048
1115
case _ =>
1049
1116
false
1050
1117
}
0 commit comments