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