Skip to content

Commit 2ea222a

Browse files
committed
Use a separate phase to add try owners
I tried several other strategies, but nothing worked. - update after Setup: This means we cannot to levelOwner during setup. - running levelOwner after Setup causes CyclicErrors when transforming symbols in Setup - having a seperate notion of ccOwner maintained in a CCState table does not work since there are too many hidden uses of owner everywhere that would ignore that table.
1 parent ab6ba74 commit 2ea222a

File tree

8 files changed

+88
-59
lines changed

8 files changed

+88
-59
lines changed

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class Compiler {
8282
new PatternMatcher) :: // Compile pattern matches
8383
List(new TestRecheck.Pre) :: // Test only: run rechecker, enabled under -Yrecheck-test
8484
List(new TestRecheck) :: // Test only: run rechecker, enabled under -Yrecheck-test
85+
List(new cc.AddTryOwners) :: // Add symbols as owners of try blocks, enabled under captureChecking
8586
List(new cc.Setup) :: // Preparations for check captures phase, enabled under captureChecking
8687
List(new cc.CheckCaptures) :: // Check captures, enabled under captureChecking
8788
List(new ElimOpaque, // Turn opaque into normal aliases

compiler/src/dotty/tools/dotc/ast/tpd.scala

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,21 @@ object tpd extends Trees.Instance[Type] with TypedTreeInfo {
12841284
!(sym.is(Method) && sym.info.isInstanceOf[MethodOrPoly]) // if is a method it is parameterless
12851285
}
12861286

1287+
/** A tree traverser that generates the the same import contexts as original typer for statements.
1288+
* TODO: Should we align TreeMapWithPreciseStatContexts and also keep track of exprOwners?
1289+
*/
1290+
abstract class TreeTraverserWithPreciseImportContexts extends TreeTraverser:
1291+
override def apply(x: Unit, trees: List[Tree])(using Context): Unit =
1292+
def recur(trees: List[Tree]): Unit = trees match
1293+
case (imp: Import) :: rest =>
1294+
traverse(rest)(using ctx.importContext(imp, imp.symbol))
1295+
case tree :: rest =>
1296+
traverse(tree)
1297+
traverse(rest)
1298+
case Nil =>
1299+
recur(trees)
1300+
end TreeTraverserWithPreciseImportContexts
1301+
12871302
extension (xs: List[tpd.Tree])
12881303
def tpes: List[Type] = xs match {
12891304
case x :: xs1 => x.tpe :: xs1.tpes
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package dotty.tools.dotc
2+
package cc
3+
4+
import core.*
5+
import DenotTransformers.IdentityDenotTransformer
6+
import Phases.Phase
7+
import Contexts.*, Symbols.*, Flags.*, Types.*
8+
import config.Feature
9+
import ast.tpd
10+
import StdNames.nme
11+
import Decorators.i
12+
13+
object AddTryOwners:
14+
val name: String = "addTryOwners"
15+
val description: String = "add symbols for try blocks in preparation of capture checking"
16+
17+
class AddTryOwners extends Phase, IdentityDenotTransformer:
18+
thisPhase =>
19+
20+
import tpd.*
21+
22+
override def phaseName: String = AddTryOwners.name
23+
override def description: String = AddTryOwners.description
24+
25+
override def isRunnable(using Context) =
26+
super.isRunnable && Feature.ccEnabledSomewhere
27+
28+
override def isCheckable = false
29+
30+
def run(using Context): Unit =
31+
val addTryOwners = new TreeTraverserWithPreciseImportContexts:
32+
def traverse(tree: Tree)(using Context): Unit = tree match
33+
case tree @ Try(expr, cases, finalizer) if Feature.enabled(Feature.saferExceptions) =>
34+
val tryOwner = newSymbol(ctx.owner, nme.TRY_BLOCK, SyntheticMethod, MethodType(Nil, defn.UnitType))
35+
ccState.tryBlockOwner(tree) = tryOwner
36+
expr.changeOwnerAfter(ctx.owner, tryOwner, thisPhase)
37+
inContext(ctx.withOwner(tryOwner)):
38+
traverse(expr)
39+
traverse(cases)
40+
traverse(finalizer)
41+
case _ =>
42+
traverseChildren(tree)
43+
addTryOwners.traverse(ctx.compilationUnit.tpdTree)
44+
end AddTryOwners
45+

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

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -380,18 +380,18 @@ extension (sym: Symbol)
380380
* - _root_
381381
*/
382382
def levelOwner(using Context): Symbol =
383-
def recur(sym: Symbol)(using Context): Symbol =
383+
def recur(sym: Symbol): Symbol =
384384
if !sym.exists || sym.isRoot || sym.isStaticOwner then defn.RootClass
385385
else if sym.isLevelOwner then sym
386386
else recur(sym.owner)
387-
recur(sym)(using ctx.withPhase(Phases.checkCapturesPhase))
387+
recur(sym)
388388

389389
/** The level owner enclosing `sym` which has the given name, or NoSymbol if none exists.
390390
* If name refers to a val that has a closure as rhs, we return the closure as level
391391
* owner.
392392
*/
393393
def levelOwnerNamed(name: String)(using Context): Symbol =
394-
def recur(sym: Symbol, prev: Symbol)(using Context): Symbol =
394+
def recur(sym: Symbol, prev: Symbol): Symbol =
395395
if sym.name.toString == name then
396396
if sym.isLevelOwner then sym
397397
else if sym.isTerm && !sym.isOneOf(Method | Module) && prev.exists then prev
@@ -401,7 +401,7 @@ extension (sym: Symbol)
401401
else
402402
val prev1 = if sym.isAnonymousFunction && sym.isLevelOwner then sym else NoSymbol
403403
recur(sym.owner, prev1)
404-
recur(sym, NoSymbol)(using ctx.withPhase(Phases.checkCapturesPhase))
404+
recur(sym, NoSymbol)
405405
.showing(i"find outer $sym [ $name ] = $result", capt)
406406

407407
/** The nesting level of `sym` for the purposes of `cc`,

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ class CheckCaptures extends Recheck, SymTransformer:
182182
def phaseName: String = "cc"
183183

184184
override def isRunnable(using Context) = super.isRunnable && Feature.ccEnabledSomewhere
185+
override def firstPrepPhase = preRecheckPhase.prev.asInstanceOf[AddTryOwners]
185186

186187
def newRechecker()(using Context) = CaptureChecker(ctx)
187188

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

Lines changed: 5 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -402,31 +402,17 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
402402
def inverse = thisMap
403403
end SubstParams
404404

405-
/** If the outer context directly enclosing the definition of `sym`
406-
* has a <try block> owner, that owner, otherwise null.
407-
*/
408-
def newOwnerFor(sym: Symbol)(using Context): Symbol =
409-
var octx = ctx
410-
while octx.owner == sym do octx = octx.outer
411-
if octx.owner.name == nme.TRY_BLOCK then octx.owner else sym.maybeOwner
412-
413405
/** Update info of `sym` for CheckCaptures phase only */
414406
private def updateInfo(sym: Symbol, info: Type)(using Context) =
415-
sym.updateInfo(thisPhase, info, newFlagsFor(sym), newOwnerFor(sym))
407+
sym.updateInfo(thisPhase, info, newFlagsFor(sym))
416408
sym.namedType match
417409
case ref: CaptureRef => ref.invalidateCaches() // TODO: needed?
418410
case _ =>
419411

420-
/** Update the owner of `sym` to `newOwnerFor(sym)`.
421-
* This can happen because of inserted try block owners.
422-
*/
423-
private def updateOwner(sym: Symbol)(using Context) =
424-
updateInfo(sym, sym.info)
425-
426412
extension (sym: Symbol) def nextInfo(using Context): Type =
427413
atPhase(thisPhase.next)(sym.info)
428414

429-
def setupTraverser(recheckDef: DefRecheck) = new TreeTraverser:
415+
def setupTraverser(recheckDef: DefRecheck) = new TreeTraverserWithPreciseImportContexts:
430416

431417
def traverse(tree: Tree)(using Context): Unit =
432418
tree match
@@ -435,7 +421,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
435421
if isExcluded(meth) then
436422
return
437423

438-
updateOwner(meth)
439424
inContext(ctx.withOwner(meth)):
440425
paramss.foreach(traverse)
441426
transformTT(tpt, boxed = false,
@@ -456,12 +441,8 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
456441
case _ => false
457442

458443
val sym = tree.symbol
459-
val newOwner =
460-
if sym.isOneOf(TermParamOrAccessor) then ctx.owner
461-
else
462-
updateOwner(sym)
463-
sym
464-
inContext(ctx.withOwner(newOwner)):
444+
val defCtx = if sym.isOneOf(TermParamOrAccessor) then ctx else ctx.withOwner(sym)
445+
inContext(defCtx):
465446
val rootsNeedMap =
466447
// need to run before cc so that rhsClosure is defined without transforming
467448
// symbols to cc, since rhsClosure is used in that transformation.
@@ -493,32 +474,14 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
493474
transformTT(arg, boxed = true, exact = false, rootTarget = ctx.owner) // type arguments in type applications are boxed
494475

495476
case tree: TypeDef if tree.symbol.isClass =>
496-
val cls = tree.symbol
497-
updateOwner(cls)
498-
if cls.is(ModuleClass) then updateOwner(cls.sourceModule)
499-
inContext(ctx.withOwner(cls)):
500-
traverseChildren(tree)
501-
502-
case tree: Try if Feature.enabled(Feature.saferExceptions) =>
503-
val tryOwner = newSymbol(ctx.owner, nme.TRY_BLOCK, SyntheticMethod, MethodType(Nil, defn.UnitType))
504-
ccState.isLevelOwner(tryOwner) = true
505-
ccState.tryBlockOwner(tree) = tryOwner
506-
inContext(ctx.withOwner(tryOwner)):
477+
inContext(ctx.withOwner(tree.symbol)):
507478
traverseChildren(tree)
508479

509480
case _ =>
510481
traverseChildren(tree)
511482
postProcess(tree)
512483
end traverse
513484

514-
override def apply(x: Unit, trees: List[Tree])(using Context): Unit = trees match
515-
case (imp: Import) :: rest =>
516-
traverse(rest)(using ctx.importContext(imp, imp.symbol))
517-
case tree :: rest =>
518-
traverse(tree)
519-
traverse(rest)
520-
case Nil =>
521-
522485
extension (sym: Symbol)(using Context) def unlessStatic =
523486
val owner = sym.levelOwner
524487
if sym.isStaticOwner then NoSymbol else owner
@@ -644,9 +607,6 @@ class Setup extends PreRecheck, SymTransformer, SetupAPI:
644607
val modul = cls.sourceModule
645608
updateInfo(modul, CapturingType(modul.info, newSelfType.captureSet))
646609
modul.termRef.invalidateCaches()
647-
else
648-
updateOwner(cls)
649-
if cls.is(ModuleClass) then updateOwner(cls.sourceModule)
650610
case _ =>
651611
val info = tree.symbol.info
652612
val newInfo = transformExplicitType(info, rootTarget = ctx.owner.unlessStatic)

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,18 +138,27 @@ abstract class Recheck extends Phase, SymTransformer:
138138
import ast.tpd.*
139139
import Recheck.*
140140

141+
/** The phase before rechecking, used to setup symbol infos. */
141142
def preRecheckPhase = this.prev.asInstanceOf[PreRecheck]
142143

144+
/** The first phase that pepares for rechecking. This is usually preRecheckPhase
145+
* but could also be before. Updated symbols will snap back to their
146+
* denotations at firestPrepPhase after rechecking.
147+
*/
148+
def firstPrepPhase: Phase = preRecheckPhase
149+
143150
override def changesBaseTypes: Boolean = true
144151

145152
override def isCheckable = false
146153
// TODO: investigate what goes wrong we Ycheck directly after rechecking.
147154
// One failing test is pos/i583a.scala
148155

149-
/** Change any `ResetPrivate` flags back to `Private` */
156+
/** Change denotation back to what it was before (pre-)rechecking` */
150157
def transformSym(symd: SymDenotation)(using Context): SymDenotation =
151158
val sym = symd.symbol
152-
if sym.isUpdatedAfter(preRecheckPhase) then atPhase(preRecheckPhase)(sym.denot)
159+
def updatedAfter(p: Phase): Boolean =
160+
sym.isUpdatedAfter(p) || p != preRecheckPhase && updatedAfter(p.next)
161+
if updatedAfter(firstPrepPhase) then atPhase(firstPrepPhase)(sym.denot)
153162
else symd
154163

155164
def run(using Context): Unit =

tests/neg-custom-args/captures/real-try.check

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,11 @@
2424
| cannot be included in outer capture set ?, defined at level 1 in method test
2525
|
2626
| longer explanation available when compiling with `-explain`
27-
-- [E007] Type Mismatch Error: tests/neg-custom-args/captures/real-try.scala:31:4 --------------------------------------
27+
-- Error: tests/neg-custom-args/captures/real-try.scala:31:21 ----------------------------------------------------------
2828
31 | Cell(() => foo(1))// // error
29-
| ^^^^^^^^^^^^^^^^^^
30-
| Found: Cell[box () ->{cap[<try block>]} Unit]^?
31-
| Required: Cell[box () ->? Unit]^?
29+
| ^
30+
|(canThrow$4 : CanThrow[Ex1 | Ex2]^{cap[<try block>]}) cannot be referenced here; it is not included in the allowed capture set ?
31+
|of an enclosing function literal with expected type box () ->? Unit
3232
|
33-
| Note that reference (cap[<try block>] : caps.Cap), defined at level 2
34-
| cannot be included in outer capture set ?, defined at level 1 in method test
35-
|
36-
| longer explanation available when compiling with `-explain`
33+
|Note that reference (canThrow$4 : CanThrow[Ex1 | Ex2]^{cap[<try block>]}), defined at level 2
34+
|cannot be included in outer capture set ?, defined at level 1 in method test

0 commit comments

Comments
 (0)