Skip to content

Commit c09154a

Browse files
authored
Fix setup of CapSet arguments. (#21309)
These arguments tell the whole truth; they cannot possibly be decorated with another capture set. So we should not add a capture set variable.
2 parents 8897dc4 + 618bbc5 commit c09154a

File tree

12 files changed

+143
-23
lines changed

12 files changed

+143
-23
lines changed

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

+3-1
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,9 @@ extension (tp: Type)
264264
def boxed(using Context): Type = tp.dealias match
265265
case tp @ CapturingType(parent, refs) if !tp.isBoxed && !refs.isAlwaysEmpty =>
266266
tp.annot match
267-
case ann: CaptureAnnotation => AnnotatedType(parent, ann.boxedAnnot)
267+
case ann: CaptureAnnotation =>
268+
assert(!parent.derivesFrom(defn.Caps_CapSet))
269+
AnnotatedType(parent, ann.boxedAnnot)
268270
case ann => tp
269271
case tp: RealTypeBounds =>
270272
tp.derivedTypeBounds(tp.lo.boxed, tp.hi.boxed)

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

+1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ object CapturingType:
3333
* boxing status is the same or if A is boxed.
3434
*/
3535
def apply(parent: Type, refs: CaptureSet, boxed: Boolean = false)(using Context): Type =
36+
assert(!boxed || !parent.derivesFrom(defn.Caps_CapSet))
3637
if refs.isAlwaysEmpty && !refs.keepAlways then parent
3738
else parent match
3839
case parent @ CapturingType(parent1, refs1) if boxed || !parent.isBoxed =>

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

+39-15
Original file line numberDiff line numberDiff line change
@@ -388,23 +388,25 @@ class CheckCaptures extends Recheck, SymTransformer:
388388
// should be included.
389389
val included = cs.filter: c =>
390390
c.stripReach match
391-
case ref: TermRef =>
392-
//if c.isReach then println(i"REACH $c in ${env.owner}")
393-
//assert(!env.owner.isAnonymousFunction)
391+
case ref: NamedType =>
394392
val refSym = ref.symbol
395393
val refOwner = refSym.owner
396394
val isVisible = isVisibleFromEnv(refOwner)
397-
if !isVisible && c.isReach && refSym.is(Param) && refOwner == env.owner then
398-
if refSym.hasAnnotation(defn.UnboxAnnot) then
399-
capt.println(i"exempt: $ref in $refOwner")
400-
else
401-
// Reach capabilities that go out of scope have to be approximated
402-
// by their underlying capture set, which cannot be universal.
403-
// Reach capabilities of @unboxed parameters are exempted.
404-
val cs = CaptureSet.ofInfo(c)
405-
cs.disallowRootCapability: () =>
406-
report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos)
407-
checkSubset(cs, env.captured, pos, provenance(env))
395+
if !isVisible
396+
&& (c.isReach || ref.isType)
397+
&& refSym.is(Param)
398+
&& refOwner == env.owner
399+
then
400+
if refSym.hasAnnotation(defn.UnboxAnnot) then
401+
capt.println(i"exempt: $ref in $refOwner")
402+
else
403+
// Reach capabilities that go out of scope have to be approximated
404+
// by their underlying capture set, which cannot be universal.
405+
// Reach capabilities of @unboxed parameters are exempted.
406+
val cs = CaptureSet.ofInfo(c)
407+
cs.disallowRootCapability: () =>
408+
report.error(em"Local reach capability $c leaks into capture scope of ${env.ownerString}", pos)
409+
checkSubset(cs, env.captured, pos, provenance(env))
408410
isVisible
409411
case ref: ThisType => isVisibleFromEnv(ref.cls)
410412
case _ => false
@@ -674,7 +676,29 @@ class CheckCaptures extends Recheck, SymTransformer:
674676
i"Sealed type variable $pname", "be instantiated to",
675677
i"This is often caused by a local capability$where\nleaking as part of its result.",
676678
tree.srcPos)
677-
handleCall(meth, tree, () => Existential.toCap(super.recheckTypeApply(tree, pt)))
679+
val res = handleCall(meth, tree, () => Existential.toCap(super.recheckTypeApply(tree, pt)))
680+
if meth == defn.Caps_containsImpl then checkContains(tree)
681+
res
682+
end recheckTypeApply
683+
684+
/** Faced with a tree of form `caps.contansImpl[CS, r.type]`, check that `R` is a tracked
685+
* capability and assert that `{r} <:CS`.
686+
*/
687+
def checkContains(tree: TypeApply)(using Context): Unit =
688+
tree.fun.knownType.widen match
689+
case fntpe: PolyType =>
690+
tree.args match
691+
case csArg :: refArg :: Nil =>
692+
val cs = csArg.knownType.captureSet
693+
val ref = refArg.knownType
694+
capt.println(i"check contains $cs , $ref")
695+
ref match
696+
case ref: CaptureRef if ref.isTracked =>
697+
checkElem(ref, cs, tree.srcPos)
698+
case _ =>
699+
report.error(em"$refArg is not a tracked capability", refArg.srcPos)
700+
case _ =>
701+
case _ =>
678702

679703
override def recheckBlock(tree: Block, pt: Type)(using Context): Type =
680704
inNestedLevel(super.recheckBlock(tree, pt))

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

+7-4
Original file line numberDiff line numberDiff line change
@@ -134,9 +134,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
134134
private def box(tp: Type)(using Context): Type =
135135
def recur(tp: Type): Type = tp.dealiasKeepAnnotsAndOpaques match
136136
case tp @ CapturingType(parent, refs) =>
137-
if tp.isBoxed then tp else tp.boxed
137+
if tp.isBoxed || parent.derivesFrom(defn.Caps_CapSet) then tp
138+
else tp.boxed
138139
case tp @ AnnotatedType(parent, ann) =>
139-
if ann.symbol.isRetains
140+
if ann.symbol.isRetains && !parent.derivesFrom(defn.Caps_CapSet)
140141
then CapturingType(parent, ann.tree.toCaptureSet, boxed = true)
141142
else tp.derivedAnnotatedType(box(parent), ann)
142143
case tp1 @ AppliedType(tycon, args) if defn.isNonRefinedFunction(tp1) =>
@@ -605,8 +606,10 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
605606
!refs.isEmpty
606607
case tp: (TypeRef | AppliedType) =>
607608
val sym = tp.typeSymbol
608-
if sym.isClass then !sym.isPureClass
609-
else instanceCanBeImpure(tp.superType)
609+
if sym.isClass
610+
then !sym.isPureClass
611+
else !tp.derivesFrom(defn.Caps_CapSet) // CapSet arguments don't get other capture set variables added
612+
&& instanceCanBeImpure(tp.superType)
610613
case tp: (RefinedOrRecType | MatchType) =>
611614
instanceCanBeImpure(tp.underlying)
612615
case tp: AndType =>

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

+4-2
Original file line numberDiff line numberDiff line change
@@ -993,15 +993,17 @@ class Definitions {
993993
@tu lazy val CapsModule: Symbol = requiredModule("scala.caps")
994994
@tu lazy val captureRoot: TermSymbol = CapsModule.requiredValue("cap")
995995
@tu lazy val Caps_Capability: TypeSymbol = CapsModule.requiredType("Capability")
996-
@tu lazy val Caps_CapSet = requiredClass("scala.caps.CapSet")
996+
@tu lazy val Caps_CapSet: ClassSymbol = requiredClass("scala.caps.CapSet")
997997
@tu lazy val Caps_reachCapability: TermSymbol = CapsModule.requiredMethod("reachCapability")
998998
@tu lazy val Caps_capsOf: TermSymbol = CapsModule.requiredMethod("capsOf")
999-
@tu lazy val Caps_Exists = requiredClass("scala.caps.Exists")
999+
@tu lazy val Caps_Exists: ClassSymbol = requiredClass("scala.caps.Exists")
10001000
@tu lazy val CapsUnsafeModule: Symbol = requiredModule("scala.caps.unsafe")
10011001
@tu lazy val Caps_unsafeAssumePure: Symbol = CapsUnsafeModule.requiredMethod("unsafeAssumePure")
10021002
@tu lazy val Caps_unsafeBox: Symbol = CapsUnsafeModule.requiredMethod("unsafeBox")
10031003
@tu lazy val Caps_unsafeUnbox: Symbol = CapsUnsafeModule.requiredMethod("unsafeUnbox")
10041004
@tu lazy val Caps_unsafeBoxFunArg: Symbol = CapsUnsafeModule.requiredMethod("unsafeBoxFunArg")
1005+
@tu lazy val Caps_ContainsTrait: TypeSymbol = CapsModule.requiredType("Capability")
1006+
@tu lazy val Caps_containsImpl: TermSymbol = CapsModule.requiredMethod("containsImpl")
10051007

10061008
@tu lazy val PureClass: Symbol = requiredClass("scala.Pure")
10071009

library/src/scala/caps.scala

+11-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
package scala
22

3-
import annotation.{experimental, compileTimeOnly}
3+
import annotation.{experimental, compileTimeOnly, retainsCap}
44

55
@experimental object caps:
66

@@ -19,6 +19,16 @@ import annotation.{experimental, compileTimeOnly}
1919
/** Carrier trait for capture set type parameters */
2020
trait CapSet extends Any
2121

22+
/** A type constraint expressing that the capture set `C` needs to contain
23+
* the capability `R`
24+
*/
25+
sealed trait Contains[C <: CapSet @retainsCap, R <: Singleton]
26+
27+
/** The only implementation of `Contains`. The constraint that `{R} <: C` is
28+
* added separately by the capture checker.
29+
*/
30+
given containsImpl[C <: CapSet @retainsCap, R <: Singleton]: Contains[C, R]()
31+
2232
@compileTimeOnly("Should be be used only internally by the Scala compiler")
2333
def capsOf[CS]: Any = ???
2434

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
-- Error: tests/neg-custom-args/captures/i21313.scala:6:27 -------------------------------------------------------------
2+
6 |def foo(x: Async) = x.await(???) // error
3+
| ^
4+
| (x : Async) is not a tracked capability
5+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i21313.scala:15:12 ---------------------------------------
6+
15 | ac1.await(src2) // error
7+
| ^^^^
8+
| Found: (src2 : Source[Int, caps.CapSet^{ac2}]^?)
9+
| Required: Source[Int, caps.CapSet^{ac1}]^
10+
|
11+
| longer explanation available when compiling with `-explain`
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import caps.CapSet
2+
3+
trait Async:
4+
def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T
5+
6+
def foo(x: Async) = x.await(???) // error
7+
8+
trait Source[+T, Cap^]:
9+
final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap.
10+
11+
def test(using ac1: Async^, ac2: Async^, x: String) =
12+
val src1 = new Source[Int, CapSet^{ac1}] {}
13+
ac1.await(src1) // ok
14+
val src2 = new Source[Int, CapSet^{ac2}] {}
15+
ac1.await(src2) // error
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Error: tests/neg-custom-args/captures/i21347.scala:4:15 -------------------------------------------------------------
2+
4 | ops.foreach: op => // error
3+
| ^
4+
| Local reach capability C leaks into capture scope of method runOps
5+
5 | op()
6+
-- Error: tests/neg-custom-args/captures/i21347.scala:8:14 -------------------------------------------------------------
7+
8 | () => runOps(f :: Nil) // error
8+
| ^^^^^^^^^^^^^^^^
9+
| reference (caps.cap : caps.Capability) is not included in the allowed capture set {}
10+
| of an enclosing function literal with expected type () -> Unit
11+
-- Error: tests/neg-custom-args/captures/i21347.scala:11:15 ------------------------------------------------------------
12+
11 | ops.foreach: op => // error
13+
| ^
14+
| Local reach capability ops* leaks into capture scope of method runOpsAlt
15+
12 | op()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import language.experimental.captureChecking
2+
3+
def runOps[C^](ops: List[() ->{C^} Unit]): Unit =
4+
ops.foreach: op => // error
5+
op()
6+
7+
def boom(f: () => Unit): () -> Unit =
8+
() => runOps(f :: Nil) // error
9+
10+
def runOpsAlt(ops: List[() => Unit]): Unit =
11+
ops.foreach: op => // error
12+
op()
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import caps.CapSet
2+
3+
trait Async:
4+
def await[T, Cap^](using caps.Contains[Cap, this.type])(src: Source[T, Cap]^): T
5+
6+
trait Source[+T, Cap^]:
7+
final def await(using ac: Async^{Cap^}) = ac.await[T, Cap](this) // Contains[Cap, ac] is assured because {ac} <: Cap.
8+
9+
def test(using ac1: Async^, ac2: Async^, x: String) =
10+
val src1 = new Source[Int, CapSet^{ac1}] {}
11+
ac1.await(src1)

tests/pos/polycap.scala

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import language.experimental.captureChecking
2+
3+
class Source[+T, Cap^]
4+
5+
def completed[T, Cap^](result: T): Source[T, Cap] =
6+
//val fut = new Source[T, Cap]()
7+
val fut2 = new Source[T, Cap]()
8+
fut2: Source[T, Cap]
9+
10+
11+
12+
13+
14+

0 commit comments

Comments
 (0)