@@ -130,21 +130,51 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
130130 }
131131 }
132132
133- private [this ] var approx : ApproxState = NoApprox
133+ /** The current approximation state. See `ApproxState`. */
134+ private [this ] var approx : ApproxState = FreshApprox
134135 protected def approxState : ApproxState = approx
135136
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+
136143 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
139151 try recur(tp1, tp2)
140152 catch {
141153 case ex : Throwable => handleRecursive(" subtype" , i " $tp1 <:< $tp2" , ex, weight = 2 )
142154 }
143- finally this .approx = saved
155+ finally {
156+ this .approx = savedApprox
157+ this .leftRoot = savedLeftRoot
158+ }
144159 }
145160
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+ */
148178 protected def recur (tp1 : Type , tp2 : Type ): Boolean = trace(s " isSubType ${traceInfo(tp1, tp2)} $approx" , subtyping) {
149179
150180 def monitoredIsSubType = {
@@ -277,7 +307,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
277307 case tp2 : SuperType =>
278308 def compareSuper = tp1 match {
279309 case tp1 : SuperType =>
280- isSubType (tp1.thistpe, tp2.thistpe) &&
310+ recur (tp1.thistpe, tp2.thistpe) &&
281311 isSameType(tp1.supertpe, tp2.supertpe)
282312 case _ =>
283313 secondTry
@@ -355,7 +385,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
355385 }
356386 case tp1 : SkolemType =>
357387 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
359389 case _ => thirdTry
360390 }
361391 case tp1 : TypeVar =>
@@ -449,7 +479,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
449479 // So if the constraint is not yet frozen, we do the same comparison again
450480 // with a frozen constraint, which means that we get a chance to do the
451481 // widening in `fourthTry` before adding to the constraint.
452- if (frozenConstraint) isSubType (tp1, bounds(tp2).lo)
482+ if (frozenConstraint) recur (tp1, bounds(tp2).lo)
453483 else isSubTypeWhenFrozen(tp1, tp2)
454484 alwaysTrue || {
455485 if (canConstrain(tp2) && ! approx.low)
@@ -879,7 +909,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
879909 compareLower(info2, tyconIsTypeRef = true )
880910 case info2 : ClassInfo =>
881911 tycon2.name.toString.startsWith(" Tuple" ) &&
882- defn.isTupleType(tp2) && isSubType (tp1, tp2.toNestedPairs) ||
912+ defn.isTupleType(tp2) && recur (tp1, tp2.toNestedPairs) ||
883913 tryBaseType(info2.cls)
884914 case _ =>
885915 fourthTry
@@ -1001,44 +1031,93 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
10011031 }
10021032
10031033 /** 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`
10051037 */
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 = {
10191039
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
10321046
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+ }
10421121
10431122 /** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where
10441123 * - `B` derives from one of the class symbols of `tp2`,
@@ -1493,7 +1572,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
14931572 if (common.exists) common
14941573 else if (v > 0 ) glb(arg1.hiBound, arg2.hiBound)
14951574 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) )
14971576 TypeBounds (lub(arg1.loBound, arg2.loBound),
14981577 glb(arg1.hiBound, arg2.hiBound))
14991578 else if (homogenizeArgs && ! frozenConstraint && isSameType(arg1, arg2)) arg1
@@ -1813,6 +1892,12 @@ object TypeComparer {
18131892 private val LoApprox = 1
18141893 private val HiApprox = 2
18151894
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+ */
18161901 class ApproxState (private val bits : Int ) extends AnyVal {
18171902 override def toString : String = {
18181903 val lo = if ((bits & LoApprox ) != 0 ) " LoApprox" else " "
@@ -1827,6 +1912,12 @@ object TypeComparer {
18271912
18281913 val NoApprox : ApproxState = new ApproxState (0 )
18291914
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+
18301921 /** Show trace of comparison operations when performing `op` as result string */
18311922 def explaining [T ](say : String => Unit )(op : Context => T )(implicit ctx : Context ): T = {
18321923 val nestedCtx = ctx.fresh.setTypeComparerFn(new ExplainingTypeComparer (_))
0 commit comments