Skip to content

Commit 5e54465

Browse files
committed
Don't prematurely force info of currently defined fields with inferred types
Don't prematurely force info of currently defined fields with inferred types when computing captureSetImpliedByFields. Fixes #24335
1 parent 5a2cff6 commit 5e54465

File tree

3 files changed

+98
-54
lines changed

3 files changed

+98
-54
lines changed

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -948,7 +948,7 @@ class CheckCaptures extends Recheck, SymTransformer:
948948
case cls: ClassSymbol =>
949949
var fieldClassifiers =
950950
for
951-
sym <- cls.info.decls.toList
951+
sym <- setup.fieldsWithExplicitTypes.getOrElse(cls, cls.info.decls.toList)
952952
if contributesFreshToClass(sym)
953953
case fresh: FreshCap <- sym.info.spanCaptureSet.elems
954954
.filter(_.isTerminalCapability)

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

Lines changed: 88 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,29 @@ trait SetupAPI:
4040
/** Check to do after the capture checking traversal */
4141
def postCheck()(using Context): Unit
4242

43+
/** A map from currently compiled class symbols to those of their fields
44+
* that have an explicit type given. Used in `captureSetImpliedByFields`
45+
* to avoid forcing fields with inferred types prematurely. The test file
46+
* where this matters is i24335.scala. The precise failure scenario which
47+
* this avoids is described in #24335.
48+
*/
49+
def fieldsWithExplicitTypes: collection.Map[ClassSymbol, List[Symbol]]
50+
51+
/** Used for error reporting:
52+
* Maps mutable variables to the symbols that capture them (in the
53+
* CheckCaptures sense, i.e. symbol is referred to from a different method
54+
* than the one it is defined in).
55+
*/
56+
def capturedBy: collection.Map[Symbol, Symbol]
57+
58+
/** Used for error reporting:
59+
* Maps anonymous functions appearing as function arguments to
60+
* the function that is called.
61+
*/
62+
def anonFunCallee: collection.Map[Symbol, Symbol]
63+
64+
end SetupAPI
65+
4366
object Setup:
4467

4568
val name: String = "setupCC"
@@ -475,6 +498,12 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
475498
extension (sym: Symbol) def nextInfo(using Context): Type =
476499
atPhase(thisPhase.next)(sym.info)
477500

501+
val fieldsWithExplicitTypes: mutable.HashMap[ClassSymbol, List[Symbol]] = mutable.HashMap()
502+
503+
val capturedBy: mutable.HashMap[Symbol, Symbol] = mutable.HashMap()
504+
505+
val anonFunCallee: mutable.HashMap[Symbol, Symbol] = mutable.HashMap()
506+
478507
/** A traverser that adds knownTypes and updates symbol infos */
479508
def setupTraverser(checker: CheckerAPI) = new TreeTraverserWithPreciseImportContexts:
480509
import checker.*
@@ -664,59 +693,65 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
664693
case tree: Bind =>
665694
val sym = tree.symbol
666695
updateInfo(sym, transformInferredType(sym.info), sym.owner)
667-
case tree: TypeDef =>
668-
tree.symbol match
669-
case cls: ClassSymbol =>
670-
checkClassifiedInheritance(cls)
671-
val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo
672-
673-
// Compute new self type
674-
def isInnerModule = cls.is(ModuleClass) && !cls.isStatic
675-
val selfInfo1 =
676-
if (selfInfo ne NoType) && !isInnerModule then
677-
// if selfInfo is explicitly given then use that one, except if
678-
// self info applies to non-static modules, these still need to be inferred
679-
selfInfo
680-
else if cls.isPureClass then
681-
// is cls is known to be pure, nothing needs to be added to self type
682-
selfInfo
683-
else if !cls.isEffectivelySealed && !cls.baseClassHasExplicitNonUniversalSelfType then
684-
// assume {cap} for completely unconstrained self types of publicly extensible classes
685-
CapturingType(cinfo.selfType, CaptureSet.universal)
686-
else
687-
// Infer the self type for the rest, which is all classes without explicit
688-
// self types (to which we also add nested module classes), provided they are
689-
// neither pure, nor are publicily extensible with an unconstrained self type.
690-
val cs = CaptureSet.ProperVar(cls, CaptureSet.emptyRefs, nestedOK = false, isRefining = false)
691-
if cls.derivesFrom(defn.Caps_Capability) then
692-
// If cls is a capability class, we need to add a fresh capability to ensure
693-
// we cannot treat the class as pure.
694-
CaptureSet.fresh(cls, cls.thisType, Origin.InDecl(cls)).subCaptures(cs)
695-
CapturingType(cinfo.selfType, cs)
696-
697-
// Compute new parent types
698-
val ps1 = inContext(ctx.withOwner(cls)):
699-
ps.mapConserve(transformExplicitType(_, NoSymbol, freshen = false))
700-
701-
// Install new types and if it is a module class also update module object
702-
if (selfInfo1 ne selfInfo) || (ps1 ne ps) then
703-
val newInfo = ClassInfo(prefix, cls, ps1, decls, selfInfo1)
704-
updateInfo(cls, newInfo, cls.owner)
705-
capt.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo")
706-
cls.thisType.asInstanceOf[ThisType].invalidateCaches()
707-
if cls.is(ModuleClass) then
708-
// if it's a module, the capture set of the module reference is the capture set of the self type
709-
val modul = cls.sourceModule
710-
val selfCaptures = selfInfo1 match
711-
case CapturingType(_, refs) => refs
712-
case _ => CaptureSet.empty
713-
// Note: Can't do val selfCaptures = selfInfo1.captureSet here.
714-
// This would potentially give stackoverflows when setup is run repeatedly.
715-
// One test case is pos-custom-args/captures/checkbounds.scala under
716-
// ccConfig.alwaysRepeatRun = true.
717-
updateInfo(modul, CapturingType(modul.info, selfCaptures), modul.owner)
718-
modul.termRef.invalidateCaches()
719-
case _ =>
696+
case tree @ TypeDef(_, impl: Template) =>
697+
val cls: ClassSymbol = tree.symbol.asClass
698+
699+
fieldsWithExplicitTypes(cls) =
700+
for
701+
case vd @ ValDef(_, tpt: TypeTree, _) <- impl.body
702+
if !tpt.isInferred && vd.symbol.exists && !vd.symbol.is(NonMember)
703+
yield
704+
vd.symbol
705+
706+
checkClassifiedInheritance(cls)
707+
val cinfo @ ClassInfo(prefix, _, ps, decls, selfInfo) = cls.classInfo
708+
709+
// Compute new self type
710+
def isInnerModule = cls.is(ModuleClass) && !cls.isStatic
711+
val selfInfo1 =
712+
if (selfInfo ne NoType) && !isInnerModule then
713+
// if selfInfo is explicitly given then use that one, except if
714+
// self info applies to non-static modules, these still need to be inferred
715+
selfInfo
716+
else if cls.isPureClass then
717+
// is cls is known to be pure, nothing needs to be added to self type
718+
selfInfo
719+
else if !cls.isEffectivelySealed && !cls.baseClassHasExplicitNonUniversalSelfType then
720+
// assume {cap} for completely unconstrained self types of publicly extensible classes
721+
CapturingType(cinfo.selfType, CaptureSet.universal)
722+
else
723+
// Infer the self type for the rest, which is all classes without explicit
724+
// self types (to which we also add nested module classes), provided they are
725+
// neither pure, nor are publicily extensible with an unconstrained self type.
726+
val cs = CaptureSet.ProperVar(cls, CaptureSet.emptyRefs, nestedOK = false, isRefining = false)
727+
if cls.derivesFrom(defn.Caps_Capability) then
728+
// If cls is a capability class, we need to add a fresh capability to ensure
729+
// we cannot treat the class as pure.
730+
CaptureSet.fresh(cls, cls.thisType, Origin.InDecl(cls)).subCaptures(cs)
731+
CapturingType(cinfo.selfType, cs)
732+
733+
// Compute new parent types
734+
val ps1 = inContext(ctx.withOwner(cls)):
735+
ps.mapConserve(transformExplicitType(_, NoSymbol, freshen = false))
736+
737+
// Install new types and if it is a module class also update module object
738+
if (selfInfo1 ne selfInfo) || (ps1 ne ps) then
739+
val newInfo = ClassInfo(prefix, cls, ps1, decls, selfInfo1)
740+
updateInfo(cls, newInfo, cls.owner)
741+
capt.println(i"update class info of $cls with parents $ps selfinfo $selfInfo to $newInfo")
742+
cls.thisType.asInstanceOf[ThisType].invalidateCaches()
743+
if cls.is(ModuleClass) then
744+
// if it's a module, the capture set of the module reference is the capture set of the self type
745+
val modul = cls.sourceModule
746+
val selfCaptures = selfInfo1 match
747+
case CapturingType(_, refs) => refs
748+
case _ => CaptureSet.empty
749+
// Note: Can't do val selfCaptures = selfInfo1.captureSet here.
750+
// This would potentially give stackoverflows when setup is run repeatedly.
751+
// One test case is pos-custom-args/captures/checkbounds.scala under
752+
// ccConfig.alwaysRepeatRun = true.
753+
updateInfo(modul, CapturingType(modul.info, selfCaptures), modul.owner)
754+
modul.termRef.invalidateCaches()
720755
case _ =>
721756
end postProcess
722757

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/i24335.scala:7:22 ----------------------------------------
2+
7 | val _: () -> Unit = l1 // error
3+
| ^^
4+
| Found: (C.this.l1 : () ->{C.this.c.io} Unit)
5+
| Required: () -> Unit
6+
|
7+
| Note that capability C.this.c.io is not included in capture set {}.
8+
|
9+
| longer explanation available when compiling with `-explain`

0 commit comments

Comments
 (0)