@@ -945,6 +945,17 @@ class CheckCaptures extends Recheck, SymTransformer:
945945 .showing(i " constr type $mt with $argTypes%, % in $constr = $result" , capt)
946946 end refineConstructorInstance
947947
948+ /** If `mbr` is a field that has (possibly restricted) FreshCaps in its span capture set,
949+ * their classifiers, otherwise the empty list.
950+ */
951+ private def classifiersOfFreshInType (mbr : Symbol )(using Context ): List [ClassSymbol ] =
952+ if contributesFreshToClass(mbr) then
953+ mbr.info.spanCaptureSet.elems
954+ .filter(_.isTerminalCapability)
955+ .toList
956+ .map(_.classifier.asClass)
957+ else Nil
958+
948959 /** The additional capture set implied by the capture sets of its fields. This
949960 * is either empty or, if some fields have a terminal capability in their span
950961 * capture sets, it consists of a single fresh cap that subsumes all these terminal
@@ -962,16 +973,9 @@ class CheckCaptures extends Recheck, SymTransformer:
962973 */
963974 def impliedClassifiers (cls : Symbol ): List [ClassSymbol ] = cls match
964975 case cls : ClassSymbol =>
965- var fieldClassifiers =
966- for
967- sym <- setup.fieldsWithExplicitTypes.getOrElse(cls, cls.info.decls.toList)
968- if contributesFreshToClass(sym)
969- case fresh : FreshCap <- sym.info.spanCaptureSet.elems
970- .filter(_.isTerminalCapability)
971- .map(_.stripReadOnly)
972- .toList
973- _ = pushInfo(i " Note: ${sym.showLocated} captures a $fresh" )
974- yield fresh.hiddenSet.classifier
976+ var fieldClassifiers = setup.fieldsWithExplicitTypes // pick fields with explicit types for classes in this compilation unit
977+ .getOrElse(cls, cls.info.decls.toList) // pick all symbols in class scope for other classes
978+ .flatMap(classifiersOfFreshInType)
975979 if cls.typeRef.isMutableType then
976980 fieldClassifiers = defn.Caps_Mutable :: fieldClassifiers
977981 val parentClassifiers =
@@ -1225,11 +1229,20 @@ class CheckCaptures extends Recheck, SymTransformer:
12251229 curEnv = saved
12261230 end recheckDefDef
12271231
1228- /** If val or def definition with inferred (result) type is visible
1229- * in other compilation units, check that the actual inferred type
1230- * conforms to the expected type where all inferred capture sets are dropped.
1231- * This ensures that if files compile separately, they will also compile
1232- * in a joint compilation.
1232+ /** Two tests for member definitions with inferred types:
1233+ *
1234+ * 1. If val or def definition with inferred (result) type is visible
1235+ * in other compilation units, check that the actual inferred type
1236+ * conforms to the expected type where all inferred capture sets are dropped.
1237+ * This ensures that if files compile separately, they will also compile
1238+ * in a joint compilation.
1239+ * 2. If a val has an inferred type with a terminal capability in its span capset,
1240+ * check that it this capability is subsumed by the capset that was inferred
1241+ * for the class from its other fields via `captureSetImpliedByFields`.
1242+ * That capset is defined to take into account all fields but is computed
1243+ * only from fields with explicitly given types in order to avoid cycles.
1244+ * See comment on Setup.fieldsWithExplicitTypes. So we have to make sure
1245+ * that fields with inferred types would not change that capset.
12331246 */
12341247 def checkInferredResult (tp : Type , tree : ValOrDefDef )(using Context ): Type =
12351248 val sym = tree.symbol
@@ -1264,11 +1277,17 @@ class CheckCaptures extends Recheck, SymTransformer:
12641277 |The new inferred type $tp
12651278 |must conform to this type. """
12661279
1280+ def covers (classCapset : CaptureSet , fieldClassifiers : List [ClassSymbol ]): Boolean =
1281+ fieldClassifiers.forall: cls =>
1282+ classCapset.elems.exists:
1283+ case fresh : FreshCap => cls.isSubClass(fresh.hiddenSet.classifier)
1284+ case _ => false
1285+
12671286 tree.tpt match
1268- case tpt : InferredTypeTree if ! isExemptFromChecks =>
1269- if ! sym.isLocalToCompilationUnit
1270- // Symbols that can't be seen outside the compilation unit can have inferred types
1271- // except for the else clause below.
1287+ case tpt : InferredTypeTree =>
1288+ // Test point (1) of doc comment above
1289+ if ! sym.isLocalToCompilationUnit && ! isExemptFromChecks
1290+ // Symbols that can't be seen outside the compilation unit can have inferred types
12721291 then
12731292 val expected = tpt.tpe.dropAllRetains
12741293 todoAtPostCheck += { () =>
@@ -1277,18 +1296,21 @@ class CheckCaptures extends Recheck, SymTransformer:
12771296 // The check that inferred <: expected is done after recheck so that it
12781297 // does not interfere with normal rechecking by constraining capture set variables.
12791298 }
1280- else if sym.is(Private )
1281- && ! sym.isLocalToCompilationUnitIgnoringPrivate
1282- && tree.tpt.nuType.spanCaptureSet.containsTerminalCapability
1299+ // Test point (2) of doc comment above
1300+ if sym.owner.isClass && ! sym.owner.isStaticOwner
12831301 && contributesFreshToClass(sym)
1284- // Private symbols capturing a root capability need explicit types
1285- // so that we can compute field constributions to class instance
1286- // capture sets across compilation units.
12871302 then
1288- report.error(
1289- em """ $sym needs an explicit type because it captures a root capability in its type ${tree.tpt.nuType}.
1290- |Fields of publicily accessible classes that capture a root capability need to be given an explicit type. """ ,
1291- tpt.srcPos)
1303+ todoAtPostCheck += { () =>
1304+ val cls = sym.owner.asClass
1305+ val fieldClassifiers = classifiersOfFreshInType(sym)
1306+ val classCapset = captureSetImpliedByFields(cls, cls.appliedRef)
1307+ if ! covers(classCapset, fieldClassifiers) then
1308+ report.error(
1309+ em """ $sym needs an explicit type because it captures a root capability in its type ${tree.tpt.nuType}.
1310+ |Fields capturing a root capability need to be given an explicit type unless the capability is already
1311+ |subsumed by the computed capability of the enclosing class. """ ,
1312+ tpt.srcPos)
1313+ }
12921314 case _ =>
12931315 tp
12941316 end checkInferredResult
0 commit comments