@@ -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:
0 commit comments