@@ -929,6 +929,17 @@ class CheckCaptures extends Recheck, SymTransformer:
929929 .showing(i " constr type $mt with $argTypes%, % in $constr = $result" , capt)
930930 end refineConstructorInstance
931931
932+ /** If `mbr` is a field that has (possibly restricted) FreshCaps in its span capture set,
933+ * their classifiers, otherwise the empty list.
934+ */
935+ private def classifiersOfFreshInType (mbr : Symbol )(using Context ): List [ClassSymbol ] =
936+ if contributesFreshToClass(mbr) then
937+ mbr.info.spanCaptureSet.elems
938+ .filter(_.isTerminalCapability)
939+ .toList
940+ .map(_.classifier.asClass)
941+ else Nil
942+
932943 /** The additional capture set implied by the capture sets of its fields. This
933944 * is either empty or, if some fields have a terminal capability in their span
934945 * capture sets, it consists of a single fresh cap that subsumes all these terminal
@@ -946,16 +957,9 @@ class CheckCaptures extends Recheck, SymTransformer:
946957 */
947958 def impliedClassifiers (cls : Symbol ): List [ClassSymbol ] = cls match
948959 case cls : ClassSymbol =>
949- var fieldClassifiers =
950- for
951- sym <- setup.fieldsWithExplicitTypes.getOrElse(cls, cls.info.decls.toList)
952- if contributesFreshToClass(sym)
953- case fresh : FreshCap <- sym.info.spanCaptureSet.elems
954- .filter(_.isTerminalCapability)
955- .map(_.stripReadOnly)
956- .toList
957- _ = pushInfo(i " Note: ${sym.showLocated} captures a $fresh" )
958- yield fresh.hiddenSet.classifier
960+ var fieldClassifiers = setup.fieldsWithExplicitTypes // pick fields with explicit types for classes in this compilation unit
961+ .getOrElse(cls, cls.info.decls.toList) // pick all symbols in class scope for other classes
962+ .flatMap(classifiersOfFreshInType)
959963 if cls.typeRef.isMutableType then
960964 fieldClassifiers = defn.Caps_Mutable :: fieldClassifiers
961965 val parentClassifiers =
@@ -1199,11 +1203,20 @@ class CheckCaptures extends Recheck, SymTransformer:
11991203 curEnv = saved
12001204 end recheckDefDef
12011205
1202- /** If val or def definition with inferred (result) type is visible
1203- * in other compilation units, check that the actual inferred type
1204- * conforms to the expected type where all inferred capture sets are dropped.
1205- * This ensures that if files compile separately, they will also compile
1206- * in a joint compilation.
1206+ /** Two tests for member definitions with inferred types:
1207+ *
1208+ * 1. If val or def definition with inferred (result) type is visible
1209+ * in other compilation units, check that the actual inferred type
1210+ * conforms to the expected type where all inferred capture sets are dropped.
1211+ * This ensures that if files compile separately, they will also compile
1212+ * in a joint compilation.
1213+ * 2. If a val has an inferred type with a terminal capability in its span capset,
1214+ * check that it this capability is subsumed by the capset that was inferred
1215+ * for the class from its other fields via `captureSetImpliedByFields`.
1216+ * That capset is defined to take into account all fields but is computed
1217+ * only from fields with explicitly given types in order to avoid cycles.
1218+ * See comment on Setup.fieldsWithExplicitTypes. So we have to make sure
1219+ * that fields with inferred types would not change that capset.
12071220 */
12081221 def checkInferredResult (tp : Type , tree : ValOrDefDef )(using Context ): Type =
12091222 val sym = tree.symbol
@@ -1238,8 +1251,15 @@ class CheckCaptures extends Recheck, SymTransformer:
12381251 |The new inferred type $tp
12391252 |must conform to this type. """
12401253
1254+ def covers (classCapset : CaptureSet , fieldClassifiers : List [ClassSymbol ]): Boolean =
1255+ fieldClassifiers.forall: cls =>
1256+ classCapset.elems.exists:
1257+ case fresh : FreshCap => cls.isSubClass(fresh.hiddenSet.classifier)
1258+ case _ => false
1259+
12411260 tree.tpt match
12421261 case tpt : InferredTypeTree =>
1262+ // Test point (1) of doc comment above
12431263 if ! sym.isLocalToCompilationUnit && ! isExemptFromChecks
12441264 // Symbols that can't be seen outside the compilation unit can have inferred types
12451265 then
@@ -1250,17 +1270,19 @@ class CheckCaptures extends Recheck, SymTransformer:
12501270 // The check that inferred <: expected is done after recheck so that it
12511271 // does not interfere with normal rechecking by constraining capture set variables.
12521272 }
1253- if ! sym.isLocalToCompilationUnitIgnoringPrivate
1273+ // Test point (2) of doc comment above
1274+ if sym.owner.isClass && ! sym.owner.isStaticOwner
12541275 && contributesFreshToClass(sym)
1255- // Private symbols capturing a root capability need explicit types
1256- // so that we can compute field constributions to class instance
1257- // capture sets across compilation units.
12581276 then
12591277 todoAtPostCheck += { () =>
1260- if sym.info.spanCaptureSet.containsTerminalCapability then
1278+ val cls = sym.owner.asClass
1279+ val fieldClassifiers = classifiersOfFreshInType(sym)
1280+ val classCapset = captureSetImpliedByFields(cls, cls.appliedRef)
1281+ if ! covers(classCapset, fieldClassifiers) then
12611282 report.error(
12621283 em """ $sym needs an explicit type because it captures a root capability in its type ${tree.tpt.nuType}.
1263- |Fields of publicily accessible classes that capture a root capability need to be given an explicit type. """ ,
1284+ |Fields capturing a root capability need to be given an explicit type unless the capability is already
1285+ |subsumed by the computed capability of the enclosing class. """ ,
12641286 tpt.srcPos)
12651287 }
12661288 case _ =>
0 commit comments