@@ -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,61 @@ 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 widenNamed (tp : Type )(using Context ): Type = tp match
1314+ case stp : SingletonType => widenNamed(stp.widen)
1315+ case ntp : NamedType => ntp.info match
1316+ case info : TypeBounds => widenNamed(info.hi)
1317+ case _ => tp
1318+ case _ => tp
1319+
13121320 inline def testAdapted (actual : Type , expected : Type , tree : Tree , addenda : Addenda )
13131321 (fail : (Tree , Type , Addenda ) => Unit )(using Context ): Type =
1322+
13141323 var expected1 = alignDependentFunction(expected, actual.stripCapturing)
13151324 val falseDeps = expected1 ne expected
1316- val actualBoxed = adapt(actual, expected1, tree)
1325+ val actual1 =
1326+ if expected.stripCapturing.isInstanceOf [SelectionProto ] then
1327+ // If the expected type is a `SelectionProto`, we should be careful about cases when
1328+ // the actual type is a type parameter (for instance, `X <: box IO^`).
1329+ // If `X` were not widen to reveal the boxed type, both sides are unboxed and thus
1330+ // no box adaptation happens. But it is unsound: selecting a member from `X` implicitly
1331+ // unboxes the value.
1332+ //
1333+ // Therefore, when the expected type is a selection proto, we conservatively widen
1334+ // the actual type to strip type parameters.
1335+ widenNamed(actual)
1336+ else actual
1337+ val actualBoxed = adapt(actual1, expected1, tree)
13171338 // println(i"check conforms $actualBoxed <<< $expected1")
13181339
13191340 if actualBoxed eq actual then
13201341 // Only `addOuterRefs` when there is no box adaptation
13211342 expected1 = addOuterRefs(expected1, actual, tree.srcPos)
1322- TypeComparer .compareResult(isCompatible(actualBoxed, expected1)) match
1343+
1344+ def tryCurrentType : Boolean =
1345+ isCompatible(actualBoxed, expected1)
1346+
1347+ /** When the actual type is a named type, and the previous attempt failed, try to widen the named type
1348+ * and try another time.
1349+ *
1350+ * This is useful for cases like:
1351+ *
1352+ * def id[X <: box IO^{a}](x: X): IO^{a} = x
1353+ *
1354+ * When typechecking the body, we need to show that `(x: X)` can be typed at `IO^{a}`.
1355+ * In the first attempt, since `X` is simply a parameter reference, we treat it as non-boxed and perform
1356+ * no box adptation. But its upper bound is in fact boxed, and adaptation is needed for typechecking the body.
1357+ * In those cases, we widen such types and try box adaptation another time.
1358+ */
1359+ def tryWidenNamed : Boolean =
1360+ val actual1 = widenNamed(actual)
1361+ (actual1 ne actual) && {
1362+ val actualBoxed1 = adapt(actual1, expected1, tree)
1363+ isCompatible(actualBoxed1, expected1)
1364+ }
1365+
1366+ TypeComparer .compareResult(tryCurrentType || tryWidenNamed) match
13231367 case TypeComparer .CompareResult .Fail (notes) =>
13241368 capt.println(i " conforms failed for ${tree}: $actual vs $expected" )
13251369 if falseDeps then expected1 = unalignFunction(expected1)
@@ -1477,7 +1521,8 @@ class CheckCaptures extends Recheck, SymTransformer:
14771521 (actualShape, CaptureSet ())
14781522 end adaptShape
14791523
1480- def adaptStr = i " adapting $actual ${if covariant then " ~~>" else " <~~" } $expected"
1524+ // val adaptStr = i"adapting $actual ${if covariant then "~~>" else "<~~"} $expected"
1525+ // println(adaptStr)
14811526
14821527 // Get wildcards out of the way
14831528 expected match
0 commit comments