@@ -42,12 +42,20 @@ object CheckCaptures:
42
42
end Pre
43
43
44
44
/** 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
49
51
*/
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
+ ):
51
59
def outer = outer0.nn
52
60
53
61
def isOutermost = outer0 == null
@@ -204,7 +212,7 @@ class CheckCaptures extends Recheck, SymTransformer:
204
212
report.error(i " $header included in allowed capture set ${res.blocking}" , pos)
205
213
206
214
/** 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 )
208
216
209
217
private val myCapturedVars : util.EqHashMap [Symbol , CaptureSet ] = EqHashMap ()
210
218
@@ -249,8 +257,12 @@ class CheckCaptures extends Recheck, SymTransformer:
249
257
if ! cs.isAlwaysEmpty then
250
258
forallOuterEnvsUpTo(ctx.owner.topLevelClass) { env =>
251
259
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)
254
266
case _ => false
255
267
}
256
268
capt.println(i " Include call capture $included in ${env.owner}" )
@@ -439,7 +451,7 @@ class CheckCaptures extends Recheck, SymTransformer:
439
451
if ! Synthetics .isExcluded(sym) then
440
452
val saved = curEnv
441
453
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)
443
455
try super .recheckDefDef(tree, sym)
444
456
finally
445
457
interpolateVarsIn(tree.tpt)
@@ -455,7 +467,7 @@ class CheckCaptures extends Recheck, SymTransformer:
455
467
val localSet = capturedVars(cls)
456
468
for parent <- impl.parents do // (1)
457
469
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)
459
471
try
460
472
val thisSet = cls.classInfo.selfType.captureSet.withDescription(i " of the self type of $cls" )
461
473
checkSubset(localSet, thisSet, tree.srcPos) // (2)
@@ -495,14 +507,20 @@ class CheckCaptures extends Recheck, SymTransformer:
495
507
recheckFinish(result, arg, pt)
496
508
*/
497
509
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.
499
512
* Otherwise, if the result type is boxed, simulate an unboxing by
500
513
* adding all references in the boxed capture set to the current environment.
501
514
*/
502
515
override def recheck (tree : Tree , pt : Type = WildcardType )(using Context ): Type =
503
516
if tree.isTerm && pt.isBoxedCapturing then
504
517
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
+
506
524
try super .recheck(tree, pt)
507
525
finally curEnv = saved
508
526
else
@@ -593,25 +611,121 @@ class CheckCaptures extends Recheck, SymTransformer:
593
611
594
612
/** Adapt function type `actual`, which is `aargs -> ares` (possibly with dependencies)
595
613
* to `expected` type.
614
+ * It returns the adapted type along with the additionally captured variable
615
+ * during adaptation.
596
616
* @param reconstruct how to rebuild the adapted function type
597
617
*/
598
618
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
613
727
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 `*`
615
729
else expected.captureSet // can't unbox with `*`
616
730
if criticalSet.isUniversal then
617
731
// We can't box/unbox the universal capability. Leave `actual` as it is
@@ -627,20 +741,13 @@ class CheckCaptures extends Recheck, SymTransformer:
627
741
|since one of their capture sets contains the root capability `*` """ ,
628
742
pos)
629
743
}
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))
632
747
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
+
644
751
645
752
var actualw = actual.widenDealias
646
753
actual match
0 commit comments