@@ -15,7 +15,7 @@ import typer.Inferencing.isFullyDefined
1515import typer .RefChecks .{checkAllOverrides , checkSelfAgainstParents , OverridingPairsChecker }
1616import typer .Checking .{checkBounds , checkAppliedTypesIn }
1717import typer .ErrorReporting .{Addenda , NothingToAdd , err }
18- import typer .ProtoTypes .{LhsProto , WildcardSelectionProto }
18+ import typer .ProtoTypes .{LhsProto , WildcardSelectionProto , SelectionProto }
1919import util .{SimpleIdentitySet , EqHashMap , EqHashSet , SrcPos , Property }
2020import transform .{Recheck , PreRecheck , CapturedVars }
2121import Recheck .*
@@ -1309,17 +1309,64 @@ class CheckCaptures extends Recheck, SymTransformer:
13091309 override def checkConformsExpr (actual : Type , expected : Type , tree : Tree , addenda : Addenda )(using Context ): Type =
13101310 testAdapted(actual, expected, tree, addenda)(err.typeMismatch)
13111311
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+
13121322 inline def testAdapted (actual : Type , expected : Type , tree : Tree , addenda : Addenda )
13131323 (fail : (Tree , Type , Addenda ) => Unit )(using Context ): Type =
1324+
13141325 var expected1 = alignDependentFunction(expected, actual.stripCapturing)
13151326 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)
13171341 // println(i"check conforms $actualBoxed <<< $expected1")
13181342
13191343 if actualBoxed eq actual then
13201344 // Only `addOuterRefs` when there is no box adaptation
13211345 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
13231370 case TypeComparer .CompareResult .Fail (notes) =>
13241371 capt.println(i " conforms failed for ${tree}: $actual vs $expected" )
13251372 if falseDeps then expected1 = unalignFunction(expected1)
@@ -1477,7 +1524,8 @@ class CheckCaptures extends Recheck, SymTransformer:
14771524 (actualShape, CaptureSet ())
14781525 end adaptShape
14791526
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)
14811529
14821530 // Get wildcards out of the way
14831531 expected match
0 commit comments