Skip to content

Commit 175f8cc

Browse files
committed
Document TypeComparer
1 parent e05c1a7 commit 175f8cc

File tree

2 files changed

+65
-14
lines changed

2 files changed

+65
-14
lines changed

compiler/src/dotty/tools/dotc/core/TypeComparer.scala

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -130,9 +130,14 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
130130
}
131131
}
132132

133+
/** The current approximation state. See `ApproxState`. */
133134
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+
*/
136141
private [this] var leftRoot: Type = null
137142

138143
protected def isSubType(tp1: Type, tp2: Type, a: ApproxState): Boolean = {
@@ -155,6 +160,16 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
155160

156161
def isSubType(tp1: Type, tp2: Type)(implicit nc: AbsentContext): Boolean = isSubType(tp1, tp2, FreshApprox)
157162

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+
*/
158173
protected def recur(tp1: Type, tp2: Type): Boolean = trace(s"isSubType ${traceInfo(tp1, tp2)} $approx", subtyping) {
159174

160175
def monitoredIsSubType = {
@@ -1017,15 +1032,49 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
10171032
*/
10181033
def isSubArgs(args1: List[Type], args2: List[Type], tp1: Type, tparams2: List[ParamInfo]): Boolean = {
10191034

1035+
/** The bounds of parameter `tparam`, where all references to type paramneters
1036+
* are replaced by corresponding arguments (or their approximations in the case of
1037+
* wildcard arguments).
1038+
*/
10201039
def paramBounds(tparam: Symbol): TypeBounds =
10211040
tparam.info.substApprox(tparams2.asInstanceOf[List[Symbol]], args2).bounds
10221041

1023-
def recur(args1: List[Type], args2: List[Type], tparams2: List[ParamInfo]): Boolean =
1042+
def recurArgs(args1: List[Type], args2: List[Type], tparams2: List[ParamInfo]): Boolean =
10241043
if (args1.isEmpty) args2.isEmpty
10251044
else args2.nonEmpty && {
10261045
val tparam = tparams2.head
10271046
val v = tparam.paramVariance
10281047

1048+
/** Try a capture conversion:
1049+
* If the original left-hand type `leftRoot` is a path `p.type`,
1050+
* and the current widened left type is an application with wildcard arguments
1051+
* such as `C[_]`, where `X` is `C`'s type parameter corresponding to the `_` argument,
1052+
* compare with `C[p.X]` instead. Otherwise return `false`.
1053+
* Also do a capture conversion in either of the following cases:
1054+
*
1055+
* - If we are after typer. We generally relax soundness requirements then.
1056+
* We need the relaxed condition to correctly compute overriding relationships.
1057+
* Missing this case led to AbstractMethod errors in the bootstrap.
1058+
*
1059+
* - If we are in mode TypevarsMissContext, which means we test implicits
1060+
* for eligibility. In this case, we can be more permissive, since it's
1061+
* just a pre-check. This relaxation is needed since the full
1062+
* implicit typing might perform an adaptation that skolemizes the
1063+
* type of a synthesized tree before comparing it with an expected type.
1064+
* This adaptation gives the tree a path type, so that capture conversion
1065+
* can do its thing. But no such adaptation is applied for implicit eligibility
1066+
* testing, so we have to compensate.
1067+
*/
1068+
def compareCaptured(arg1: TypeBounds, arg2: Type) = tparam match {
1069+
case tparam: Symbol
1070+
if leftRoot.isStable || ctx.isAfterTyper || ctx.mode.is(Mode.TypevarsMissContext) =>
1071+
val captured = TypeRef(leftRoot, tparam)
1072+
assert(captured.exists, i"$leftRoot has no member $tparam in isSubArgs($args1, $args2, $tp1, $tparams2)")
1073+
isSubArg(captured, arg2)
1074+
case _ =>
1075+
false
1076+
}
1077+
10291078
def isSubArg(arg1: Type, arg2: Type): Boolean = arg2 match {
10301079
case arg2: TypeBounds =>
10311080
val arg1norm = arg1 match {
@@ -1040,14 +1089,7 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
10401089
case _ =>
10411090
arg1 match {
10421091
case arg1: TypeBounds =>
1043-
tparam match {
1044-
case tparam: Symbol
1045-
if leftRoot.isStable || ctx.isAfterTyper || ctx.mode.is(Mode.TypevarsMissContext) =>
1046-
val captured = TypeRef(leftRoot, tparam)
1047-
isSubArg(captured, arg2)
1048-
case _ =>
1049-
false
1050-
}
1092+
compareCaptured(arg1, arg2)
10511093
case _ =>
10521094
(v > 0 || isSubType(arg2, arg1)) &&
10531095
(v < 0 || isSubType(arg1, arg2))
@@ -1062,9 +1104,9 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
10621104
val adapted2 = arg2.adaptHkVariances(tparam.paramInfo)
10631105
adapted2.ne(arg2) && isSubArg(arg1, adapted2)
10641106
}
1065-
} && recur(args1.tail, args2.tail, tparams2.tail)
1107+
} && recurArgs(args1.tail, args2.tail, tparams2.tail)
10661108

1067-
recur(args1, args2, tparams2)
1109+
recurArgs(args1, args2, tparams2)
10681110
}
10691111

10701112
/** Test whether `tp1` has a base type of the form `B[T1, ..., Tn]` where
@@ -1827,8 +1869,6 @@ class TypeComparer(initctx: Context) extends ConstraintHandling[AbsentContext] {
18271869

18281870
object TypeComparer {
18291871

1830-
val oldScheme = true
1831-
18321872
/** Class for unification variables used in `natValue`. */
18331873
private class AnyConstantType extends UncachedGroundType with ValueType {
18341874
var tpe: Type = NoType
@@ -1842,6 +1882,12 @@ object TypeComparer {
18421882
private val LoApprox = 1
18431883
private val HiApprox = 2
18441884

1885+
/** The approximation state indicates how the pair of types currently compared
1886+
* relates to the types compared originally.
1887+
* - `NoApprox`: They are still the same types
1888+
* - `LoApprox`: The left type is approximated (i.e widened)"
1889+
* - `HiApprox`: The right type is approximated (i.e narrowed)"
1890+
*/
18451891
class ApproxState(private val bits: Int) extends AnyVal {
18461892
override def toString: String = {
18471893
val lo = if ((bits & LoApprox) != 0) "LoApprox" else ""
@@ -1855,6 +1901,11 @@ object TypeComparer {
18551901
}
18561902

18571903
val NoApprox: ApproxState = new ApproxState(0)
1904+
1905+
/** A special approximation state to indicate that this is the first time we
1906+
* compare (approximations of) this pair of types. It's converted to `NoApprox`
1907+
* in `isSubType`, but also leads to `leftRoot` being set there.
1908+
*/
18581909
val FreshApprox: ApproxState = new ApproxState(4)
18591910

18601911
/** Show trace of comparison operations when performing `op` as result string */

compiler/src/dotty/tools/dotc/typer/Typer.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2715,7 +2715,7 @@ class Typer extends Namer
27152715
// If tree's type is not stable and has wildcard arguments, make it
27162716
// stable by casting it to a skolem. I.e. a tree `t` of widened type `T`
27172717
// gets widened to `t.$asInstanceOf$[SkolemType(T)]`. This will enable
2718-
// capture comversion in the subsequent subtype check.
2718+
// capture conversion in the subsequent subtype check.
27192719
if (!tree.tpe.isStable && hasWildcardArg(wtp) && !ctx.isAfterTyper)
27202720
return readapt(tree.cast(SkolemType(wtp)))
27212721

0 commit comments

Comments
 (0)