Skip to content

Commit 2f32850

Browse files
authored
CC: Drop idempotent type maps (#22910)
Reorganize the code so that maps become more manageable - [x] drop the idempotent ones and apply immediately - [x] detect when immediate applications cause errors by freezing observed sets - [x] restart capture checking with augmented use sets if such errors are detected - [x] fuse successible SubstBindingMaps and Filters Hopefully this leads to a more efficient system that avoids the timeouts we have been seeing. Edit: I did see a timeout for [99f5628](99f5628). As a response: - [x] Move all dubious `@sharable` vals in the cc package to the current `CCState`. Let's see whether this fixes the problem. Also, tighten a bunch of rules and checks to rule out non-sensical elements in capture sets. This makes the previous `healTypeParam` logic redundant.
2 parents 262ebd2 + 86970ef commit 2f32850

File tree

89 files changed

+1604
-1165
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+1604
-1165
lines changed

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

+4-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,8 @@ import dotty.tools.dotc.util.chaining.*
4242
import java.util.{Timer, TimerTask}
4343

4444
/** A compiler run. Exports various methods to compile source files */
45-
class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with ConstraintRunInfo {
45+
class Run(comp: Compiler, ictx: Context)
46+
extends ImplicitRunInfo, ConstraintRunInfo, cc.CaptureRunInfo {
4647

4748
/** Default timeout to stop looking for further implicit suggestions, in ms.
4849
* This is usually for the first import suggestion; subsequent suggestions
@@ -519,6 +520,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
519520
/** Print summary of warnings and errors encountered */
520521
def printSummary(): Unit = {
521522
printMaxConstraint()
523+
printMaxPath()
522524
val r = runContext.reporter
523525
if !r.errorsReported then
524526
profile.printSummary()
@@ -529,6 +531,7 @@ class Run(comp: Compiler, ictx: Context) extends ImplicitRunInfo with Constraint
529531
override def reset(): Unit = {
530532
super[ImplicitRunInfo].reset()
531533
super[ConstraintRunInfo].reset()
534+
super[CaptureRunInfo].reset()
532535
myCtx = null
533536
myUnits = Nil
534537
myUnitsCached = Nil

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

+1-1
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ class TreeTypeMap(
5656
/** Replace occurrences of `This(oldOwner)` in some prefix of a type
5757
* by the corresponding `This(newOwner)`.
5858
*/
59-
private val mapOwnerThis = new TypeMap with cc.CaptureSet.IdempotentCaptRefMap {
59+
private val mapOwnerThis = new TypeMap {
6060
private def mapPrefix(from: List[Symbol], to: List[Symbol], tp: Type): Type = from match {
6161
case Nil => tp
6262
case (cls: ClassSymbol) :: from1 => mapPrefix(from1, to.tail, tp.substThis(cls, to.head.thisType))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
package dotty.tools
2+
package dotc
3+
package cc
4+
5+
import core.*
6+
import CaptureSet.{CompareResult, CompareFailure, VarState}
7+
import collection.mutable
8+
import reporting.Message
9+
import Contexts.Context
10+
import Types.MethodType
11+
import Symbols.Symbol
12+
13+
/** Capture checking state, which is known to other capture checking components */
14+
class CCState:
15+
import CCState.*
16+
17+
// ------ Error diagnostics -----------------------------
18+
19+
/** Error reprting notes produces since the last call to `test` */
20+
var notes: List[ErrorNote] = Nil
21+
22+
def addNote(note: ErrorNote): Unit =
23+
if !notes.exists(_.getClass == note.getClass) then
24+
notes = note :: notes
25+
26+
def test(op: => CompareResult): CompareResult =
27+
val saved = notes
28+
notes = Nil
29+
try op match
30+
case res: CompareFailure => res.withNotes(notes)
31+
case res => res
32+
finally notes = saved
33+
34+
def testOK(op: => Boolean): CompareResult =
35+
test(if op then CompareResult.OK else CompareResult.Fail(Nil))
36+
37+
/** Warnings relating to upper approximations of capture sets with
38+
* existentially bound variables.
39+
*/
40+
val approxWarnings: mutable.ListBuffer[Message] = mutable.ListBuffer()
41+
42+
// ------ Level handling ---------------------------
43+
44+
private var curLevel: Level = outermostLevel
45+
46+
/** The level of the current environment. Levels start at 0 and increase for
47+
* each nested function or class. -1 means the level is undefined.
48+
*/
49+
def currentLevel(using Context): Level = curLevel
50+
51+
/** Perform `op` in the next inner level */
52+
inline def inNestedLevel[T](inline op: T)(using Context): T =
53+
val saved = curLevel
54+
curLevel = curLevel.nextInner
55+
try op finally curLevel = saved
56+
57+
/** Perform `op` in the next inner level unless `p` holds. */
58+
inline def inNestedLevelUnless[T](inline p: Boolean)(inline op: T)(using Context): T =
59+
val saved = curLevel
60+
if !p then curLevel = curLevel.nextInner
61+
try op finally curLevel = saved
62+
63+
/** A map recording the level of a symbol */
64+
private val mySymLevel: mutable.Map[Symbol, Level] = mutable.Map()
65+
66+
def symLevel(sym: Symbol): Level = mySymLevel.getOrElse(sym, undefinedLevel)
67+
68+
def recordLevel(sym: Symbol)(using Context): Unit = mySymLevel(sym) = curLevel
69+
70+
// ------ BiTypeMap adjustment -----------------------
71+
72+
private var myMapFutureElems = true
73+
74+
/** When mapping a capture set with a BiTypeMap, should we create a BiMapped set
75+
* so that future elements can also be mapped, and elements added to the BiMapped
76+
* are back-propagated? Turned off when creating capture set variables for the
77+
* first time, since we then do not want to change the binder to the original type
78+
* without capture sets when back propagating. Error case where this shows:
79+
* pos-customargs/captures/lists.scala, method m2c.
80+
*/
81+
def mapFutureElems(using Context) = myMapFutureElems
82+
83+
/** Don't map future elements in this `op` */
84+
inline def withoutMappedFutureElems[T](op: => T)(using Context): T =
85+
val saved = mapFutureElems
86+
myMapFutureElems = false
87+
try op finally myMapFutureElems = saved
88+
89+
// ------ Iteration count of capture checking run
90+
91+
private var iterCount = 1
92+
93+
def iterationId = iterCount
94+
95+
def nextIteration[T](op: => T): T =
96+
iterCount += 1
97+
try op finally iterCount -= 1
98+
99+
// ------ Global counters -----------------------
100+
101+
/** Next CaptureSet.Var id */
102+
var varId = 0
103+
104+
/** Next root id */
105+
var rootId = 0
106+
107+
// ------ VarState singleton objects ------------
108+
// See CaptureSet.VarState creation methods for documentation
109+
110+
object Separate extends VarState.Separating
111+
object HardSeparate extends VarState.Separating
112+
object Unrecorded extends VarState.Unrecorded
113+
object ClosedUnrecorded extends VarState.ClosedUnrecorded
114+
115+
// ------ Context info accessed from companion object when isCaptureCheckingOrSetup is true
116+
117+
private var openExistentialScopes: List[MethodType] = Nil
118+
119+
private var capIsRoot: Boolean = false
120+
121+
object CCState:
122+
123+
opaque type Level = Int
124+
125+
val undefinedLevel: Level = -1
126+
127+
val outermostLevel: Level = 0
128+
129+
extension (x: Level)
130+
def isDefined: Boolean = x >= 0
131+
def <= (y: Level) = (x: Int) <= y
132+
def nextInner: Level = if isDefined then x + 1 else x
133+
134+
/** If we are currently in capture checking or setup, and `mt` is a method
135+
* type that is not a prefix of a curried method, perform `op` assuming
136+
* a fresh enclosing existential scope `mt`, otherwise perform `op` directly.
137+
*/
138+
inline def inNewExistentialScope[T](mt: MethodType)(op: => T)(using Context): T =
139+
if isCaptureCheckingOrSetup then
140+
val ccs = ccState
141+
val saved = ccs.openExistentialScopes
142+
if mt.marksExistentialScope then ccs.openExistentialScopes = mt :: ccs.openExistentialScopes
143+
try op finally ccs.openExistentialScopes = saved
144+
else
145+
op
146+
147+
/** The currently opened existential scopes */
148+
def openExistentialScopes(using Context): List[MethodType] = ccState.openExistentialScopes
149+
150+
/** Run `op` under the assumption that `cap` can subsume all other capabilties
151+
* except Result capabilities. Every use of this method should be scrutinized
152+
* for whether it introduces an unsoundness hole.
153+
*/
154+
inline def withCapAsRoot[T](op: => T)(using Context): T =
155+
if isCaptureCheckingOrSetup then
156+
val ccs = ccState
157+
val saved = ccs.capIsRoot
158+
ccs.capIsRoot = true
159+
try op finally ccs.capIsRoot = saved
160+
else op
161+
162+
/** Is `caps.cap` a root capability that is allowed to subsume other capabilities? */
163+
def capIsRoot(using Context): Boolean = ccState.capIsRoot
164+
165+
end CCState

0 commit comments

Comments
 (0)