@@ -42,12 +42,20 @@ object CheckCaptures:
4242 end Pre
4343
4444 /** A class describing environments.
45- * @param owner the current owner
46- * @param captured the caputure set containing all references to tracked free variables outside of boxes
47- * @param isBoxed true if the environment is inside a box (in which case references are not counted)
48- * @param outer0 the next enclosing environment
45+ * @param owner the current owner
46+ * @param nestedInOwner true if the environment is a temporary one nested in the owner's environment,
47+ * and does not have a different actual owner symbol (this happens when doing box adaptation).
48+ * @param captured the caputure set containing all references to tracked free variables outside of boxes
49+ * @param isBoxed true if the environment is inside a box (in which case references are not counted)
50+ * @param outer0 the next enclosing environment
4951 */
50- case class Env (owner : Symbol , captured : CaptureSet , isBoxed : Boolean , outer0 : Env | Null ):
52+ case class Env (
53+ owner : Symbol ,
54+ nestedInOwner : Boolean ,
55+ captured : CaptureSet ,
56+ isBoxed : Boolean ,
57+ outer0 : Env | Null
58+ ):
5159 def outer = outer0.nn
5260
5361 def isOutermost = outer0 == null
@@ -204,7 +212,7 @@ class CheckCaptures extends Recheck, SymTransformer:
204212 report.error(i " $header included in allowed capture set ${res.blocking}" , pos)
205213
206214 /** The current environment */
207- private var curEnv : Env = Env (NoSymbol , CaptureSet .empty, isBoxed = false , null )
215+ private var curEnv : Env = Env (NoSymbol , nestedInOwner = false , CaptureSet .empty, isBoxed = false , null )
208216
209217 private val myCapturedVars : util.EqHashMap [Symbol , CaptureSet ] = EqHashMap ()
210218
@@ -249,8 +257,12 @@ class CheckCaptures extends Recheck, SymTransformer:
249257 if ! cs.isAlwaysEmpty then
250258 forallOuterEnvsUpTo(ctx.owner.topLevelClass) { env =>
251259 val included = cs.filter {
252- case ref : TermRef => env.owner.isProperlyContainedIn(ref.symbol.owner)
253- case ref : ThisType => env.owner.isProperlyContainedIn(ref.cls)
260+ case ref : TermRef =>
261+ (env.nestedInOwner || env.owner != ref.symbol.owner)
262+ && env.owner.isContainedIn(ref.symbol.owner)
263+ case ref : ThisType =>
264+ (env.nestedInOwner || env.owner != ref.cls)
265+ && env.owner.isContainedIn(ref.cls)
254266 case _ => false
255267 }
256268 capt.println(i " Include call capture $included in ${env.owner}" )
@@ -439,7 +451,7 @@ class CheckCaptures extends Recheck, SymTransformer:
439451 if ! Synthetics .isExcluded(sym) then
440452 val saved = curEnv
441453 val localSet = capturedVars(sym)
442- if ! localSet.isAlwaysEmpty then curEnv = Env (sym, localSet, isBoxed = false , curEnv)
454+ if ! localSet.isAlwaysEmpty then curEnv = Env (sym, nestedInOwner = false , localSet, isBoxed = false , curEnv)
443455 try super .recheckDefDef(tree, sym)
444456 finally
445457 interpolateVarsIn(tree.tpt)
@@ -455,7 +467,7 @@ class CheckCaptures extends Recheck, SymTransformer:
455467 val localSet = capturedVars(cls)
456468 for parent <- impl.parents do // (1)
457469 checkSubset(capturedVars(parent.tpe.classSymbol), localSet, parent.srcPos)
458- if ! localSet.isAlwaysEmpty then curEnv = Env (cls, localSet, isBoxed = false , curEnv)
470+ if ! localSet.isAlwaysEmpty then curEnv = Env (cls, nestedInOwner = false , localSet, isBoxed = false , curEnv)
459471 try
460472 val thisSet = cls.classInfo.selfType.captureSet.withDescription(i " of the self type of $cls" )
461473 checkSubset(localSet, thisSet, tree.srcPos) // (2)
@@ -502,7 +514,7 @@ class CheckCaptures extends Recheck, SymTransformer:
502514 override def recheck (tree : Tree , pt : Type = WildcardType )(using Context ): Type =
503515 if tree.isTerm && pt.isBoxedCapturing then
504516 val saved = curEnv
505- curEnv = Env (curEnv.owner, CaptureSet .Var (), isBoxed = true , curEnv)
517+ curEnv = Env (curEnv.owner, nestedInOwner = false , CaptureSet .Var (), isBoxed = true , curEnv)
506518 try super .recheck(tree, pt)
507519 finally curEnv = saved
508520 else
@@ -593,25 +605,121 @@ class CheckCaptures extends Recheck, SymTransformer:
593605
594606 /** Adapt function type `actual`, which is `aargs -> ares` (possibly with dependencies)
595607 * to `expected` type.
608+ * It returns the adapted type along with the additionally captured variable
609+ * during adaptation.
596610 * @param reconstruct how to rebuild the adapted function type
597611 */
598612 def adaptFun (actual : Type , aargs : List [Type ], ares : Type , expected : Type ,
599- covariant : Boolean ,
600- reconstruct : (List [Type ], Type ) => Type ): Type =
601- val (eargs, eres) = expected.dealias match
602- case defn.FunctionOf (eargs, eres, _, _) => (eargs, eres)
603- case _ => (aargs.map(_ => WildcardType ), WildcardType )
604- val aargs1 = aargs.zipWithConserve(eargs)(adapt(_, _, ! covariant))
605- val ares1 = adapt(ares, eres, covariant)
606- if (ares1 eq ares) && (aargs1 eq aargs) then actual
607- else reconstruct(aargs1, ares1)
608-
609- def adapt (actual : Type , expected : Type , covariant : Boolean ): Type = actual.dealias match
610- case actual @ CapturingType (parent, refs) =>
611- val parent1 = adapt(parent, expected, covariant)
612- if actual.isBoxed != expected.isBoxedCapturing then
613+ covariant : Boolean , boxed : Boolean ,
614+ reconstruct : (List [Type ], Type ) => Type ): (Type , CaptureSet ) =
615+ val saved = curEnv
616+ curEnv = Env (curEnv.owner, nestedInOwner = true , CaptureSet .Var (), isBoxed = false , if boxed then null else curEnv)
617+
618+ try
619+ val (eargs, eres) = expected.dealias.stripCapturing match
620+ case defn.FunctionOf (eargs, eres, _, _) => (eargs, eres)
621+ case expected : MethodType => (expected.paramInfos, expected.resType)
622+ case expected @ RefinedType (_, _, rinfo : MethodType ) if defn.isFunctionType(expected) => (rinfo.paramInfos, rinfo.resType)
623+ case _ => (aargs.map(_ => WildcardType ), WildcardType )
624+ val aargs1 = aargs.zipWithConserve(eargs) { (aarg, earg) => adapt(aarg, earg, ! covariant) }
625+ val ares1 = adapt(ares, eres, covariant)
626+
627+ val resTp =
628+ if (ares1 eq ares) && (aargs1 eq aargs) then actual
629+ else reconstruct(aargs1, ares1)
630+
631+ (resTp, curEnv.captured)
632+ finally
633+ curEnv = saved
634+
635+ /** Adapt type function type `actual` to the expected type.
636+ * @see [[adaptFun ]]
637+ */
638+ def adaptTypeFun (
639+ actual : Type , ares : Type , expected : Type ,
640+ covariant : Boolean , boxed : Boolean ,
641+ reconstruct : Type => Type ): (Type , CaptureSet ) =
642+ val saved = curEnv
643+ curEnv = Env (curEnv.owner, nestedInOwner = true , CaptureSet .Var (), isBoxed = false , if boxed then null else curEnv)
644+
645+ try
646+ val eres = expected.dealias.stripCapturing match
647+ case RefinedType (_, _, rinfo : PolyType ) => rinfo.resType
648+ case expected : PolyType => expected.resType
649+ case _ => WildcardType
650+
651+ val ares1 = adapt(ares, eres, covariant)
652+
653+ val resTp =
654+ if ares1 eq ares then actual
655+ else reconstruct(ares1)
656+
657+ (resTp, curEnv.captured)
658+ finally
659+ curEnv = saved
660+ end adaptTypeFun
661+
662+ def adaptInfo (actual : Type , expected : Type , covariant : Boolean ): String =
663+ val arrow = if covariant then " ~~>" else " <~~"
664+ i " adapting $actual $arrow $expected"
665+
666+ /** Destruct a capturing type `tp` to a tuple (cs, tp0, boxed),
667+ * where `tp0` is not a capturing type.
668+ *
669+ * If `tp` is a nested capturing type, the return tuple always represents
670+ * the innermost capturing type. The outer capture annotations can be
671+ * reconstructed with the returned function.
672+ */
673+ def destructCapturingType (tp : Type , reconstruct : Type => Type = x => x): ((Type , CaptureSet , Boolean ), Type => Type ) =
674+ tp.dealias match
675+ case tp @ CapturingType (parent, cs) =>
676+ if parent.dealias.isCapturingType then
677+ destructCapturingType(parent, res => reconstruct(tp.derivedCapturingType(res, cs)))
678+ else
679+ ((parent, cs, tp.isBoxed), reconstruct)
680+ case actual =>
681+ ((actual, CaptureSet (), false ), reconstruct)
682+
683+ def adapt (actual : Type , expected : Type , covariant : Boolean ): Type = trace(adaptInfo(actual, expected, covariant), recheckr, show = true ) {
684+ if expected.isInstanceOf [WildcardType ] then actual
685+ else
686+ val ((parent, cs, actualIsBoxed), recon) = destructCapturingType(actual)
687+
688+ val needsAdaptation = actualIsBoxed != expected.isBoxedCapturing
689+ val insertBox = needsAdaptation && covariant != actualIsBoxed
690+
691+ val (parent1, cs1) = parent match {
692+ case actual @ AppliedType (tycon, args) if defn.isNonRefinedFunction(actual) =>
693+ val (parent1, leaked) = adaptFun(parent, args.init, args.last, expected, covariant, insertBox,
694+ (aargs1, ares1) => actual.derivedAppliedType(tycon, aargs1 :+ ares1))
695+ (parent1, leaked ++ cs)
696+ case actual @ RefinedType (_, _, rinfo : MethodType ) if defn.isFunctionType(actual) =>
697+ // TODO Find a way to combine handling of generic and dependent function types (here and elsewhere)
698+ val (parent1, leaked) = adaptFun(parent, rinfo.paramInfos, rinfo.resType, expected, covariant, insertBox,
699+ (aargs1, ares1) =>
700+ rinfo.derivedLambdaType(paramInfos = aargs1, resType = ares1)
701+ .toFunctionType(isJava = false , alwaysDependent = true ))
702+ (parent1, leaked ++ cs)
703+ case actual : MethodType =>
704+ val (parent1, leaked) = adaptFun(parent, actual.paramInfos, actual.resType, expected, covariant, insertBox,
705+ (aargs1, ares1) =>
706+ actual.derivedLambdaType(paramInfos = aargs1, resType = ares1))
707+ (parent1, leaked ++ cs)
708+ case actual @ RefinedType (p, nme, rinfo : PolyType ) if defn.isFunctionOrPolyType(actual) =>
709+ val (parent1, leaked) = adaptTypeFun(parent, rinfo.resType, expected, covariant, insertBox,
710+ ares1 =>
711+ val rinfo1 = rinfo.derivedLambdaType(rinfo.paramNames, rinfo.paramInfos, ares1)
712+ val actual1 = actual.derivedRefinedType(p, nme, rinfo1)
713+ actual1
714+ )
715+ (parent1, leaked ++ cs)
716+ case _ =>
717+ (parent, cs)
718+ }
719+
720+ if needsAdaptation then
613721 val criticalSet = // the set which is not allowed to have `*`
614- if covariant then refs // can't box with `*`
722+ if covariant then cs1 // can't box with `*`
615723 else expected.captureSet // can't unbox with `*`
616724 if criticalSet.isUniversal then
617725 // We can't box/unbox the universal capability. Leave `actual` as it is
@@ -627,20 +735,13 @@ class CheckCaptures extends Recheck, SymTransformer:
627735 |since one of their capture sets contains the root capability `*` """ ,
628736 pos)
629737 }
630- if covariant == actual.isBoxed then markFree(refs, pos)
631- CapturingType (parent1, refs, boxed = ! actual.isBoxed)
738+ if ! insertBox then // unboxing
739+ markFree(criticalSet, pos)
740+ recon(CapturingType (parent1, cs1, ! actualIsBoxed))
632741 else
633- actual.derivedCapturingType(parent1, refs)
634- case actual @ AppliedType (tycon, args) if defn.isNonRefinedFunction(actual) =>
635- adaptFun(actual, args.init, args.last, expected, covariant,
636- (aargs1, ares1) => actual.derivedAppliedType(tycon, aargs1 :+ ares1))
637- case actual @ RefinedType (_, _, rinfo : MethodType ) if defn.isFunctionType(actual) =>
638- // TODO Find a way to combine handling of generic and dependent function types (here and elsewhere)
639- adaptFun(actual, rinfo.paramInfos, rinfo.resType, expected, covariant,
640- (aargs1, ares1) =>
641- rinfo.derivedLambdaType(paramInfos = aargs1, resType = ares1)
642- .toFunctionType(isJava = false , alwaysDependent = true ))
643- case _ => actual
742+ recon(CapturingType (parent1, cs1, actualIsBoxed))
743+ }
744+
644745
645746 var actualw = actual.widenDealias
646747 actual match
0 commit comments