@@ -15,7 +15,7 @@ import typer.Inferencing.isFullyDefined
15
15
import typer .RefChecks .{checkAllOverrides , checkSelfAgainstParents , OverridingPairsChecker }
16
16
import typer .Checking .{checkBounds , checkAppliedTypesIn }
17
17
import typer .ErrorReporting .{Addenda , NothingToAdd , err }
18
- import typer .ProtoTypes .{LhsProto , WildcardSelectionProto }
18
+ import typer .ProtoTypes .{LhsProto , WildcardSelectionProto , SelectionProto }
19
19
import util .{SimpleIdentitySet , EqHashMap , EqHashSet , SrcPos , Property }
20
20
import transform .{Recheck , PreRecheck , CapturedVars }
21
21
import Recheck .*
@@ -1309,17 +1309,64 @@ class CheckCaptures extends Recheck, SymTransformer:
1309
1309
override def checkConformsExpr (actual : Type , expected : Type , tree : Tree , addenda : Addenda )(using Context ): Type =
1310
1310
testAdapted(actual, expected, tree, addenda)(err.typeMismatch)
1311
1311
1312
+ @ annotation.tailrec
1313
+ private def findImpureUpperBound (tp : Type )(using Context ): Type = tp match
1314
+ case _ : SingletonType => findImpureUpperBound(tp.widen)
1315
+ case tp : TypeRef if tp.symbol.isAbstractOrParamType =>
1316
+ tp.info match
1317
+ case TypeBounds (_, hi) if hi.isBoxedCapturing => hi
1318
+ case TypeBounds (_, hi) => findImpureUpperBound(hi)
1319
+ case _ => NoType
1320
+ case _ => NoType
1321
+
1312
1322
inline def testAdapted (actual : Type , expected : Type , tree : Tree , addenda : Addenda )
1313
1323
(fail : (Tree , Type , Addenda ) => Unit )(using Context ): Type =
1324
+
1314
1325
var expected1 = alignDependentFunction(expected, actual.stripCapturing)
1315
1326
val falseDeps = expected1 ne expected
1316
- val actualBoxed = adapt(actual, expected1, tree)
1327
+ val actual1 =
1328
+ if expected.stripCapturing.isInstanceOf [SelectionProto ] then
1329
+ // If the expected type is a `SelectionProto`, we should be careful about cases when
1330
+ // the actual type is a type parameter (for instance, `X <: box IO^`).
1331
+ // If `X` were not widen to reveal the boxed type, both sides are unboxed and thus
1332
+ // no box adaptation happens. But it is unsound: selecting a member from `X` implicitly
1333
+ // unboxes the value.
1334
+ //
1335
+ // Therefore, when the expected type is a selection proto, we conservatively widen
1336
+ // the actual type to strip type parameters.
1337
+ val hi = findImpureUpperBound(actual)
1338
+ if ! hi.exists then actual else hi
1339
+ else actual
1340
+ val actualBoxed = adapt(actual1, expected1, tree)
1317
1341
// println(i"check conforms $actualBoxed <<< $expected1")
1318
1342
1319
1343
if actualBoxed eq actual then
1320
1344
// Only `addOuterRefs` when there is no box adaptation
1321
1345
expected1 = addOuterRefs(expected1, actual, tree.srcPos)
1322
- TypeComparer .compareResult(isCompatible(actualBoxed, expected1)) match
1346
+
1347
+ def tryCurrentType : Boolean =
1348
+ isCompatible(actualBoxed, expected1)
1349
+
1350
+ /** When the actual type is a named type, and the previous attempt failed, try to widen the named type
1351
+ * and try another time.
1352
+ *
1353
+ * This is useful for cases like:
1354
+ *
1355
+ * def id[X <: box IO^{a}](x: X): IO^{a} = x
1356
+ *
1357
+ * When typechecking the body, we need to show that `(x: X)` can be typed at `IO^{a}`.
1358
+ * In the first attempt, since `X` is simply a parameter reference, we treat it as non-boxed and perform
1359
+ * no box adptation. But its upper bound is in fact boxed, and adaptation is needed for typechecking the body.
1360
+ * In those cases, we widen such types and try box adaptation another time.
1361
+ */
1362
+ def tryWidenNamed : Boolean =
1363
+ val actual1 = findImpureUpperBound(actual)
1364
+ actual1.exists && {
1365
+ val actualBoxed1 = adapt(actual1, expected1, tree)
1366
+ isCompatible(actualBoxed1, expected1)
1367
+ }
1368
+
1369
+ TypeComparer .compareResult(tryCurrentType || tryWidenNamed) match
1323
1370
case TypeComparer .CompareResult .Fail (notes) =>
1324
1371
capt.println(i " conforms failed for ${tree}: $actual vs $expected" )
1325
1372
if falseDeps then expected1 = unalignFunction(expected1)
@@ -1477,7 +1524,8 @@ class CheckCaptures extends Recheck, SymTransformer:
1477
1524
(actualShape, CaptureSet ())
1478
1525
end adaptShape
1479
1526
1480
- def adaptStr = i " adapting $actual ${if covariant then " ~~>" else " <~~" } $expected"
1527
+ // val adaptStr = i"adapting $actual ${if covariant then "~~>" else "<~~"} $expected"
1528
+ // println(adaptStr)
1481
1529
1482
1530
// Get wildcards out of the way
1483
1531
expected match
0 commit comments