Skip to content

Commit 481e173

Browse files
authored
Changes around reaches and uses (#23584)
2 parents 53c7472 + 5bb26d8 commit 481e173

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

68 files changed

+437
-313
lines changed

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

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -843,6 +843,7 @@ object Capabilities:
843843
case LambdaActual(restp: Type)
844844
case OverriddenType(member: Symbol)
845845
case DeepCS(ref: TypeRef)
846+
case Parameter(param: Symbol)
846847
case Unknown
847848

848849
def explanation(using Context): String = this match
@@ -876,6 +877,8 @@ object Capabilities:
876877
i" when instantiating upper bound of member overridden by $member"
877878
case DeepCS(ref: TypeRef) =>
878879
i" when computing deep capture set of $ref"
880+
case Parameter(param) =>
881+
i" of parameter $param of ${param.owner}"
879882
case Unknown =>
880883
""
881884
end Origin
@@ -942,8 +945,8 @@ object Capabilities:
942945
CapToFresh(origin)(tp)
943946

944947
/** Maps fresh to cap */
945-
def freshToCap(tp: Type)(using Context): Type =
946-
CapToFresh(Origin.Unknown).inverse(tp)
948+
def freshToCap(param: Symbol, tp: Type)(using Context): Type =
949+
CapToFresh(Origin.Parameter(param)).inverse(tp)
947950

948951
/** Map top-level free existential variables one-to-one to Fresh instances */
949952
def resultToFresh(tp: Type, origin: Origin)(using Context): Type =

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

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,17 @@ extension (tp: Type)
9999
case AnnotatedType(tp1, ann) if tp1.derivesFrom(defn.Caps_CapSet) && ann.symbol.isRetains =>
100100
ann.tree.retainedSet.retainedElementsRaw
101101
case tp =>
102-
// Nothing is a special type to represent the empty set
103-
if tp.isNothingType then Nil
104-
else tp :: Nil // should be checked by wellformedness
102+
tp.dealiasKeepAnnots match
103+
case tp: TypeRef if tp.symbol == defn.Caps_CapSet =>
104+
// This can happen in cases where we try to type an eta expansion `$x => f($x)`
105+
// from a polymorphic target type using capture sets. In that case the parameter type
106+
// of $x is not treated as inferred and is approximated to CapSet. An example is
107+
// capset-problem.scala. We handle these cases by appromxating to the empty set.
108+
Nil
109+
case _ =>
110+
// Nothing is a special type to represent the empty set
111+
if tp.isNothingType then Nil
112+
else tp :: Nil // should be checked by wellformedness
105113

106114
/** A list of capabilities of a retained set. */
107115
def retainedElements(using Context): List[Capability] =
@@ -575,18 +583,30 @@ extension (sym: Symbol)
575583
def hasTrackedParts(using Context): Boolean =
576584
!CaptureSet.ofTypeDeeply(sym.info).isAlwaysEmpty
577585

578-
/** `sym` itself or its info is annotated @use or it is a type parameter with a matching
579-
* @use-annotated term parameter that contains `sym` in its deep capture set.
586+
/** Until 3.7:
587+
* `sym` itself or its info is annotated @use or it is a type parameter with a matching
588+
* @use-annotated term parameter that contains `sym` in its deep capture set.
589+
* From 3.8:
590+
* `sym` is a capset parameter without a `@reserve` annotation that
591+
* - belongs to a class in a class, or
592+
* - belongs to a method where it appears in a the deep capture set of a following term parameter of the same method.
580593
*/
581594
def isUseParam(using Context): Boolean =
582595
sym.hasAnnotation(defn.UseAnnot)
583596
|| sym.info.hasAnnotation(defn.UseAnnot)
584597
|| sym.is(TypeParam)
585-
&& sym.owner.rawParamss.nestedExists: param =>
586-
param.is(TermParam) && param.hasAnnotation(defn.UseAnnot)
587-
&& param.info.deepCaptureSet.elems.exists:
588-
case c: TypeRef => c.symbol == sym
589-
case _ => false
598+
&& !sym.info.hasAnnotation(defn.ReserveAnnot)
599+
&& (sym.owner.isClass
600+
|| sym.owner.rawParamss.nestedExists: param =>
601+
param.is(TermParam)
602+
&& (!ccConfig.allowUse || param.hasAnnotation(defn.UseAnnot))
603+
&& param.info.deepCaptureSet.elems.exists:
604+
case c: TypeRef => c.symbol == sym
605+
case _ => false
606+
|| {
607+
//println(i"not is use param $sym")
608+
false
609+
})
590610

591611
/** `sym` or its info is annotated with `@consume`. */
592612
def isConsumeParam(using Context): Boolean =

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

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -468,11 +468,11 @@ sealed abstract class CaptureSet extends Showable:
468468
case elem: FreshCap => elem.ccOwner.isContainedIn(rootLimit)
469469
case _ => false
470470

471-
/** Invoke `handler` if this set has (or later aquires) a root capability.
472-
* Excluded are Fresh instances unless their ccOwner is contained in `upto`.
471+
/** Invoke `handler` if this set has (or later aquires) a bad root capability.
472+
* Fresh instances count as good as long as their ccOwner is outside `upto`.
473473
* If `upto` is NoSymbol, all Fresh instances are admitted.
474474
*/
475-
def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
475+
def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
476476
if elems.exists(isBadRoot(upto, _)) then handler()
477477
this
478478

@@ -720,7 +720,7 @@ object CaptureSet:
720720
private def narrowClassifier(cls: ClassSymbol)(using Context): Unit =
721721
val newClassifier = leastClassifier(classifier, cls)
722722
if newClassifier == defn.NothingClass then
723-
println(i"conflicting classifications for $this, was $classifier, now $cls")
723+
capt.println(i"conflicting classifications for $this, was $classifier, now $cls")
724724
myClassifier = newClassifier
725725

726726
override def adoptClassifier(cls: ClassSymbol)(using Context): Unit =
@@ -840,10 +840,10 @@ object CaptureSet:
840840
|| isConst
841841
|| varState.canRecord && { includeDep(cs); true }
842842

843-
override def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
843+
override def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context): this.type =
844844
rootLimit = upto
845845
rootAddedHandler = handler
846-
super.disallowRootCapability(upto)(handler)
846+
super.disallowBadRoots(upto)(handler)
847847

848848
override def ensureWellformed(handler: Capability => (Context) ?=> Unit)(using Context): this.type =
849849
newElemAddedHandler = handler
@@ -945,7 +945,7 @@ object CaptureSet:
945945
* Test case: Without that tweak, logger.scala would not compile.
946946
*/
947947
class RefiningVar(owner: Symbol)(using Context) extends Var(owner):
948-
override def disallowRootCapability(upto: Symbol)(handler: () => Context ?=> Unit)(using Context) = this
948+
override def disallowBadRoots(upto: Symbol)(handler: () => Context ?=> Unit)(using Context) = this
949949

950950
/** A variable that is derived from some other variable via a map or filter. */
951951
abstract class DerivedVar(owner: Symbol, initialElems: Refs)(using @constructorOnly ctx: Context)
@@ -1306,7 +1306,7 @@ object CaptureSet:
13061306
|cannot be included in outer capture set $cs"""
13071307
else if !elem.tryClassifyAs(cs.classifier) then
13081308
i"""capability ${elem} is not classified as ${cs.classifier}, therefore it
1309-
|cannot be included in capture set $cs of ${cs.classifier} elements"""
1309+
|cannot be included in capture set $cs of ${cs.classifier.name} elements"""
13101310
else if cs.isBadRoot(elem) then
13111311
elem match
13121312
case elem: FreshCap =>

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

Lines changed: 48 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -120,11 +120,11 @@ object CheckCaptures:
120120
report.error(em"$elem: $tpe is not a legal element of a capture set", ann.srcPos)
121121
ann.retainedSet.retainedElementsRaw.foreach(check)
122122

123-
/** Under the sealed policy, report an error if some part of `tp` contains the
124-
* root capability in its capture set or if it refers to a type parameter that
125-
* could possibly be instantiated with cap in a way that's visible at the type.
123+
/** Disallow bad roots anywhere in type `tp``.
124+
* @param upto controls up to which owner local fresh capabilities should be disallowed.
125+
* See disallowBadRoots for details.
126126
*/
127-
private def disallowRootCapabilitiesIn(tp: Type, upto: Symbol, what: String, have: String, addendum: String, pos: SrcPos)(using Context) =
127+
private def disallowBadRootsIn(tp: Type, upto: Symbol, what: => String, have: => String, addendum: => String, pos: SrcPos)(using Context) =
128128
val check = new TypeTraverser:
129129

130130
private val seen = new EqHashSet[TypeRef]
@@ -151,7 +151,7 @@ object CheckCaptures:
151151
case CapturingType(parent, refs) =>
152152
if variance >= 0 then
153153
val openScopes = openExistentialScopes
154-
refs.disallowRootCapability(upto): () =>
154+
refs.disallowBadRoots(upto): () =>
155155
def part =
156156
if t eq tp then ""
157157
else
@@ -179,7 +179,10 @@ object CheckCaptures:
179179
case t =>
180180
traverseChildren(t)
181181
check.traverse(tp)
182-
end disallowRootCapabilitiesIn
182+
end disallowBadRootsIn
183+
184+
private def ownerStr(owner: Symbol)(using Context): String =
185+
if owner.isAnonymousFunction then "enclosing function" else owner.show
183186

184187
trait CheckerAPI:
185188
/** Complete symbol info of a val or a def */
@@ -490,7 +493,7 @@ class CheckCaptures extends Recheck, SymTransformer:
490493
case c1: CoreCapability =>
491494
CaptureSet.ofType(c1.widen, followResult = true)
492495
capt.println(i"Widen reach $c to $underlying in ${env.owner}")
493-
underlying.disallowRootCapability(NoSymbol): () =>
496+
underlying.disallowBadRoots(NoSymbol): () =>
494497
report.error(em"Local capability $c${env.owner.qualString("in")} cannot have `cap` as underlying capture set", tree.srcPos)
495498
recur(underlying, env, lastEnv)
496499

@@ -534,20 +537,26 @@ class CheckCaptures extends Recheck, SymTransformer:
534537
// Under deferredReaches, don't propagate out of methods inside terms.
535538
// The use set of these methods will be charged when that method is called.
536539

537-
recur(cs, curEnv, null)
538-
useInfos += ((tree, cs, curEnv))
540+
if !cs.isAlwaysEmpty then
541+
recur(cs, curEnv, null)
542+
useInfos += ((tree, cs, curEnv))
539543
end markFree
540544

541-
/** If capability `c` refers to a parameter that is not @use declared, report an error.
545+
/** If capability `c` refers to a parameter that is not implicitly or explicitly
546+
* @use declared, report an error.
542547
*/
543548
def checkUseDeclared(c: Capability, pos: SrcPos)(using Context): Unit =
544549
c.paramPathRoot match
545550
case ref: NamedType if !ref.symbol.isUseParam =>
546551
val what = if ref.isType then "Capture set parameter" else "Local reach capability"
547-
val owner = ref.symbol.owner
552+
def mitigation =
553+
if ccConfig.allowUse
554+
then i"\nTo allow this, the ${ref.symbol} should be declared with a @use annotation."
555+
else if !ref.isType then i"\nYou could try to abstract the capabilities referred to by $c in a capset variable."
556+
else ""
548557
report.error(
549-
em"""$what $c leaks into capture scope${owner.qualString("of")}.
550-
|To allow this, the ${ref.symbol} should be declared with a @use annotation""", pos)
558+
em"$what $c leaks into capture scope${ref.symbol.owner.qualString("of")}.$mitigation",
559+
pos)
551560
case _ =>
552561

553562
/** Include references captured by the called method in the current environment stack */
@@ -570,16 +579,16 @@ class CheckCaptures extends Recheck, SymTransformer:
570579
case _ =>
571580
tp
572581

573-
/** Under the sealed policy, disallow the root capability in type arguments.
574-
* Type arguments come either from a TypeApply node or from an AppliedType
582+
/** Type arguments come either from a TypeApply node or from an AppliedType
575583
* which represents a trait parent in a template.
576-
* Also, if a corresponding formal type parameter is declared or implied @use,
577-
* charge the deep capture set of the argument to the environent.
584+
* - Disallow global cap and result caps in such arguments.
585+
* - If a corresponding formal type parameter is declared or implied @use,
586+
* charge the deep capture set of the argument to the environent.
578587
* @param fn the type application, of type TypeApply or TypeTree
579588
* @param sym the constructor symbol (could be a method or a val or a class)
580589
* @param args the type arguments
581590
*/
582-
def disallowCapInTypeArgs(fn: Tree, sym: Symbol, args: List[Tree])(using Context): Unit =
591+
def markFreeTypeArgs(fn: Tree, sym: Symbol, args: List[Tree])(using Context): Unit =
583592
def isExempt = sym.isTypeTestOrCast || defn.capsErasedValueMethods.contains(sym)
584593
if !isExempt then
585594
val paramNames = atPhase(thisPhase.prev):
@@ -592,17 +601,17 @@ class CheckCaptures extends Recheck, SymTransformer:
592601

593602
for case (arg: TypeTree, pname) <- args.lazyZip(paramNames) do
594603
def where = if sym.exists then i" in an argument of $sym" else ""
595-
val (addendum, errTree) =
604+
def addendum =
596605
if arg.isInferred
597-
then (i"\nThis is often caused by a local capability$where\nleaking as part of its result.", fn)
598-
else if arg.span.exists then ("", arg)
599-
else ("", fn)
600-
disallowRootCapabilitiesIn(arg.nuType, NoSymbol,
606+
then i"\nThis is often caused by a local capability$where\nleaking as part of its result."
607+
else ""
608+
def errTree = if !arg.isInferred && arg.span.exists then arg else fn
609+
disallowBadRootsIn(arg.nuType, NoSymbol,
601610
i"Type variable $pname of $sym", "be instantiated to", addendum, errTree.srcPos)
602611

603612
val param = fn.symbol.paramNamed(pname)
604613
if param.isUseParam then markFree(arg.nuType.deepCaptureSet, errTree)
605-
end disallowCapInTypeArgs
614+
end markFreeTypeArgs
606615

607616
/** Rechecking idents involves:
608617
* - adding call captures for idents referring to methods
@@ -870,7 +879,7 @@ class CheckCaptures extends Recheck, SymTransformer:
870879
case fun @ Select(qual, nme.apply) => qual.symbol.orElse(fun.symbol)
871880
case fun => fun.symbol
872881
def methDescr = if meth.exists then i"$meth's type " else ""
873-
disallowCapInTypeArgs(tree.fun, meth, tree.args)
882+
markFreeTypeArgs(tree.fun, meth, tree.args)
874883
val funType = super.recheckTypeApply(tree, pt)
875884
val res = resultToFresh(funType, Origin.ResultInstance(funType, meth))
876885
includeCallCaptures(tree.symbol, res, tree)
@@ -919,7 +928,7 @@ class CheckCaptures extends Recheck, SymTransformer:
919928
assert(params.hasSameLengthAs(argTypes), i"$mdef vs $pt, ${params}")
920929
for (argType, param) <- argTypes.lazyZip(params) do
921930
val paramTpt = param.asInstanceOf[ValDef].tpt
922-
val paramType = freshToCap(paramTpt.nuType)
931+
val paramType = freshToCap(param.symbol, paramTpt.nuType)
923932
checkConformsExpr(argType, paramType, param)
924933
.showing(i"compared expected closure formal $argType against $param with ${paramTpt.nuType}", capt)
925934
if !pt.isInstanceOf[RefinedType]
@@ -986,7 +995,7 @@ class CheckCaptures extends Recheck, SymTransformer:
986995
i"\n\nNote that $sym does not count as local since it is captured by $enclStr"
987996
case _ =>
988997
""
989-
disallowRootCapabilitiesIn(
998+
disallowBadRootsIn(
990999
tree.tpt.nuType, NoSymbol, i"Mutable $sym", "have type", addendum, sym.srcPos)
9911000
checkInferredResult(super.recheckValDef(tree, sym), tree)
9921001
finally
@@ -1169,7 +1178,7 @@ class CheckCaptures extends Recheck, SymTransformer:
11691178
for case tpt: TypeTree <- impl.parents do
11701179
tpt.tpe match
11711180
case AppliedType(fn, args) =>
1172-
disallowCapInTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_)))
1181+
markFreeTypeArgs(tpt, fn.typeSymbol, args.map(TypeTree(_)))
11731182
case _ =>
11741183
ccState.inNestedLevelUnless(cls.is(Module)):
11751184
super.recheckClassDef(tree, impl, cls)
@@ -1202,7 +1211,7 @@ class CheckCaptures extends Recheck, SymTransformer:
12021211
recheck(tree.expr, pt)
12031212
val tp = recheckTryRest(bodyType, tree.cases, tree.finalizer, pt)
12041213
if Feature.enabled(Feature.saferExceptions) then
1205-
disallowRootCapabilitiesIn(tp, ctx.owner,
1214+
disallowBadRootsIn(tp, ctx.owner,
12061215
"The result of `try`", "have type",
12071216
"\nThis is often caused by a locally generated exception capability leaking as part of its result.",
12081217
tree.srcPos)
@@ -1747,7 +1756,7 @@ class CheckCaptures extends Recheck, SymTransformer:
17471756

17481757
override def checkInheritedTraitParameters: Boolean = false
17491758

1750-
/** Check that overrides don't change the @use or @consume status of their parameters */
1759+
/** Check that overrides don't change the @use, @consume, or @reserve status of their parameters */
17511760
override def additionalChecks(member: Symbol, other: Symbol)(using Context): Unit =
17521761
for
17531762
(params1, params2) <- member.rawParamss.lazyZip(other.rawParamss)
@@ -1764,6 +1773,7 @@ class CheckCaptures extends Recheck, SymTransformer:
17641773

17651774
checkAnnot(defn.UseAnnot)
17661775
checkAnnot(defn.ConsumeAnnot)
1776+
checkAnnot(defn.ReserveAnnot)
17671777
end OverridingPairsCheckerCC
17681778

17691779
def traverse(t: Tree)(using Context) =
@@ -1946,7 +1956,7 @@ class CheckCaptures extends Recheck, SymTransformer:
19461956
if !(pos.span.isSynthetic && ctx.reporter.errorsReported)
19471957
&& !arg.typeSymbol.name.is(WildcardParamName)
19481958
then
1949-
CheckCaptures.disallowRootCapabilitiesIn(arg, NoSymbol,
1959+
CheckCaptures.disallowBadRootsIn(arg, NoSymbol,
19501960
"Array", "have element type", "",
19511961
pos)
19521962
traverseChildren(t)
@@ -1991,7 +2001,13 @@ class CheckCaptures extends Recheck, SymTransformer:
19912001
case c: DerivedCapability =>
19922002
checkElem(c.underlying)
19932003
case c: FreshCap =>
1994-
check(c.hiddenSet)
2004+
c.origin match
2005+
case Origin.Parameter(param) =>
2006+
report.error(
2007+
em"Local $c created in type of $param leaks into capture scope${param.owner.qualString("of")}",
2008+
tree.srcPos)
2009+
case _ =>
2010+
check(c.hiddenSet)
19952011
case _ =>
19962012

19972013
check(uses)

0 commit comments

Comments
 (0)