@@ -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)
@@ -502,7 +514,7 @@ class CheckCaptures extends Recheck, SymTransformer:
502
514
override def recheck (tree : Tree , pt : Type = WildcardType )(using Context ): Type =
503
515
if tree.isTerm && pt.isBoxedCapturing then
504
516
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)
506
518
try super .recheck(tree, pt)
507
519
finally curEnv = saved
508
520
else
@@ -593,25 +605,121 @@ class CheckCaptures extends Recheck, SymTransformer:
593
605
594
606
/** Adapt function type `actual`, which is `aargs -> ares` (possibly with dependencies)
595
607
* to `expected` type.
608
+ * It returns the adapted type along with the additionally captured variable
609
+ * during adaptation.
596
610
* @param reconstruct how to rebuild the adapted function type
597
611
*/
598
612
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
613
721
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 `*`
615
723
else expected.captureSet // can't unbox with `*`
616
724
if criticalSet.isUniversal then
617
725
// We can't box/unbox the universal capability. Leave `actual` as it is
@@ -627,20 +735,13 @@ class CheckCaptures extends Recheck, SymTransformer:
627
735
|since one of their capture sets contains the root capability `*` """ ,
628
736
pos)
629
737
}
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))
632
741
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
+
644
745
645
746
var actualw = actual.widenDealias
646
747
actual match
0 commit comments