Skip to content

Commit a178c04

Browse files
committed
Allow Fresh instances in disallowRootCapability
1 parent 806e2b5 commit a178c04

18 files changed

+197
-143
lines changed

compiler/src/dotty/tools/dotc/cc/CaptureSet.scala

+39-25
Original file line numberDiff line numberDiff line change
@@ -352,16 +352,27 @@ sealed abstract class CaptureSet extends Showable:
352352

353353
def readOnly(using Context): CaptureSet = map(ReadOnlyMap())
354354

355-
/** Invoke handler if this set has (or later aquires) the root capability `cap` */
356-
def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type =
357-
val hasRoot =
358-
if ccConfig.newScheme then
359-
elems.exists: elem =>
360-
val elem1 = elem.stripReadOnly
361-
elem1.isCap || elem1.isResultRoot
362-
else
363-
containsRootCapability
364-
if hasRoot then handler()
355+
/** A bad root `elem` is inadmissible as a member of this set. What is a bad roots depends
356+
* on the value of `rootLimit`.
357+
* If the limit is null, all capture roots are good.
358+
* If the limit is NoSymbol, all Fresh roots are good, but cap and Result roots are bad.
359+
* If the limit is some other symbol, cap and Result roots are bad, as well as
360+
* all Fresh roots that are contained (via ccOwner) in `rootLimit`.
361+
*/
362+
protected def isBadRoot(rootLimit: Symbol | Null, elem: CaptureRef)(using Context): Boolean =
363+
if rootLimit == null then false
364+
else
365+
val elem1 = elem.stripReadOnly
366+
elem1.isCap
367+
|| elem1.isResultRoot
368+
|| elem1.isFresh && elem1.ccOwner.isContainedIn(rootLimit)
369+
370+
/** Invoke `handler` if this set has (or later aquires) a root capability.
371+
* Excluded are Fresh instances unless their ccOwner is contained in `upto`.
372+
* If `upto` is NoSymbol, all Fresh instances are admitted.
373+
*/
374+
def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
375+
if elems.exists(isBadRoot(upto, _)) then handler()
365376
this
366377

367378
/** Invoke handler on the elements to ensure wellformedness of the capture set.
@@ -537,7 +548,10 @@ object CaptureSet:
537548
/** A handler to be invoked if the root reference `cap` is added to this set */
538549
var rootAddedHandler: () => Context ?=> Unit = () => ()
539550

540-
private[CaptureSet] var universalOK = true
551+
/** The limit deciding which capture roots are bad (i.e. cannot be contained in this set).
552+
* @see isBadRoot for details.
553+
*/
554+
private[CaptureSet] var rootLimit: Symbol | Null = null
541555

542556
/** A handler to be invoked when new elems are added to this set */
543557
var newElemAddedHandler: CaptureRef => Context ?=> Unit = _ => ()
@@ -588,7 +602,7 @@ object CaptureSet:
588602
assert(elem.isTrackableRef, elem)
589603
assert(!this.isInstanceOf[HiddenSet] || summon[VarState].isSeparating, summon[VarState])
590604
elems += elem
591-
if elem.isRootCapability then
605+
if isBadRoot(rootLimit, elem) then
592606
rootAddedHandler()
593607
newElemAddedHandler(elem)
594608
val normElem = if isMaybeSet then elem else elem.stripMaybe
@@ -610,16 +624,16 @@ object CaptureSet:
610624

611625
private def levelOK(elem: CaptureRef)(using Context): Boolean = elem match
612626
case elem @ root.Fresh(_) =>
613-
if ccConfig.newScheme then
614-
if !level.isDefined || ccState.symLevel(elem.ccOwner) <= level then true
615-
else
616-
println(i"LEVEL ERROR $elem cannot be included in $this of $owner")
617-
false
618-
else universalOK
627+
!level.isDefined
628+
|| ccState.symLevel(elem.ccOwner) <= level
629+
|| {
630+
capt.println(i"LEVEL ERROR $elem cannot be included in $this of $owner")
631+
false
632+
}
619633
case elem @ root.Result(mt) =>
620-
universalOK && (this.isInstanceOf[BiMapped] || isPartOf(mt.resType))
634+
rootLimit == null && (this.isInstanceOf[BiMapped] || isPartOf(mt.resType))
621635
case elem: TermRef if elem.isCap =>
622-
universalOK
636+
rootLimit == null
623637
case elem: TermRef if level.isDefined =>
624638
elem.prefix match
625639
case prefix: CaptureRef =>
@@ -650,10 +664,10 @@ object CaptureSet:
650664
else
651665
CompareResult.Fail(this :: Nil)
652666

653-
override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context): this.type =
654-
universalOK = false
667+
override def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
668+
rootLimit = upto
655669
rootAddedHandler = handler
656-
super.disallowRootCapability(handler)
670+
super.disallowRootCapability(upto)(handler)
657671

658672
override def ensureWellformed(handler: CaptureRef => (Context) ?=> Unit)(using Context): this.type =
659673
newElemAddedHandler = handler
@@ -756,7 +770,7 @@ object CaptureSet:
756770
* Test case: Without that tweak, logger.scala would not compile.
757771
*/
758772
class RefiningVar(owner: Symbol)(using Context) extends Var(owner):
759-
override def disallowRootCapability(handler: () => Context ?=> Unit)(using Context) = this
773+
override def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context) = this
760774

761775
/** A variable that is derived from some other variable via a map or filter. */
762776
abstract class DerivedVar(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context)
@@ -814,7 +828,7 @@ object CaptureSet:
814828
.showing(i"propagating new elem $elem backward from $this to $source = $result", captDebug)
815829
.andAlso(addNewElem(elem))
816830
catch case ex: AssertionError =>
817-
println(i"fail while tryInclude $elem of ${elem.getClass} in $this / ${this.summarize}")
831+
println(i"fail while prop backwards tryInclude $elem of ${elem.getClass} in $this / ${this.summarize}")
818832
throw ex
819833

820834
/** For a BiTypeMap, supertypes of the mapped type also constrain

compiler/src/dotty/tools/dotc/cc/CheckCaptures.scala

+16-12
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ object CheckCaptures:
123123
* root capability in its capture set or if it refers to a type parameter that
124124
* could possibly be instantiated with cap in a way that's visible at the type.
125125
*/
126-
private def disallowRootCapabilitiesIn(tp: Type, carrier: Symbol, what: String, have: String, addendum: String, pos: SrcPos)(using Context) =
126+
private def disallowRootCapabilitiesIn(tp: Type, upto: Symbol, what: String, have: String, addendum: String, pos: SrcPos)(using Context) =
127127
val check = new TypeTraverser:
128128

129129
private val seen = new EqHashSet[TypeRef]
@@ -150,7 +150,7 @@ object CheckCaptures:
150150
case CapturingType(parent, refs) =>
151151
if variance >= 0 then
152152
val openScopes = openExistentialScopes
153-
refs.disallowRootCapability: () =>
153+
refs.disallowRootCapability(upto): () =>
154154
def part =
155155
if t eq tp then ""
156156
else
@@ -488,7 +488,7 @@ class CheckCaptures extends Recheck, SymTransformer:
488488
case _ =>
489489
CaptureSet.ofType(c.widen, followResult = false)
490490
capt.println(i"Widen reach $c to $underlying in ${env.owner}")
491-
underlying.disallowRootCapability: () =>
491+
underlying.disallowRootCapability(NoSymbol): () =>
492492
report.error(em"Local capability $c in ${env.ownerString} cannot have `cap` as underlying capture set", tree.srcPos)
493493
recur(underlying, env, lastEnv)
494494

@@ -517,7 +517,7 @@ class CheckCaptures extends Recheck, SymTransformer:
517517
// fresh capabilities. We don't need to also follow the hidden set since separation
518518
// checking makes ure that locally hidden references need to go to @consume parameters.
519519
else
520-
underlying.disallowRootCapability: () =>
520+
underlying.disallowRootCapability(ctx.owner): () =>
521521
report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", tree.srcPos)
522522
recur(underlying, env, null)
523523
case c: TypeRef if c.isParamPath =>
@@ -590,7 +590,7 @@ class CheckCaptures extends Recheck, SymTransformer:
590590
def where = if sym.exists then i" in an argument of $sym" else ""
591591
val (addendum, errTree) =
592592
if arg.isInferred
593-
then ("\nThis is often caused by a local capability$where\nleaking as part of its result.", fn)
593+
then (i"\nThis is often caused by a local capability$where\nleaking as part of its result.", fn)
594594
else if arg.span.exists then ("", arg)
595595
else ("", fn)
596596
disallowRootCapabilitiesIn(arg.nuType, NoSymbol,
@@ -988,7 +988,7 @@ class CheckCaptures extends Recheck, SymTransformer:
988988
if sym.is(Module) then sym.info // Modules are checked by checking the module class
989989
else
990990
if sym.is(Mutable) && !sym.hasAnnotation(defn.UncheckedCapturesAnnot) then
991-
val (carrier, addendum) = capturedBy.get(sym) match
991+
val addendum = capturedBy.get(sym) match
992992
case Some(encl) =>
993993
val enclStr =
994994
if encl.isAnonymousFunction then
@@ -997,11 +997,11 @@ class CheckCaptures extends Recheck, SymTransformer:
997997
case _ => ""
998998
s"an anonymous function$location"
999999
else encl.show
1000-
(NoSymbol, i"\n\nNote that $sym does not count as local since it is captured by $enclStr")
1000+
i"\n\nNote that $sym does not count as local since it is captured by $enclStr"
10011001
case _ =>
1002-
(sym, "")
1002+
""
10031003
disallowRootCapabilitiesIn(
1004-
tree.tpt.nuType, carrier, i"Mutable $sym", "have type", addendum, sym.srcPos)
1004+
tree.tpt.nuType, NoSymbol, i"Mutable $sym", "have type", addendum, sym.srcPos)
10051005
checkInferredResult(super.recheckValDef(tree, sym), tree)
10061006
finally
10071007
if !sym.is(Param) then
@@ -1200,7 +1200,12 @@ class CheckCaptures extends Recheck, SymTransformer:
12001200
* result type of a try
12011201
*/
12021202
override def recheckTry(tree: Try, pt: Type)(using Context): Type =
1203-
val tp = super.recheckTry(tree, pt)
1203+
val tryOwner = Setup.firstCanThrowEvidence(tree.expr) match
1204+
case Some(vd) => vd.symbol.owner
1205+
case None => ctx.owner
1206+
val bodyType = inContext(ctx.withOwner(tryOwner)):
1207+
recheck(tree.expr, pt)
1208+
val tp = recheckTryRest(bodyType, tree.cases, tree.finalizer, pt)
12041209
if Feature.enabled(Feature.saferExceptions) then
12051210
disallowRootCapabilitiesIn(tp, ctx.owner,
12061211
"The result of `try`", "have type",
@@ -1272,8 +1277,7 @@ class CheckCaptures extends Recheck, SymTransformer:
12721277
val msg = note match
12731278
case CompareResult.LevelError(cs, ref) =>
12741279
if ref.stripReadOnly.isCapOrFresh then
1275-
def capStr = if ref.isReadOnly then "cap.rd" else "cap"
1276-
i"""the universal capability `$capStr`
1280+
i"""the universa capability $ref
12771281
|cannot be included in capture set $cs"""
12781282
else
12791283
val levelStr = ref match

compiler/src/dotty/tools/dotc/cc/Setup.scala

+57-32
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import CCState.*
2121
import dotty.tools.dotc.util.NoSourcePosition
2222
import CheckCaptures.CheckerAPI
2323
import NamerOps.methodType
24+
import NameKinds.{CanThrowEvidenceName, TryOwnerName}
2425

2526
/** Operations accessed from CheckCaptures */
2627
trait SetupAPI:
@@ -51,6 +52,15 @@ object Setup:
5152
Some((res, exc))
5253
case _ =>
5354
None
55+
56+
def firstCanThrowEvidence(body: Tree)(using Context): Option[Tree] = body match
57+
case Block(stats, expr) =>
58+
if stats.isEmpty then firstCanThrowEvidence(expr)
59+
else stats.find:
60+
case vd: ValDef => vd.symbol.name.is(CanThrowEvidenceName)
61+
case _ => false
62+
case _ => None
63+
5464
end Setup
5565
import Setup.*
5666

@@ -480,9 +490,9 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
480490
end transformExplicitType
481491

482492
/** Update info of `sym` for CheckCaptures phase only */
483-
private def updateInfo(sym: Symbol, info: Type)(using Context) =
493+
private def updateInfo(sym: Symbol, info: Type, owner: Symbol)(using Context) =
484494
toBeUpdated += sym
485-
sym.updateInfo(thisPhase, info, newFlagsFor(sym))
495+
sym.updateInfo(thisPhase, info, newFlagsFor(sym), owner)
486496
toBeUpdated -= sym
487497

488498
/** The info of `sym` at the CheckCaptures phase */
@@ -590,6 +600,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
590600
traverse(elems)
591601
tpt.setNuType(box(transformInferredType(tpt.tpe)))
592602

603+
case tree @ Try(body, catches, finalizer) =>
604+
val tryOwner = firstCanThrowEvidence(body) match
605+
case Some(vd) =>
606+
newSymbol(ctx.owner, TryOwnerName.fresh(),
607+
Method | Synthetic, ExprType(defn.NothingType), coord = tree.span)
608+
case _ =>
609+
ctx.owner
610+
inContext(ctx.withOwner(tryOwner)):
611+
traverse(body)
612+
catches.foreach(traverse)
613+
traverse(finalizer)
593614
case _ =>
594615
traverseChildren(tree)
595616
postProcess(tree)
@@ -634,6 +655,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
634655
// have a new type installed here (meaning hasRememberedType is true)
635656
def signatureChanges =
636657
tree.tpt.hasNuType || paramSignatureChanges
658+
def ownerChanges =
659+
ctx.owner.name.is(TryOwnerName)
637660

638661
def paramsToCap(mt: Type)(using Context): Type = mt match
639662
case mt: MethodType =>
@@ -644,38 +667,40 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
644667
mt.derivedLambdaType(resType = paramsToCap(mt.resType))
645668
case _ => mt
646669

647-
// If there's a change in the signature, update the info of `sym`
648-
if sym.exists && signatureChanges then
670+
// If there's a change in the signature or owner, update the info of `sym`
671+
if sym.exists && (signatureChanges || ownerChanges) then
649672
val updatedInfo =
650-
651-
val paramSymss = sym.paramSymss
652-
def newInfo(using Context) = // will be run in this or next phase
653-
root.toResultInResults(sym, report.error(_, tree.srcPos)):
654-
if sym.is(Method) then
655-
paramsToCap(methodType(paramSymss, localReturnType))
656-
else tree.tpt.nuType
657-
if tree.tpt.isInstanceOf[InferredTypeTree]
658-
&& !sym.is(Param) && !sym.is(ParamAccessor)
659-
then
660-
val prevInfo = sym.info
661-
new LazyType:
662-
def complete(denot: SymDenotation)(using Context) =
663-
assert(ctx.phase == thisPhase.next, i"$sym")
664-
sym.info = prevInfo // set info provisionally so we can analyze the symbol in recheck
665-
completeDef(tree, sym, this)
666-
sym.info = newInfo
667-
.showing(i"new info of $sym = $result", capt)
668-
else if sym.is(Method) then
669-
new LazyType:
670-
def complete(denot: SymDenotation)(using Context) =
671-
sym.info = newInfo
672-
.showing(i"new info of $sym = $result", capt)
673-
else newInfo
674-
updateInfo(sym, updatedInfo)
673+
if signatureChanges then
674+
val paramSymss = sym.paramSymss
675+
def newInfo(using Context) = // will be run in this or next phase
676+
root.toResultInResults(sym, report.error(_, tree.srcPos)):
677+
if sym.is(Method) then
678+
paramsToCap(methodType(paramSymss, localReturnType))
679+
else tree.tpt.nuType
680+
if tree.tpt.isInstanceOf[InferredTypeTree]
681+
&& !sym.is(Param) && !sym.is(ParamAccessor)
682+
then
683+
val prevInfo = sym.info
684+
new LazyType:
685+
def complete(denot: SymDenotation)(using Context) =
686+
assert(ctx.phase == thisPhase.next, i"$sym")
687+
sym.info = prevInfo // set info provisionally so we can analyze the symbol in recheck
688+
completeDef(tree, sym, this)
689+
sym.info = newInfo
690+
.showing(i"new info of $sym = $result", capt)
691+
else if sym.is(Method) then
692+
new LazyType:
693+
def complete(denot: SymDenotation)(using Context) =
694+
sym.info = newInfo
695+
.showing(i"new info of $sym = $result", capt)
696+
else newInfo
697+
else sym.info
698+
val updatedOwner = if ownerChanges then ctx.owner else sym.owner
699+
updateInfo(sym, updatedInfo, updatedOwner)
675700

676701
case tree: Bind =>
677702
val sym = tree.symbol
678-
updateInfo(sym, transformInferredType(sym.info))
703+
updateInfo(sym, transformInferredType(sym.info), sym.owner)
679704
case tree: TypeDef =>
680705
tree.symbol match
681706
case cls: ClassSymbol =>
@@ -708,7 +733,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
708733
// Install new types and if it is a module class also update module object
709734
if (selfInfo1 ne selfInfo) || (ps1 ne ps) then
710735
val newInfo = ClassInfo(prefix, cls, ps1, decls, selfInfo1)
711-
updateInfo(cls, newInfo)
736+
updateInfo(cls, newInfo, cls.owner)
712737
capt.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo")
713738
cls.thisType.asInstanceOf[ThisType].invalidateCaches()
714739
if cls.is(ModuleClass) then
@@ -721,7 +746,7 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
721746
// This would potentially give stackoverflows when setup is run repeatedly.
722747
// One test case is pos-custom-args/captures/checkbounds.scala under
723748
// ccConfig.alwaysRepeatRun = true.
724-
updateInfo(modul, CapturingType(modul.info, selfCaptures))
749+
updateInfo(modul, CapturingType(modul.info, selfCaptures), modul.owner)
725750
modul.termRef.invalidateCaches()
726751
case _ =>
727752
case _ =>

compiler/src/dotty/tools/dotc/cc/ccConfig.scala

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,6 @@ object ccConfig:
5656

5757
/** Not used currently. Handy for trying out new features */
5858
def newScheme(using Context): Boolean =
59-
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.8`)
59+
Feature.sourceVersion.stable.isAtLeast(SourceVersion.`3.7`)
6060

6161
end ccConfig

compiler/src/dotty/tools/dotc/cc/root.scala

+10-2
Original file line numberDiff line numberDiff line change
@@ -260,9 +260,17 @@ object root:
260260

261261
end CapToFresh
262262

263-
/** Maps cap to fresh */
263+
/** Maps cap to fresh. CapToFresh is a BiTypeMap since we don't want to
264+
* freeze a set when it is mapped. On the other hand, we do not want Fresh
265+
* values to flow back to cap since that would fail disallowRootCapability
266+
* tests elsewhere. We therefore use `withoutMappedFutureElems` to prevent
267+
* the map being installed for future use.
268+
*/
264269
def capToFresh(tp: Type, origin: Origin)(using Context): Type =
265-
if ccConfig.useSepChecks then CapToFresh(origin)(tp) else tp
270+
if ccConfig.useSepChecks then
271+
ccState.withoutMappedFutureElems:
272+
CapToFresh(origin)(tp)
273+
else tp
266274

267275
/** Maps fresh to cap */
268276
def freshToCap(tp: Type)(using Context): Type =

compiler/src/dotty/tools/dotc/core/NameKinds.scala

+1
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,7 @@ object NameKinds {
312312

313313
/** Other unique names */
314314
val CanThrowEvidenceName: UniqueNameKind = new UniqueNameKind("canThrow$")
315+
val TryOwnerName: UniqueNameKind = new UniqueNameKind("try$")
315316
val TempResultName: UniqueNameKind = new UniqueNameKind("ev$")
316317
val DepParamName: UniqueNameKind = new UniqueNameKind("(param)")
317318
val LazyImplicitName: UniqueNameKind = new UniqueNameKind("$_lazy_implicit_$")

0 commit comments

Comments
 (0)