Skip to content

Simplify box adaptation #16845

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Feb 7, 2023
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 27 additions & 36 deletions compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala
Original file line number Diff line number Diff line change
Expand Up @@ -656,7 +656,7 @@ class CheckCaptures extends Recheck, SymTransformer:
expected

/** Adapt `actual` type to `expected` type by inserting boxing and unboxing conversions
*
*
* @param alwaysConst always make capture set variables constant after adaptation
*/
def adaptBoxed(actual: Type, expected: Type, pos: SrcPos, alwaysConst: Boolean = false)(using Context): Type =
Expand Down Expand Up @@ -721,61 +721,52 @@ class CheckCaptures extends Recheck, SymTransformer:
val arrow = if covariant then "~~>" else "<~~"
i"adapting $actual $arrow $expected"

/** Destruct a capturing type `tp` to a tuple (cs, tp0, boxed),
* where `tp0` is not a capturing type.
*
* If `tp` is a nested capturing type, the return tuple always represents
* the innermost capturing type. The outer capture annotations can be
* reconstructed with the returned function.
*/
def destructCapturingType(tp: Type, reconstruct: Type => Type = x => x): ((Type, CaptureSet, Boolean), Type => Type) =
tp.dealias match
case tp @ CapturingType(parent, cs) =>
if parent.dealias.isCapturingType then
destructCapturingType(parent, res => reconstruct(tp.derivedCapturingType(res, cs)))
else
((parent, cs, tp.isBoxed), reconstruct)
case actual =>
val res = if tp.isFromJavaObject then tp else actual
((res, CaptureSet(), false), reconstruct)

def adapt(actual: Type, expected: Type, covariant: Boolean): Type = trace(adaptInfo(actual, expected, covariant), recheckr, show = true) {
if expected.isInstanceOf[WildcardType] then actual
else
val ((parent, cs, actualIsBoxed), recon) = destructCapturingType(actual)

val needsAdaptation = actualIsBoxed != expected.isBoxedCapturing
val insertBox = needsAdaptation && covariant != actualIsBoxed

val (parent1, cs1) = parent match {
// Decompose the actual type into the inner shape type, the capture set and the box status
val styp = if actual.isFromJavaObject then actual else actual.stripCapturing
val cs = actual.captureSet
val boxed = actual.isBoxedCapturing

// A box/unbox should be inserted, if the actual box status mismatches with the expectation
val needsAdaptation = boxed != expected.isBoxedCapturing
// Whether to insert a box or an unbox?
val insertBox = needsAdaptation && covariant != boxed

// Adapt the inner shape type: get the adapted shape type, and the capture set leaked during adaptation
val (styp1, leaked) = styp match {
case actual @ AppliedType(tycon, args) if defn.isNonRefinedFunction(actual) =>
val (parent1, leaked) = adaptFun(parent, args.init, args.last, expected, covariant, insertBox,
adaptFun(actual, args.init, args.last, expected, covariant, insertBox,
(aargs1, ares1) => actual.derivedAppliedType(tycon, aargs1 :+ ares1))
(parent1, leaked ++ cs)
case actual @ RefinedType(_, _, rinfo: MethodType) if defn.isFunctionType(actual) =>
// TODO Find a way to combine handling of generic and dependent function types (here and elsewhere)
val (parent1, leaked) = adaptFun(parent, rinfo.paramInfos, rinfo.resType, expected, covariant, insertBox,
adaptFun(actual, rinfo.paramInfos, rinfo.resType, expected, covariant, insertBox,
(aargs1, ares1) =>
rinfo.derivedLambdaType(paramInfos = aargs1, resType = ares1)
.toFunctionType(isJava = false, alwaysDependent = true))
(parent1, leaked ++ cs)
case actual: MethodType =>
val (parent1, leaked) = adaptFun(parent, actual.paramInfos, actual.resType, expected, covariant, insertBox,
adaptFun(actual, actual.paramInfos, actual.resType, expected, covariant, insertBox,
(aargs1, ares1) =>
actual.derivedLambdaType(paramInfos = aargs1, resType = ares1))
(parent1, leaked ++ cs)
case actual @ RefinedType(p, nme, rinfo: PolyType) if defn.isFunctionOrPolyType(actual) =>
val (parent1, leaked) = adaptTypeFun(parent, rinfo.resType, expected, covariant, insertBox,
adaptTypeFun(actual, rinfo.resType, expected, covariant, insertBox,
ares1 =>
val rinfo1 = rinfo.derivedLambdaType(rinfo.paramNames, rinfo.paramInfos, ares1)
val actual1 = actual.derivedRefinedType(p, nme, rinfo1)
actual1
)
(parent1, leaked ++ cs)
case _ =>
(parent, cs)
(styp, CaptureSet())
}

// Capture set of the term after adaptation
val cs1 = cs ++ leaked

// Compute the adapted type
def adaptedType(resultBoxed: Boolean) =
styp1.capturing(if alwaysConst then CaptureSet(cs1.elems) else cs1).forceBoxStatus(resultBoxed)

if needsAdaptation then
val criticalSet = // the set which is not allowed to have `*`
if covariant then cs1 // can't box with `*`
Expand All @@ -797,9 +788,9 @@ class CheckCaptures extends Recheck, SymTransformer:
}
if !insertBox then // unboxing
markFree(criticalSet, pos)
recon(CapturingType(parent1, if alwaysConst then CaptureSet(cs1.elems) else cs1, !actualIsBoxed))
adaptedType(!boxed)
else
recon(CapturingType(parent1, if alwaysConst then CaptureSet(cs1.elems) else cs1, actualIsBoxed))
adaptedType(boxed)
}

var actualw = actual.widenDealias
Expand Down