@@ -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)
@@ -495,14 +507,20 @@ class CheckCaptures extends Recheck, SymTransformer:
495507 recheckFinish(result, arg, pt)
496508 */
497509
498- /** If expected type `pt` is boxed, don't propagate free variables.
510+ /** If expected type `pt` is boxed and the tree is a function or a reference,
511+ * don't propagate free variables.
499512 * Otherwise, if the result type is boxed, simulate an unboxing by
500513 * adding all references in the boxed capture set to the current environment.
501514 */
502515 override def recheck (tree : Tree , pt : Type = WildcardType )(using Context ): Type =
503516 if tree.isTerm && pt.isBoxedCapturing then
504517 val saved = curEnv
505- curEnv = Env (curEnv.owner, CaptureSet .Var (), isBoxed = true , curEnv)
518+
519+ tree match
520+ case _ : RefTree | closureDef(_) =>
521+ curEnv = Env (curEnv.owner, nestedInOwner = false , CaptureSet .Var (), isBoxed = true , curEnv)
522+ case _ =>
523+
506524 try super .recheck(tree, pt)
507525 finally curEnv = saved
508526 else
@@ -593,25 +611,121 @@ class CheckCaptures extends Recheck, SymTransformer:
593611
594612 /** Adapt function type `actual`, which is `aargs -> ares` (possibly with dependencies)
595613 * to `expected` type.
614+ * It returns the adapted type along with the additionally captured variable
615+ * during adaptation.
596616 * @param reconstruct how to rebuild the adapted function type
597617 */
598618 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
619+ covariant : Boolean , boxed : Boolean ,
620+ reconstruct : (List [Type ], Type ) => Type ): (Type , CaptureSet ) =
621+ val saved = curEnv
622+ curEnv = Env (curEnv.owner, nestedInOwner = true , CaptureSet .Var (), isBoxed = false , if boxed then null else curEnv)
623+
624+ try
625+ val (eargs, eres) = expected.dealias.stripCapturing match
626+ case defn.FunctionOf (eargs, eres, _, _) => (eargs, eres)
627+ case expected : MethodType => (expected.paramInfos, expected.resType)
628+ case expected @ RefinedType (_, _, rinfo : MethodType ) if defn.isFunctionType(expected) => (rinfo.paramInfos, rinfo.resType)
629+ case _ => (aargs.map(_ => WildcardType ), WildcardType )
630+ val aargs1 = aargs.zipWithConserve(eargs) { (aarg, earg) => adapt(aarg, earg, ! covariant) }
631+ val ares1 = adapt(ares, eres, covariant)
632+
633+ val resTp =
634+ if (ares1 eq ares) && (aargs1 eq aargs) then actual
635+ else reconstruct(aargs1, ares1)
636+
637+ (resTp, curEnv.captured)
638+ finally
639+ curEnv = saved
640+
641+ /** Adapt type function type `actual` to the expected type.
642+ * @see [[adaptFun ]]
643+ */
644+ def adaptTypeFun (
645+ actual : Type , ares : Type , expected : Type ,
646+ covariant : Boolean , boxed : Boolean ,
647+ reconstruct : Type => Type ): (Type , CaptureSet ) =
648+ val saved = curEnv
649+ curEnv = Env (curEnv.owner, nestedInOwner = true , CaptureSet .Var (), isBoxed = false , if boxed then null else curEnv)
650+
651+ try
652+ val eres = expected.dealias.stripCapturing match
653+ case RefinedType (_, _, rinfo : PolyType ) => rinfo.resType
654+ case expected : PolyType => expected.resType
655+ case _ => WildcardType
656+
657+ val ares1 = adapt(ares, eres, covariant)
658+
659+ val resTp =
660+ if ares1 eq ares then actual
661+ else reconstruct(ares1)
662+
663+ (resTp, curEnv.captured)
664+ finally
665+ curEnv = saved
666+ end adaptTypeFun
667+
668+ def adaptInfo (actual : Type , expected : Type , covariant : Boolean ): String =
669+ val arrow = if covariant then " ~~>" else " <~~"
670+ i " adapting $actual $arrow $expected"
671+
672+ /** Destruct a capturing type `tp` to a tuple (cs, tp0, boxed),
673+ * where `tp0` is not a capturing type.
674+ *
675+ * If `tp` is a nested capturing type, the return tuple always represents
676+ * the innermost capturing type. The outer capture annotations can be
677+ * reconstructed with the returned function.
678+ */
679+ def destructCapturingType (tp : Type , reconstruct : Type => Type = x => x): ((Type , CaptureSet , Boolean ), Type => Type ) =
680+ tp.dealias match
681+ case tp @ CapturingType (parent, cs) =>
682+ if parent.dealias.isCapturingType then
683+ destructCapturingType(parent, res => reconstruct(tp.derivedCapturingType(res, cs)))
684+ else
685+ ((parent, cs, tp.isBoxed), reconstruct)
686+ case actual =>
687+ ((actual, CaptureSet (), false ), reconstruct)
688+
689+ def adapt (actual : Type , expected : Type , covariant : Boolean ): Type = trace(adaptInfo(actual, expected, covariant), recheckr, show = true ) {
690+ if expected.isInstanceOf [WildcardType ] then actual
691+ else
692+ val ((parent, cs, actualIsBoxed), recon) = destructCapturingType(actual)
693+
694+ val needsAdaptation = actualIsBoxed != expected.isBoxedCapturing
695+ val insertBox = needsAdaptation && covariant != actualIsBoxed
696+
697+ val (parent1, cs1) = parent match {
698+ case actual @ AppliedType (tycon, args) if defn.isNonRefinedFunction(actual) =>
699+ val (parent1, leaked) = adaptFun(parent, args.init, args.last, expected, covariant, insertBox,
700+ (aargs1, ares1) => actual.derivedAppliedType(tycon, aargs1 :+ ares1))
701+ (parent1, leaked ++ cs)
702+ case actual @ RefinedType (_, _, rinfo : MethodType ) if defn.isFunctionType(actual) =>
703+ // TODO Find a way to combine handling of generic and dependent function types (here and elsewhere)
704+ val (parent1, leaked) = adaptFun(parent, rinfo.paramInfos, rinfo.resType, expected, covariant, insertBox,
705+ (aargs1, ares1) =>
706+ rinfo.derivedLambdaType(paramInfos = aargs1, resType = ares1)
707+ .toFunctionType(isJava = false , alwaysDependent = true ))
708+ (parent1, leaked ++ cs)
709+ case actual : MethodType =>
710+ val (parent1, leaked) = adaptFun(parent, actual.paramInfos, actual.resType, expected, covariant, insertBox,
711+ (aargs1, ares1) =>
712+ actual.derivedLambdaType(paramInfos = aargs1, resType = ares1))
713+ (parent1, leaked ++ cs)
714+ case actual @ RefinedType (p, nme, rinfo : PolyType ) if defn.isFunctionOrPolyType(actual) =>
715+ val (parent1, leaked) = adaptTypeFun(parent, rinfo.resType, expected, covariant, insertBox,
716+ ares1 =>
717+ val rinfo1 = rinfo.derivedLambdaType(rinfo.paramNames, rinfo.paramInfos, ares1)
718+ val actual1 = actual.derivedRefinedType(p, nme, rinfo1)
719+ actual1
720+ )
721+ (parent1, leaked ++ cs)
722+ case _ =>
723+ (parent, cs)
724+ }
725+
726+ if needsAdaptation then
613727 val criticalSet = // the set which is not allowed to have `*`
614- if covariant then refs // can't box with `*`
728+ if covariant then cs1 // can't box with `*`
615729 else expected.captureSet // can't unbox with `*`
616730 if criticalSet.isUniversal then
617731 // We can't box/unbox the universal capability. Leave `actual` as it is
@@ -627,20 +741,13 @@ class CheckCaptures extends Recheck, SymTransformer:
627741 |since one of their capture sets contains the root capability `*` """ ,
628742 pos)
629743 }
630- if covariant == actual.isBoxed then markFree(refs, pos)
631- CapturingType (parent1, refs, boxed = ! actual.isBoxed)
744+ if ! insertBox then // unboxing
745+ markFree(criticalSet, pos)
746+ recon(CapturingType (parent1, cs1, ! actualIsBoxed))
632747 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
748+ recon(CapturingType (parent1, cs1, actualIsBoxed))
749+ }
750+
644751
645752 var actualw = actual.widenDealias
646753 actual match
0 commit comments