Skip to content

Commit 09ea77e

Browse files
authored
A more robust scheme for resetting denotations after Recheck (#18534)
The new scheme works also for arbitrary denotation changes in PreRecheck. Furthermore, recheck denot transformers are not run after Recheck has ended. This means that effectively only symbols touched by the Rechecker are transformed and reset again afterwards.
2 parents ac5fcfb + ddc4eb8 commit 09ea77e

File tree

9 files changed

+70
-101
lines changed

9 files changed

+70
-101
lines changed

compiler/src/dotty/tools/dotc/Run.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,10 +168,10 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
168168
*/
169169
var pureFunsImportEncountered = false
170170

171-
/** Will be set to true if any of the compiled compilation units contains
172-
* a captureChecking language import.
171+
/** Will be set to true if experimental.captureChecking is enabled
172+
* or any of the compiled compilation units contains a captureChecking language import.
173173
*/
174-
var ccImportEncountered = false
174+
var ccEnabledSomewhere = Feature.enabledBySetting(Feature.captureChecking)(using ictx)
175175

176176
private var myEnrichedErrorMessage = false
177177

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

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -29,19 +29,22 @@ object CheckCaptures:
2929

3030
class Pre extends PreRecheck, SymTransformer:
3131

32-
override def isEnabled(using Context) = true
32+
override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere
3333

3434
/** - Reset `private` flags of parameter accessors so that we can refine them
3535
* in Setup if they have non-empty capture sets.
3636
* - Special handling of some symbols defined for case classes.
37+
* Enabled only until recheck is finished, and provided some compilation unit
38+
* is CC-enabled.
3739
*/
3840
def transformSym(sym: SymDenotation)(using Context): SymDenotation =
39-
if sym.isAllOf(PrivateParamAccessor) && !sym.hasAnnotation(defn.ConstructorOnlyAnnot) then
40-
sym.copySymDenotation(initFlags = sym.flags &~ Private | Recheck.ResetPrivate)
41-
else if Synthetics.needsTransform(sym) then
42-
Synthetics.transform(sym, toCC = true)
43-
else
44-
sym
41+
if !pastRecheck && Feature.ccEnabledSomewhere then
42+
if sym.isAllOf(PrivateParamAccessor) && !sym.hasAnnotation(defn.ConstructorOnlyAnnot) then
43+
sym.copySymDenotation(initFlags = sym.flags &~ Private | Recheck.ResetPrivate)
44+
else if Synthetics.needsTransform(sym) then
45+
Synthetics.transform(sym)
46+
else sym
47+
else sym
4548
end Pre
4649

4750
enum EnvKind:
@@ -190,7 +193,8 @@ class CheckCaptures extends Recheck, SymTransformer:
190193
import CheckCaptures.*
191194

192195
def phaseName: String = "cc"
193-
override def isEnabled(using Context) = true
196+
197+
override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere
194198

195199
def newRechecker()(using Context) = CaptureChecker(ctx)
196200

@@ -199,11 +203,7 @@ class CheckCaptures extends Recheck, SymTransformer:
199203
override def run(using Context): Unit =
200204
if Feature.ccEnabled then
201205
super.run
202-
203-
override def transformSym(sym: SymDenotation)(using Context): SymDenotation =
204-
if Synthetics.needsTransform(sym) then Synthetics.transform(sym, toCC = false)
205-
else super.transformSym(sym)
206-
206+
207207
override def printingContext(ctx: Context) = ctx.withProperty(ccStateKey, Some(new CCState))
208208

209209
class CaptureChecker(ictx: Context) extends Rechecker(ictx):

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,7 +306,7 @@ extends tpd.TreeTraverser:
306306

307307
/** Update info of `sym` for CheckCaptures phase only */
308308
private def updateInfo(sym: Symbol, info: Type)(using Context) =
309-
sym.updateInfoBetween(preRecheckPhase, thisPhase, info, newOwnerFor(sym))
309+
sym.updateInfo(preRecheckPhase, info, newOwnerFor(sym))
310310
sym.namedType match
311311
case ref: CaptureRef => ref.invalidateCaches()
312312
case _ =>

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

Lines changed: 19 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ object Synthetics:
6161
* @param sym The method to transform @pre needsTransform(sym) must hold.
6262
* @param toCC Whether to transform the type to capture checking or back.
6363
*/
64-
def transform(sym: SymDenotation, toCC: Boolean)(using Context): SymDenotation =
64+
def transform(sym: SymDenotation)(using Context): SymDenotation =
6565

6666
/** Add capture dependencies to the type of the `apply` or `copy` method of a case class.
6767
* An apply method in a case class like this:
@@ -92,31 +92,18 @@ object Synthetics:
9292
case _ =>
9393
info
9494

95-
/** Drop capture dependencies from the type of `apply` or `copy` method of a case class */
96-
def dropCaptureDeps(tp: Type): Type = tp match
97-
case tp: MethodOrPoly =>
98-
tp.derivedLambdaType(resType = dropCaptureDeps(tp.resType))
99-
case CapturingType(parent, _) =>
100-
dropCaptureDeps(parent)
101-
case RefinedType(parent, _, _) =>
102-
dropCaptureDeps(parent)
103-
case _ =>
104-
tp
105-
10695
/** Add capture information to the type of the default getter of a case class copy method
107-
* if toCC = true, or remove the added info again if toCC = false.
10896
*/
10997
def transformDefaultGetterCaptures(info: Type, owner: Symbol, idx: Int)(using Context): Type = info match
11098
case info: MethodOrPoly =>
11199
info.derivedLambdaType(resType = transformDefaultGetterCaptures(info.resType, owner, idx))
112100
case info: ExprType =>
113101
info.derivedExprType(transformDefaultGetterCaptures(info.resType, owner, idx))
114102
case EventuallyCapturingType(parent, _) =>
115-
if toCC then transformDefaultGetterCaptures(parent, owner, idx)
116-
else parent
103+
transformDefaultGetterCaptures(parent, owner, idx)
117104
case info @ AnnotatedType(parent, annot) =>
118105
info.derivedAnnotatedType(transformDefaultGetterCaptures(parent, owner, idx), annot)
119-
case _ if toCC && idx < owner.asClass.paramGetters.length =>
106+
case _ if idx < owner.asClass.paramGetters.length =>
120107
val param = owner.asClass.paramGetters(idx)
121108
val pinfo = param.info
122109
atPhase(ctx.phase.next) {
@@ -126,49 +113,29 @@ object Synthetics:
126113
case _ =>
127114
info
128115

129-
/** Augment an unapply of type `(x: C): D` to `(x: C^{cap}): D^{x}` if toCC is true,
130-
* or remove the added capture sets again if toCC = false.
131-
*/
116+
/** Augment an unapply of type `(x: C): D` to `(x: C^{cap}): D^{x}` */
132117
def transformUnapplyCaptures(info: Type)(using Context): Type = info match
133118
case info: MethodType =>
134-
if toCC then
135-
val paramInfo :: Nil = info.paramInfos: @unchecked
136-
val newParamInfo = CapturingType(paramInfo, CaptureSet.universal)
137-
val trackedParam = info.paramRefs.head
138-
def newResult(tp: Type): Type = tp match
139-
case tp: MethodOrPoly =>
140-
tp.derivedLambdaType(resType = newResult(tp.resType))
141-
case _ =>
142-
CapturingType(tp, CaptureSet(trackedParam))
143-
info.derivedLambdaType(paramInfos = newParamInfo :: Nil, resType = newResult(info.resType))
144-
.showing(i"augment unapply type $info to $result", capt)
145-
else info.paramInfos match
146-
case CapturingType(oldParamInfo, _) :: Nil =>
147-
def oldResult(tp: Type): Type = tp match
148-
case tp: MethodOrPoly =>
149-
tp.derivedLambdaType(resType = oldResult(tp.resType))
150-
case CapturingType(tp, _) =>
151-
tp
152-
info.derivedLambdaType(paramInfos = oldParamInfo :: Nil, resType = oldResult(info.resType))
119+
val paramInfo :: Nil = info.paramInfos: @unchecked
120+
val newParamInfo = CapturingType(paramInfo, CaptureSet.universal)
121+
val trackedParam = info.paramRefs.head
122+
def newResult(tp: Type): Type = tp match
123+
case tp: MethodOrPoly =>
124+
tp.derivedLambdaType(resType = newResult(tp.resType))
153125
case _ =>
154-
info
126+
CapturingType(tp, CaptureSet(trackedParam))
127+
info.derivedLambdaType(paramInfos = newParamInfo :: Nil, resType = newResult(info.resType))
128+
.showing(i"augment unapply type $info to $result", capt)
155129
case info: PolyType =>
156130
info.derivedLambdaType(resType = transformUnapplyCaptures(info.resType))
157131

158132
def transformComposeCaptures(symd: SymDenotation) =
159133
val (pt: PolyType) = symd.info: @unchecked
160134
val (mt: MethodType) = pt.resType: @unchecked
161135
val (enclThis: ThisType) = symd.owner.thisType: @unchecked
162-
val mt1 =
163-
if toCC then
164-
MethodType(mt.paramNames)(
165-
mt1 => mt.paramInfos.map(_.capturing(CaptureSet.universal)),
166-
mt1 => CapturingType(mt.resType, CaptureSet(enclThis, mt1.paramRefs.head)))
167-
else
168-
MethodType(mt.paramNames)(
169-
mt1 => mt.paramInfos.map(_.stripCapturing),
170-
mt1 => mt.resType.stripCapturing)
171-
pt.derivedLambdaType(resType = mt1)
136+
pt.derivedLambdaType(resType = MethodType(mt.paramNames)(
137+
mt1 => mt.paramInfos.map(_.capturing(CaptureSet.universal)),
138+
mt1 => CapturingType(mt.resType, CaptureSet(enclThis, mt1.paramRefs.head))))
172139

173140
def transformCurriedTupledCaptures(symd: SymDenotation) =
174141
val (et: ExprType) = symd.info: @unchecked
@@ -179,26 +146,18 @@ object Synthetics:
179146
defn.FunctionOf(args, mapFinalResult(res, f), isContextual)
180147
else
181148
f(tp)
182-
val resType1 =
183-
if toCC then
184-
mapFinalResult(et.resType, CapturingType(_, CaptureSet(enclThis)))
185-
else
186-
et.resType.stripCapturing
187-
ExprType(resType1)
149+
ExprType(mapFinalResult(et.resType, CapturingType(_, CaptureSet(enclThis))))
188150

189151
def transformCompareCaptures =
190-
if toCC then
191-
MethodType(defn.ObjectType.capturing(CaptureSet.universal) :: Nil, defn.BooleanType)
192-
else
193-
defn.methOfAnyRef(defn.BooleanType)
152+
MethodType(defn.ObjectType.capturing(CaptureSet.universal) :: Nil, defn.BooleanType)
194153

195154
sym.copySymDenotation(info = sym.name match
196155
case DefaultGetterName(nme.copy, n) =>
197156
transformDefaultGetterCaptures(sym.info, sym.owner, n)
198157
case nme.unapply =>
199158
transformUnapplyCaptures(sym.info)
200159
case nme.apply | nme.copy =>
201-
if toCC then addCaptureDeps(sym.info) else dropCaptureDeps(sym.info)
160+
addCaptureDeps(sym.info)
202161
case nme.andThen | nme.compose =>
203162
transformComposeCaptures(sym)
204163
case nme.curried | nme.tupled =>

compiler/src/dotty/tools/dotc/config/Feature.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,8 @@ object Feature:
103103

104104
/** Is captureChecking enabled for any of the currently compiled compilation units? */
105105
def ccEnabledSomewhere(using Context) =
106-
enabledBySetting(captureChecking)
107-
|| ctx.run != null && ctx.run.nn.ccImportEncountered
106+
if ctx.run != null then ctx.run.nn.ccEnabledSomewhere
107+
else enabledBySetting(captureChecking)
108108

109109
def sourceVersionSetting(using Context): SourceVersion =
110110
SourceVersion.valueOf(ctx.settings.source.value)
@@ -174,7 +174,7 @@ object Feature:
174174
true
175175
else if fullFeatureName == captureChecking then
176176
ctx.compilationUnit.needsCaptureChecking = true
177-
if ctx.run != null then ctx.run.nn.ccImportEncountered = true
177+
if ctx.run != null then ctx.run.nn.ccEnabledSomewhere = true
178178
true
179179
else
180180
false

compiler/src/dotty/tools/dotc/core/Phases.scala

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -299,13 +299,24 @@ object Phases {
299299
*/
300300
def phaseName: String
301301

302+
/** This property is queried when phases are first assembled.
303+
* If it is false, the phase will be dropped from the set of phases to traverse.
304+
*/
305+
def isEnabled(using Context): Boolean = true
306+
307+
/** This property is queried before a phase is run.
308+
* If it is false, the phase is skipped.
309+
*/
302310
def isRunnable(using Context): Boolean =
303311
!ctx.reporter.hasErrors
304312
// TODO: This might test an unintended condition.
305313
// To find out whether any errors have been reported during this
306314
// run one calls `errorsReported`, not `hasErrors`.
307315
// But maybe changing this would prevent useful phases from running?
308316

317+
/** True for all phases except NoPhase */
318+
def exists: Boolean = true
319+
309320
/** If set, allow missing or superfluous arguments in applications
310321
* and type applications.
311322
*/
@@ -360,10 +371,6 @@ object Phases {
360371
/** Can this transform change the base types of a type? */
361372
def changesBaseTypes: Boolean = changesParents
362373

363-
def isEnabled(using Context): Boolean = true
364-
365-
def exists: Boolean = true
366-
367374
def initContext(ctx: FreshContext): Unit = ()
368375

369376
/** A hook that allows to transform the usual context passed to the function

compiler/src/dotty/tools/dotc/core/TypeErrors.scala

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,10 @@ abstract class TypeError(using creationContext: Context) extends Exception(""):
2222
* This is expensive and only useful for debugging purposes.
2323
*/
2424
def computeStackTrace: Boolean =
25-
ctx.debug || (cyclicErrors != noPrinter && this.isInstanceOf[CyclicReference] && !(ctx.mode is Mode.CheckCyclic))
25+
ctx.debug
26+
|| (cyclicErrors != noPrinter && this.isInstanceOf[CyclicReference] && !(ctx.mode is Mode.CheckCyclic))
27+
|| ctx.settings.YdebugTypeError.value
28+
|| ctx.settings.YdebugError.value
2629

2730
override def fillInStackTrace(): Throwable =
2831
if computeStackTrace then super.fillInStackTrace().nn

compiler/src/dotty/tools/dotc/transform/PreRecheck.scala

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ abstract class PreRecheck extends Phase, DenotTransformer:
1414

1515
override def changesBaseTypes: Boolean = true
1616

17+
var pastRecheck = false
18+
1719
def run(using Context): Unit = ()
1820

1921
override def isCheckable = false

compiler/src/dotty/tools/dotc/transform/Recheck.scala

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,26 +48,19 @@ object Recheck:
4848

4949
extension (sym: Symbol)
5050

51-
/** Update symbol's info to newInfo from prevPhase.next to lastPhase.
51+
/** Update symbol's info to newInfo after `prevPhase`.
5252
* Also update owner to newOwnerOrNull if it is not null.
53-
* Reset to previous info and owner for phases after lastPhase.
53+
* The update is valid until after Recheck. After that the symbol's denotation
54+
* is reset to what it was before PreRecheck.
5455
*/
55-
def updateInfoBetween(prevPhase: DenotTransformer, lastPhase: DenotTransformer, newInfo: Type, newOwnerOrNull: Symbol | Null = null)(using Context): Unit =
56+
def updateInfo(prevPhase: DenotTransformer, newInfo: Type, newOwnerOrNull: Symbol | Null = null)(using Context): Unit =
5657
val newOwner = if newOwnerOrNull == null then sym.owner else newOwnerOrNull
5758
if (sym.info ne newInfo) || (sym.owner ne newOwner) then
5859
val flags = sym.flags
59-
sym.copySymDenotation(
60-
initFlags =
61-
if flags.isAllOf(ResetPrivateParamAccessor)
62-
then flags &~ ResetPrivate | Private
63-
else flags
64-
).installAfter(lastPhase) // reset
6560
sym.copySymDenotation(
6661
owner = newOwner,
6762
info = newInfo,
68-
initFlags =
69-
if newInfo.isInstanceOf[LazyType] then flags &~ Touched
70-
else flags
63+
initFlags = if newInfo.isInstanceOf[LazyType] then flags &~ Touched else flags
7164
).installAfter(prevPhase)
7265

7366
/** Does symbol have a new denotation valid from phase.next that is different
@@ -158,16 +151,21 @@ abstract class Recheck extends Phase, SymTransformer:
158151
// One failing test is pos/i583a.scala
159152

160153
/** Change any `ResetPrivate` flags back to `Private` */
161-
def transformSym(sym: SymDenotation)(using Context): SymDenotation =
162-
if sym.isAllOf(Recheck.ResetPrivateParamAccessor) then
163-
sym.copySymDenotation(initFlags = sym.flags &~ Recheck.ResetPrivate | Private)
164-
else sym
154+
def transformSym(symd: SymDenotation)(using Context): SymDenotation =
155+
val sym = symd.symbol
156+
if sym.isUpdatedAfter(preRecheckPhase)
157+
then atPhase(preRecheckPhase)(sym.denot.copySymDenotation())
158+
else symd
165159

166160
def run(using Context): Unit =
167161
val rechecker = newRechecker()
168162
rechecker.checkUnit(ctx.compilationUnit)
169163
rechecker.reset()
170164

165+
override def runOn(units: List[CompilationUnit])(using runCtx: Context): List[CompilationUnit] =
166+
try super.runOn(units)
167+
finally preRecheckPhase.pastRecheck = true
168+
171169
def newRechecker()(using Context): Rechecker
172170

173171
/** The typechecker pass */

0 commit comments

Comments
 (0)