@@ -130,21 +130,51 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
130
130
}
131
131
}
132
132
133
- private [this ] var approx : ApproxState = NoApprox
133
+ /** The current approximation state. See `ApproxState`. */
134
+ private [this ] var approx : ApproxState = FreshApprox
134
135
protected def approxState : ApproxState = approx
135
136
137
+ /** The original left-hand type of the comparison. Gets reset
138
+ * everytime we compare components of the previous pair of types.
139
+ * This type is used for capture conversion in `isSubArgs`.
140
+ */
141
+ private [this ] var leftRoot : Type = _
142
+
136
143
protected def isSubType (tp1 : Type , tp2 : Type , a : ApproxState ): Boolean = {
137
- val saved = approx
138
- this .approx = a
144
+ val savedApprox = approx
145
+ val savedLeftRoot = leftRoot
146
+ if (a == FreshApprox ) {
147
+ this .approx = NoApprox
148
+ this .leftRoot = tp1
149
+ }
150
+ else this .approx = a
139
151
try recur(tp1, tp2)
140
152
catch {
141
153
case ex : Throwable => handleRecursive(" subtype" , i " $tp1 <:< $tp2" , ex, weight = 2 )
142
154
}
143
- finally this .approx = saved
155
+ finally {
156
+ this .approx = savedApprox
157
+ this .leftRoot = savedLeftRoot
158
+ }
144
159
}
145
160
146
- def isSubType (tp1 : Type , tp2 : Type )(implicit nc : AbsentContext ): Boolean = isSubType(tp1, tp2, NoApprox )
147
-
161
+ def isSubType (tp1 : Type , tp2 : Type )(implicit nc : AbsentContext ): Boolean = isSubType(tp1, tp2, FreshApprox )
162
+
163
+ /** The inner loop of the isSubType comparison.
164
+ * Recursive calls from recur should go to recur directly if the two types
165
+ * compared in the callee are essentially the same as the types compared in the
166
+ * caller. "The same" means: represent essentially the same sets of values.
167
+ * `recur` should not be used to compare components of types. In this case
168
+ * one should use `isSubType(_, _)`.
169
+ * `recur` should also not be used to compare approximated versions of the original
170
+ * types (as when we go from an abstract type to one of its bounds). In that case
171
+ * one should use `isSubType(_, _, a)` where `a` defines the kind of approximation.
172
+ *
173
+ * Note: Logicaly, `recur` could be nested in `isSubType`, which would avoid
174
+ * the instance state consisting `approx` and `leftRoot`. But then the implemented
175
+ * code would have two extra parameters for each of the many calls that go from
176
+ * one sub-part of isSubType to another.
177
+ */
148
178
protected def recur (tp1 : Type , tp2 : Type ): Boolean = trace(s " isSubType ${traceInfo(tp1, tp2)} $approx" , subtyping) {
149
179
150
180
def monitoredIsSubType = {
@@ -277,7 +307,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
277
307
case tp2 : SuperType =>
278
308
def compareSuper = tp1 match {
279
309
case tp1 : SuperType =>
280
- isSubType (tp1.thistpe, tp2.thistpe) &&
310
+ recur (tp1.thistpe, tp2.thistpe) &&
281
311
isSameType(tp1.supertpe, tp2.supertpe)
282
312
case _ =>
283
313
secondTry
@@ -355,7 +385,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
355
385
}
356
386
case tp1 : SkolemType =>
357
387
tp2 match {
358
- case tp2 : SkolemType if ! ctx.phase.isTyper && isSubType (tp1.info, tp2.info) => true
388
+ case tp2 : SkolemType if ! ctx.phase.isTyper && recur (tp1.info, tp2.info) => true
359
389
case _ => thirdTry
360
390
}
361
391
case tp1 : TypeVar =>
@@ -449,7 +479,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
449
479
// So if the constraint is not yet frozen, we do the same comparison again
450
480
// with a frozen constraint, which means that we get a chance to do the
451
481
// widening in `fourthTry` before adding to the constraint.
452
- if (frozenConstraint) isSubType (tp1, bounds(tp2).lo)
482
+ if (frozenConstraint) recur (tp1, bounds(tp2).lo)
453
483
else isSubTypeWhenFrozen(tp1, tp2)
454
484
alwaysTrue || {
455
485
if (canConstrain(tp2) && ! approx.low)
@@ -879,7 +909,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
879
909
compareLower(info2, tyconIsTypeRef = true )
880
910
case info2 : ClassInfo =>
881
911
tycon2.name.toString.startsWith(" Tuple" ) &&
882
- defn.isTupleType(tp2) && isSubType (tp1, tp2.toNestedPairs) ||
912
+ defn.isTupleType(tp2) && recur (tp1, tp2.toNestedPairs) ||
883
913
tryBaseType(info2.cls)
884
914
case _ =>
885
915
fourthTry
@@ -1001,44 +1031,93 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
1001
1031
}
1002
1032
1003
1033
/** Subtype test for corresponding arguments in `args1`, `args2` according to
1004
- * variances in type parameters `tparams`.
1034
+ * variances in type parameters `tparams2`.
1035
+ * @param tp1 The applied type containing `args1`
1036
+ * @param tparams2 The type parameters of the type constructor applied to `args2`
1005
1037
*/
1006
- def isSubArgs (args1 : List [Type ], args2 : List [Type ], tp1 : Type , tparams : List [ParamInfo ]): Boolean =
1007
- if (args1.isEmpty) args2.isEmpty
1008
- else args2.nonEmpty && {
1009
- val tparam = tparams.head
1010
- val v = tparam.paramVariance
1011
-
1012
- def compareCaptured (arg1 : Type , arg2 : Type ): Boolean = arg1 match {
1013
- case arg1 : TypeBounds =>
1014
- val captured = TypeRef (tp1, tparam.asInstanceOf [TypeSymbol ])
1015
- isSubArg(captured, arg2)
1016
- case _ =>
1017
- false
1018
- }
1038
+ def isSubArgs (args1 : List [Type ], args2 : List [Type ], tp1 : Type , tparams2 : List [ParamInfo ]): Boolean = {
1019
1039
1020
- def isSubArg (arg1 : Type , arg2 : Type ): Boolean = arg2 match {
1021
- case arg2 : TypeBounds =>
1022
- arg2.contains(arg1) || compareCaptured(arg1, arg2)
1023
- case _ =>
1024
- arg1 match {
1025
- case arg1 : TypeBounds =>
1026
- compareCaptured(arg1, arg2)
1027
- case _ =>
1028
- (v > 0 || isSubType(arg2, arg1)) &&
1029
- (v < 0 || isSubType(arg1, arg2))
1030
- }
1031
- }
1040
+ /** The bounds of parameter `tparam`, where all references to type paramneters
1041
+ * are replaced by corresponding arguments (or their approximations in the case of
1042
+ * wildcard arguments).
1043
+ */
1044
+ def paramBounds (tparam : Symbol ): TypeBounds =
1045
+ tparam.info.substApprox(tparams2.asInstanceOf [List [Symbol ]], args2).bounds
1032
1046
1033
- val arg1 = args1.head
1034
- val arg2 = args2.head
1035
- isSubArg(arg1, arg2) || {
1036
- // last effort: try to adapt variances of higher-kinded types if this is sound.
1037
- // TODO: Move this to eta-expansion?
1038
- val adapted2 = arg2.adaptHkVariances(tparam.paramInfo)
1039
- adapted2.ne(arg2) && isSubArg(arg1, adapted2)
1040
- }
1041
- } && isSubArgs(args1.tail, args2.tail, tp1, tparams.tail)
1047
+ def recurArgs (args1 : List [Type ], args2 : List [Type ], tparams2 : List [ParamInfo ]): Boolean =
1048
+ if (args1.isEmpty) args2.isEmpty
1049
+ else args2.nonEmpty && {
1050
+ val tparam = tparams2.head
1051
+ val v = tparam.paramVariance
1052
+
1053
+ /** Try a capture conversion:
1054
+ * If the original left-hand type `leftRoot` is a path `p.type`,
1055
+ * and the current widened left type is an application with wildcard arguments
1056
+ * such as `C[_]`, where `X` is `C`'s type parameter corresponding to the `_` argument,
1057
+ * compare with `C[p.X]` instead. Otherwise return `false`.
1058
+ * Also do a capture conversion in either of the following cases:
1059
+ *
1060
+ * - If we are after typer. We generally relax soundness requirements then.
1061
+ * We need the relaxed condition to correctly compute overriding relationships.
1062
+ * Missing this case led to AbstractMethod errors in the bootstrap.
1063
+ *
1064
+ * - If we are in mode TypevarsMissContext, which means we test implicits
1065
+ * for eligibility. In this case, we can be more permissive, since it's
1066
+ * just a pre-check. This relaxation is needed since the full
1067
+ * implicit typing might perform an adaptation that skolemizes the
1068
+ * type of a synthesized tree before comparing it with an expected type.
1069
+ * But no such adaptation is applied for implicit eligibility
1070
+ * testing, so we have to compensate.
1071
+ *
1072
+ * Note: Doing the capture conversion on path types is actually not necessary
1073
+ * since we can already deal with the situation through skolemization in Typer#captureWildcards.
1074
+ * But performance tests indicate that it's better to do it, since we avoid
1075
+ * skolemizations, which are more expensive . And, besides, capture conversion on
1076
+ * paths is less intrusive than skolemization.
1077
+ */
1078
+ def compareCaptured (arg1 : TypeBounds , arg2 : Type ) = tparam match {
1079
+ case tparam : Symbol
1080
+ if leftRoot.isStable || ctx.isAfterTyper || ctx.mode.is(Mode .TypevarsMissContext ) =>
1081
+ val captured = TypeRef (leftRoot, tparam)
1082
+ assert(captured.exists, i " $leftRoot has no member $tparam in isSubArgs( $args1, $args2, $tp1, $tparams2) " )
1083
+ isSubArg(captured, arg2)
1084
+ case _ =>
1085
+ false
1086
+ }
1087
+
1088
+ def isSubArg (arg1 : Type , arg2 : Type ): Boolean = arg2 match {
1089
+ case arg2 : TypeBounds =>
1090
+ val arg1norm = arg1 match {
1091
+ case arg1 : TypeBounds =>
1092
+ tparam match {
1093
+ case tparam : Symbol => arg1 & paramBounds(tparam)
1094
+ case _ => arg1 // This case can only arise when a hk-type is illegally instantiated with a wildcard
1095
+ }
1096
+ case _ => arg1
1097
+ }
1098
+ arg2.contains(arg1norm)
1099
+ case _ =>
1100
+ arg1 match {
1101
+ case arg1 : TypeBounds =>
1102
+ compareCaptured(arg1, arg2)
1103
+ case _ =>
1104
+ (v > 0 || isSubType(arg2, arg1)) &&
1105
+ (v < 0 || isSubType(arg1, arg2))
1106
+ }
1107
+ }
1108
+
1109
+ val arg1 = args1.head
1110
+ val arg2 = args2.head
1111
+ isSubArg(arg1, arg2) || {
1112
+ // last effort: try to adapt variances of higher-kinded types if this is sound.
1113
+ // TODO: Move this to eta-expansion?
1114
+ val adapted2 = arg2.adaptHkVariances(tparam.paramInfo)
1115
+ adapted2.ne(arg2) && isSubArg(arg1, adapted2)
1116
+ }
1117
+ } && recurArgs(args1.tail, args2.tail, tparams2.tail)
1118
+
1119
+ recurArgs(args1, args2, tparams2)
1120
+ }
1042
1121
1043
1122
/** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where
1044
1123
* - `B` derives from one of the class symbols of `tp2`,
@@ -1493,7 +1572,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
1493
1572
if (common.exists) common
1494
1573
else if (v > 0 ) glb(arg1.hiBound, arg2.hiBound)
1495
1574
else if (v < 0 ) lub(arg1.loBound, arg2.loBound)
1496
- else if (arg1. isInstanceOf [ TypeBounds ] || arg2. isInstanceOf [ TypeBounds ] )
1575
+ else if (isBounds( arg1) || isBounds( arg2) )
1497
1576
TypeBounds (lub(arg1.loBound, arg2.loBound),
1498
1577
glb(arg1.hiBound, arg2.hiBound))
1499
1578
else if (homogenizeArgs && ! frozenConstraint && isSameType(arg1, arg2)) arg1
@@ -1813,6 +1892,12 @@ object TypeComparer {
1813
1892
private val LoApprox = 1
1814
1893
private val HiApprox = 2
1815
1894
1895
+ /** The approximation state indicates how the pair of types currently compared
1896
+ * relates to the types compared originally.
1897
+ * - `NoApprox`: They are still the same types
1898
+ * - `LoApprox`: The left type is approximated (i.e widened)"
1899
+ * - `HiApprox`: The right type is approximated (i.e narrowed)"
1900
+ */
1816
1901
class ApproxState (private val bits : Int ) extends AnyVal {
1817
1902
override def toString : String = {
1818
1903
val lo = if ((bits & LoApprox ) != 0 ) " LoApprox" else " "
@@ -1827,6 +1912,12 @@ object TypeComparer {
1827
1912
1828
1913
val NoApprox : ApproxState = new ApproxState (0 )
1829
1914
1915
+ /** A special approximation state to indicate that this is the first time we
1916
+ * compare (approximations of) this pair of types. It's converted to `NoApprox`
1917
+ * in `isSubType`, but also leads to `leftRoot` being set there.
1918
+ */
1919
+ val FreshApprox : ApproxState = new ApproxState (4 )
1920
+
1830
1921
/** Show trace of comparison operations when performing `op` as result string */
1831
1922
def explaining [T ](say : String => Unit )(op : Context => T )(implicit ctx : Context ): T = {
1832
1923
val nestedCtx = ctx.fresh.setTypeComparerFn(new ExplainingTypeComparer (_))
0 commit comments