Skip to content

Commit e777f34

Browse files
authored
Merge pull request #5957 from dotty-staging/fix-#5948
Fix #5948: Be more careful with capturing
2 parents 63ce36a + d4306dd commit e777f34

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

42 files changed

+389
-138
lines changed

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -888,13 +888,20 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
888888
tree.select(defn.Any_asInstanceOf).appliedToType(tp)
889889
}
890890

891-
/** `tree.asInstanceOf[tp]` (or its box/unbox/cast equivalent when after
891+
/** cast tree to `tp`, assuming no exception is raised, i.e the operation is pure */
892+
def cast(tp: Type)(implicit ctx: Context): Tree = {
893+
assert(tp.isValueType, i"bad cast: $tree.asInstanceOf[$tp]")
894+
tree.select(if (ctx.erasedTypes) defn.Any_asInstanceOf else defn.Any_typeCast)
895+
.appliedToType(tp)
896+
}
897+
898+
/** cast `tree` to `tp` (or its box/unbox/cast equivalent when after
892899
* erasure and value and non-value types are mixed),
893900
* unless tree's type already conforms to `tp`.
894901
*/
895902
def ensureConforms(tp: Type)(implicit ctx: Context): Tree =
896903
if (tree.tpe <:< tp) tree
897-
else if (!ctx.erasedTypes) asInstance(tp)
904+
else if (!ctx.erasedTypes) cast(tp)
898905
else Erasure.Boxing.adaptToType(tree, tp)
899906

900907
/** `tree ne null` (might need a cast to be type correct) */

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ class Definitions {
277277
lazy val Any_isInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOf_, _ => BooleanType, Final)
278278
lazy val Any_asInstanceOf: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOf_, _.paramRefs(0), Final)
279279
lazy val Any_typeTest: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.isInstanceOfPM, _ => BooleanType, Final | Synthetic | Artifact)
280-
lazy val Any_typeCast: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOfPM, _.paramRefs(0), Final | Synthetic | Artifact | Erased)
280+
lazy val Any_typeCast: TermSymbol = enterT1ParameterlessMethod(AnyClass, nme.asInstanceOfPM, _.paramRefs(0), Final | Synthetic | Artifact | StableRealizable)
281281
// generated by pattern matcher, eliminated by erasure
282282

283283
def AnyMethods: List[TermSymbol] = List(Any_==, Any_!=, Any_equals, Any_hashCode,
@@ -680,7 +680,7 @@ class Definitions {
680680
lazy val ModuleSerializationProxyType: TypeRef = ctx.requiredClassRef("scala.runtime.ModuleSerializationProxy")
681681
def ModuleSerializationProxyClass(implicit ctx: Context): ClassSymbol = ModuleSerializationProxyType.symbol.asClass
682682
lazy val ModuleSerializationProxyConstructor: TermSymbol =
683-
ModuleSerializationProxyClass.requiredMethod(nme.CONSTRUCTOR, List(ClassType(WildcardType)))
683+
ModuleSerializationProxyClass.requiredMethod(nme.CONSTRUCTOR, List(ClassType(TypeBounds.empty)))
684684

685685
lazy val GenericType: TypeRef = ctx.requiredClassRef("scala.reflect.Generic")
686686
def GenericClass(implicit ctx: Context): ClassSymbol = GenericType.symbol.asClass
@@ -755,6 +755,10 @@ class Definitions {
755755

756756
def Eql_eqlAny(implicit ctx: Context): TermSymbol = EqlModule.requiredMethod(nme.eqlAny)
757757

758+
lazy val TypeBoxType: TypeRef = ctx.requiredClassRef("scala.internal.TypeBox")
759+
760+
lazy val TypeBox_CAP: TypeSymbol = TypeBoxType.symbol.requiredType(tpnme.CAP)
761+
758762
lazy val NotType: TypeRef = ctx.requiredClassRef("scala.implicits.Not")
759763
def NotClass(implicit ctx: Context): ClassSymbol = NotType.symbol.asClass
760764
def NotModule(implicit ctx: Context): Symbol = NotClass.companionModule

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

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -166,8 +166,6 @@ class OrderingConstraint(private val boundsMap: ParamBounds,
166166
entries != null && isBounds(entries(pnum)) && (typeVar(entries, pnum) eq tvar)
167167
}
168168

169-
private def isBounds(tp: Type) = tp.isInstanceOf[TypeBounds]
170-
171169
// ---------- Dependency handling ----------------------------------------------
172170

173171
def lower(param: TypeParamRef): List[TypeParamRef] = lowerLens(this, param.binder, param.paramNum)

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,7 @@ object StdNames {
326326
val AnnotatedType: N = "AnnotatedType"
327327
val AppliedTypeTree: N = "AppliedTypeTree"
328328
val ArrayAnnotArg: N = "ArrayAnnotArg"
329+
val CAP: N = "CAP"
329330
val Constant: N = "Constant"
330331
val ConstantType: N = "ConstantType"
331332
val doubleHash: N = "doubleHash"

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1790,7 +1790,7 @@ object SymDenotations {
17901790
case LambdaParam(_, _) :: _ =>
17911791
recur(tp.superType)
17921792
case tparams: List[Symbol @unchecked] =>
1793-
new ctx.SubstApproxMap(tparams, args).apply(recur(tycon))
1793+
recur(tycon).substApprox(tparams, args)
17941794
}
17951795
record(tp, baseTp)
17961796
baseTp

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ object TypeApplications {
106106
private[this] var available = (0 until args.length).toSet
107107
var allReplaced: Boolean = true
108108
def hasWildcardArg(p: TypeParamRef): Boolean =
109-
p.binder == tycon && args(p.paramNum).isInstanceOf[TypeBounds]
109+
p.binder == tycon && isBounds(args(p.paramNum))
110110
def canReduceWildcard(p: TypeParamRef): Boolean =
111111
!ctx.mode.is(Mode.AllowLambdaWildcardApply) || available.contains(p.paramNum)
112112
def atNestedLevel(op: => Type): Type = {
@@ -356,7 +356,7 @@ class TypeApplications(val self: Type) extends AnyVal {
356356
else dealiased match {
357357
case dealiased: HKTypeLambda =>
358358
def tryReduce =
359-
if (!args.exists(_.isInstanceOf[TypeBounds])) {
359+
if (!args.exists(isBounds)) {
360360
val followAlias = Config.simplifyApplications && {
361361
dealiased.resType match {
362362
case AppliedType(tyconBody, dealiasedArgs) =>

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

Lines changed: 137 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)