Skip to content

Commit c43976b

Browse files
committed
Implement inheritance condition for Mutable types
1 parent 5c51b7b commit c43976b

File tree

4 files changed

+127
-59
lines changed

4 files changed

+127
-59
lines changed

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

Lines changed: 89 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -890,7 +890,7 @@ class CheckCaptures extends Recheck, SymTransformer:
890890
var allCaptures: CaptureSet =
891891
if core.derivesFromCapability
892892
then initCs ++ FreshCap(Origin.NewCapability(core)).singletonCaptureSet
893-
else initCs ++ impliedByFields(core)
893+
else initCs ++ captureSetImpliedByFields(cls, core)
894894
for (getterName, argType) <- mt.paramNames.lazyZip(argTypes) do
895895
val getter = cls.info.member(getterName).suchThat(_.isRefiningParamAccessor).symbol
896896
if !getter.is(Private) && getter.hasTrackedParts then
@@ -914,57 +914,57 @@ class CheckCaptures extends Recheck, SymTransformer:
914914
val (refined, cs) = addParamArgRefinements(core, initCs)
915915
refined.capturing(cs)
916916

917-
/** The additional capture set implied by the capture sets of its fields. This
918-
* is either empty or, if some fields have a terminal capability in their span
919-
* capture sets, it consists of a single fresh cap that subsumes all these terminal
920-
* capabiltities. Class parameters are not counted.
921-
*/
922-
def impliedByFields(core: Type): CaptureSet =
923-
var infos: List[String] = Nil
924-
def pushInfo(msg: => String) =
925-
if ctx.settings.YccVerbose.value then infos = msg :: infos
926-
927-
/** The classifiers of the fresh caps in the span capture sets of all fields
928-
* in the given class `cls`.
929-
*/
930-
def impliedClassifiers(cls: Symbol): List[ClassSymbol] = cls match
931-
case cls: ClassSymbol =>
932-
val fieldClassifiers =
933-
for
934-
sym <- cls.info.decls.toList
935-
if contributesFreshToClass(sym)
936-
case fresh: FreshCap <- sym.info.spanCaptureSet.elems
937-
.filter(_.isTerminalCapability)
938-
.map(_.stripReadOnly)
939-
.toList
940-
_ = pushInfo(i"Note: ${sym.showLocated} captures a $fresh")
941-
yield fresh.hiddenSet.classifier
942-
val parentClassifiers =
943-
cls.parentSyms.map(impliedClassifiers).filter(_.nonEmpty)
944-
if fieldClassifiers.isEmpty && parentClassifiers.isEmpty
945-
then Nil
946-
else parentClassifiers.foldLeft(fieldClassifiers.distinct)(dominators)
947-
case _ => Nil
948-
949-
def fresh =
950-
FreshCap(Origin.NewInstance(core)).tap: fresh =>
951-
if ctx.settings.YccVerbose.value then
952-
pushInfo(i"Note: instance of $cls captures a $fresh that comes from a field")
953-
report.echo(infos.mkString("\n"), ctx.owner.srcPos)
954-
955-
knownFresh.getOrElseUpdate(cls, impliedClassifiers(cls)) match
956-
case Nil => CaptureSet.empty
957-
case cl :: Nil =>
958-
val result = fresh
959-
result.hiddenSet.adoptClassifier(cl)
960-
result.singletonCaptureSet
961-
case _ => fresh.singletonCaptureSet
962-
end impliedByFields
963-
964917
augmentConstructorType(resType, capturedVars(cls))
965918
.showing(i"constr type $mt with $argTypes%, % in $constr = $result", capt)
966919
end refineConstructorInstance
967920

921+
/** The additional capture set implied by the capture sets of its fields. This
922+
* is either empty or, if some fields have a terminal capability in their span
923+
* capture sets, it consists of a single fresh cap that subsumes all these terminal
924+
* capabiltities. Class parameters are not counted.
925+
*/
926+
def captureSetImpliedByFields(cls: ClassSymbol, core: Type)(using Context): CaptureSet =
927+
var infos: List[String] = Nil
928+
def pushInfo(msg: => String) =
929+
if ctx.settings.YccVerbose.value then infos = msg :: infos
930+
931+
/** The classifiers of the fresh caps in the span capture sets of all fields
932+
* in the given class `cls`.
933+
*/
934+
def impliedClassifiers(cls: Symbol): List[ClassSymbol] = cls match
935+
case cls: ClassSymbol =>
936+
val fieldClassifiers =
937+
for
938+
sym <- cls.info.decls.toList
939+
if contributesFreshToClass(sym)
940+
case fresh: FreshCap <- sym.info.spanCaptureSet.elems
941+
.filter(_.isTerminalCapability)
942+
.map(_.stripReadOnly)
943+
.toList
944+
_ = pushInfo(i"Note: ${sym.showLocated} captures a $fresh")
945+
yield fresh.hiddenSet.classifier
946+
val parentClassifiers =
947+
cls.parentSyms.map(impliedClassifiers).filter(_.nonEmpty)
948+
if fieldClassifiers.isEmpty && parentClassifiers.isEmpty
949+
then Nil
950+
else parentClassifiers.foldLeft(fieldClassifiers.distinct)(dominators)
951+
case _ => Nil
952+
953+
def fresh =
954+
FreshCap(Origin.NewInstance(core)).tap: fresh =>
955+
if ctx.settings.YccVerbose.value then
956+
pushInfo(i"Note: instance of $cls captures a $fresh that comes from a field")
957+
report.echo(infos.mkString("\n"), ctx.owner.srcPos)
958+
959+
knownFresh.getOrElseUpdate(cls, impliedClassifiers(cls)) match
960+
case Nil => CaptureSet.empty
961+
case cl :: Nil =>
962+
val result = fresh
963+
result.hiddenSet.adoptClassifier(cl)
964+
result.singletonCaptureSet
965+
case _ => fresh.singletonCaptureSet
966+
end captureSetImpliedByFields
967+
968968
/** Recheck type applications:
969969
* - Map existential captures in result to `cap`
970970
* - include captures of called methods in environment
@@ -2138,10 +2138,40 @@ class CheckCaptures extends Recheck, SymTransformer:
21382138
end for
21392139
end checkEscapingUses
21402140

2141-
/** Check that arguments of TypeApplys and AppliedTypes conform to their bounds.
2141+
/** Check all parent class constructors of classes extending Mutable
2142+
* either also extend Mutable or are read-only.
2143+
*
2144+
* A parent class constructor is _read-only_ if the following conditions are met
2145+
* 1. The class does not retain any exclusive capabilities from its environment.
2146+
* 2. The constructor does not take arguments that retain exclusive capabilities.
2147+
* 3. The class does not does not have fields that retain exclusive universal capabilities.
2148+
*/
2149+
def checkMutableInheritance(cls: ClassSymbol, parents: List[Tree])(using Context): Unit =
2150+
if cls.derivesFrom(defn.Caps_Mutable) then
2151+
for parent <- parents do
2152+
if !parent.tpe.derivesFromMutable then
2153+
val pcls = parent.nuType.classSymbol
2154+
val parentIsExclusive =
2155+
if parent.isType then
2156+
capturedVars(pcls).isExclusive
2157+
|| captureSetImpliedByFields(cls, parent.nuType).isExclusive
2158+
else parent.nuType.captureSet.isExclusive
2159+
if parentIsExclusive then
2160+
report.error(
2161+
em"""illegal inheritance: $cls which extends `Mutable` is not allowed to also extend $pcls
2162+
|since $pcls retains exclusive capabilities but does not extend `Mutable`.""",
2163+
parent.srcPos)
2164+
2165+
/** Checks to run after the rechecking pass:
2166+
* - Check that arguments of TypeApplys and AppliedTypes conform to their bounds.
2167+
* - Check that no uses refer to reach capabilities of parameters of enclosing
2168+
* methods or classes.
2169+
* - Run the separation checker under language.experimental.separationChecking
2170+
* - Check that classes extending Mutable do not extend other classes that do
2171+
* not extend Mutable yet retain exclusive capabilities
21422172
*/
21432173
def postCheck(unit: tpd.Tree)(using Context): Unit =
2144-
val checker = new TreeTraverser:
2174+
val check = new TreeTraverser:
21452175
def traverse(tree: Tree)(using Context): Unit =
21462176
val lctx = tree match
21472177
case _: DefTree | _: TypeDef if tree.symbol.exists => ctx.withOwner(tree.symbol)
@@ -2161,30 +2191,35 @@ class CheckCaptures extends Recheck, SymTransformer:
21612191
if ccConfig.postCheckCapturesets then
21622192
args.lazyZip(tl.paramNames).foreach(checkTypeParam(_, _, fun.symbol))
21632193
case _ =>
2194+
case TypeDef(_, impl: Template) =>
2195+
checkMutableInheritance(tree.symbol.asClass, impl.parents)
21642196
case _ =>
21652197
end check
2166-
end checker
2198+
end check
21672199

2168-
checker.traverse(unit)(using ctx.withOwner(defn.RootClass))
2200+
check.traverse(unit)(using ctx.withOwner(defn.RootClass))
21692201
checkEscapingUses()
2202+
//checkMutableParents()
2203+
21702204
if sepChecksEnabled then
21712205
for (tree, cs, env) <- useInfos do
21722206
usedSet(tree) = tree.markedFree ++ cs
21732207
ccState.inSepCheck:
21742208
SepCheck(this).traverse(unit)
2209+
21752210
if !ctx.reporter.errorsReported then
21762211
// We dont report errors here if previous errors were reported, because other
21772212
// errors often result in bad applied types, but flagging these bad types gives
21782213
// often worse error messages than the original errors.
2179-
val checkApplied = new TreeTraverser:
2214+
val checkAppliedTypes = new TreeTraverser:
21802215
def traverse(t: Tree)(using Context) = t match
21812216
case tree: InferredTypeTree =>
21822217
case tree: New =>
21832218
case tree: TypeTree =>
21842219
withCollapsedFresh:
21852220
checkAppliedTypesIn(tree.withType(tree.nuType))
21862221
case _ => traverseChildren(t)
2187-
checkApplied.traverse(unit)
2222+
checkAppliedTypes.traverse(unit)
21882223
end postCheck
21892224

21902225
/** Perform the following kinds of checks:

docs/_docs/reference/experimental/capture-checking/mutability.md

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,11 +77,11 @@ When we create an instance of a mutable type we always add `cap` to its capture
7777

7878
**Restriction:** A non-mutable type cannot be downcast by a pattern match to a mutable type.
7979

80-
**Definition:** A class is _read_only_ if the following conditions are met:
80+
**Definition:** A parent class constructor is _read-only_ if the following conditions are met:
8181

82-
1. It does not extend any exclusive capabilities from its environment.
83-
2. It does not take parameters with exclusive capabilities.
84-
3. It does not contain mutable fields, or fields that take exclusive capabilities.
82+
1. The class does not retain any exclusive capabilities from its environment.
83+
2. The constructor does not take arguments that retain exclusive capabilities.
84+
3. The class does not does not have fields that retain exclusive universal capabilities.
8585

8686
**Restriction:** If a class or trait extends `Mutable` all its parent classes or traits must either extend `Mutable` or be read-only.
8787

@@ -455,4 +455,3 @@ val c = b += 3
455455
// b cannot be used from here
456456
```
457457
This code is equivalent to functional append with `+`, and is at the same time more efficient since it re-uses the storage of the argument buffer.
458-
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Error: tests/neg-custom-args/captures/mutable-inheritance.scala:3:16 ------------------------------------------------
2+
3 |class A extends E, caps.Mutable // error
3+
| ^
4+
| illegal inheritance: class A which extends `Mutable` is not allowed to also extend class E
5+
| since class E retains exclusive capabilities but does not extend `Mutable`.
6+
-- Error: tests/neg-custom-args/captures/mutable-inheritance.scala:6:16 ------------------------------------------------
7+
6 |class C extends B(??? : Int => Int), caps.Mutable // error
8+
| ^^^^^^^^^^^^^^^^^^^
9+
| illegal inheritance: class C which extends `Mutable` is not allowed to also extend class B
10+
| since class B retains exclusive capabilities but does not extend `Mutable`.
11+
-- Error: tests/neg-custom-args/captures/mutable-inheritance.scala:17:16 -----------------------------------------------
12+
17 |class H extends G, caps.Mutable // error
13+
| ^
14+
| illegal inheritance: class H which extends `Mutable` is not allowed to also extend class G
15+
| since class G retains exclusive capabilities but does not extend `Mutable`.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
class E extends caps.ExclusiveCapability
3+
class A extends E, caps.Mutable // error
4+
5+
class B(x: Int => Int)
6+
class C extends B(??? : Int => Int), caps.Mutable // error
7+
8+
class D extends caps.Mutable:
9+
var x: Int = 0
10+
11+
class F extends D, caps.Mutable // ok
12+
13+
class Ref extends caps.Mutable
14+
15+
class G:
16+
val r: Ref^ = Ref()
17+
class H extends G, caps.Mutable // error
18+
19+

0 commit comments

Comments
 (0)