Skip to content

Commit 756c1f3

Browse files
authored
Add test to simulate arena allocation of contexts in dotc (#16333)
2 parents 8fba321 + a7338db commit 756c1f3

File tree

2 files changed

+236
-0
lines changed

2 files changed

+236
-0
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
OK IntType()
2+
ERROR: Type error
3+
found : StringType()
4+
expected: IntType()
5+
for : Ref(s)
6+
ERROR: cyclic reference involving x
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
2+
// A mini typechecker to experiment with arena allocated contexts
3+
import compiletime.uninitialized
4+
import annotation.{experimental, tailrec, constructorOnly}
5+
import collection.mutable
6+
import language.`3.3`
7+
8+
case class Symbol(name: String, initOwner: Symbol | Null) extends caps.Pure:
9+
def owner = initOwner.nn
10+
private var myInfo: Type = uninitialized
11+
def infoOrCompleter: Type = myInfo
12+
def info: Type =
13+
infoOrCompleter match
14+
case completer: LazyType =>
15+
myInfo = NoType
16+
completer.complete()
17+
info
18+
case NoType =>
19+
throw TypeError(s"cyclic reference involving $name")
20+
case tp =>
21+
tp
22+
def info_=(tp: Type) = myInfo = tp
23+
def exists: Boolean = true
24+
def orElse(alt: => Symbol): Symbol = this
25+
26+
object NoSymbol extends Symbol("", null):
27+
override def owner = assert(false, "NoSymbol.owner")
28+
override def infoOrCompleter = NoType
29+
override def exists: Boolean = false
30+
override def orElse(alt: => Symbol): Symbol = alt
31+
32+
abstract class Type extends caps.Pure:
33+
def exists = true
34+
def show: String
35+
case class IntType()(using @constructorOnly c: Context) extends Type:
36+
def show = "Int"
37+
case class StringType()(using @constructorOnly c: Context) extends Type:
38+
def show = "String"
39+
case object NoType extends Type:
40+
override def exists = false
41+
def show = "<none>"
42+
43+
abstract class LazyType(using DetachedContext) extends Type:
44+
def complete(): Unit = doComplete()
45+
def doComplete()(using Context): Unit
46+
def show = "?"
47+
48+
enum Tree:
49+
case Let(bindings: List[Binding], res: Tree)
50+
case Ref(name: String)
51+
case Add(x: Tree, y: Tree)
52+
case Length(x: Tree)
53+
case Lit(value: Any)
54+
55+
case class Binding(name: String, rhs: Tree)
56+
57+
class Scope:
58+
private val elems = mutable.Map[String, Symbol]()
59+
def enter(sym: Symbol)(using Context): Unit =
60+
if elems.contains(sym.name) then
61+
report.error(s"duplicate definition: ${sym.name}")
62+
elems(sym.name) = sym
63+
def lookup(name: String): Symbol =
64+
elems.getOrElse(name, NoSymbol)
65+
def elements: Iterator[Symbol] = elems.valuesIterator
66+
67+
object EmptyScope extends Scope
68+
69+
class TypeError(val msg: String) extends Exception
70+
71+
class Run:
72+
var errorCount = 0
73+
74+
object report:
75+
def error(msg: -> String)(using Context) =
76+
ctx.run.errorCount += 1
77+
println(s"ERROR: $msg")
78+
79+
abstract class Ctx:
80+
def outer: Context
81+
def owner: Symbol
82+
def scope: Scope
83+
def run: Run
84+
def detached: DetachedContext
85+
86+
type Context = {*} Ctx
87+
88+
abstract class DetachedContext extends Ctx:
89+
def outer: DetachedContext
90+
91+
class FreshCtx(val level: Int) extends DetachedContext:
92+
var outer: FreshCtx = uninitialized
93+
var owner: Symbol = uninitialized
94+
var scope: Scope = uninitialized
95+
var run: Run = uninitialized
96+
def initFrom(other: Context): this.type =
97+
outer = other.asInstanceOf[FreshCtx]
98+
owner = other.owner
99+
scope = other.scope
100+
run = other.run
101+
this
102+
def detached: DetachedContext =
103+
var c = this
104+
while c.level >= 0 && (ctxStack(c.level) eq c) do
105+
ctxStack(c.level) = FreshCtx(c.level)
106+
c = c.outer
107+
this
108+
109+
object NoContext extends FreshCtx(-1):
110+
owner = NoSymbol
111+
scope = EmptyScope
112+
113+
type FreshContext = {*} FreshCtx
114+
115+
inline def ctx(using c: Context): {c} Ctx = c
116+
117+
// !cc! it does not work if ctxStack is an Array[FreshContext] instead.
118+
var ctxStack = Array.tabulate(16)(new FreshCtx(_))
119+
var curLevel = 0
120+
121+
private def freshContext(using Context): FreshContext =
122+
if curLevel == ctxStack.length then
123+
val prev = ctxStack
124+
ctxStack = new Array[FreshCtx](curLevel * 2)
125+
Array.copy(prev, 0, ctxStack, 0, prev.length)
126+
for level <- curLevel until ctxStack.length do
127+
ctxStack(level) = FreshCtx(level)
128+
val result = ctxStack(curLevel).initFrom(ctx)
129+
curLevel += 1
130+
result
131+
132+
inline def inFreshContext[T](inline op: FreshContext ?-> T)(using Context): T =
133+
try op(using freshContext) finally curLevel -= 1
134+
135+
inline def withOwner[T](owner: Symbol)(inline op: Context ?-> T)(using Context): T =
136+
inFreshContext: c ?=>
137+
c.owner = owner
138+
op
139+
140+
inline def withScope[T](scope: Scope)(inline op: Context ?-> T)(using Context): T =
141+
inFreshContext: c ?=>
142+
c.scope = scope
143+
op
144+
145+
def typed(tree: Tree, expected: Type = NoType)(using Context): Type =
146+
try
147+
val tp = typedUnadapted(tree, expected)
148+
if expected.exists && tp != expected then
149+
report.error(
150+
s"""Type error
151+
| found : $tp
152+
| expected: $expected
153+
| for : $tree""".stripMargin)
154+
tp
155+
catch case ex: TypeError =>
156+
report.error(ex.msg)
157+
NoType
158+
159+
import Tree.*
160+
def typedUnadapted(tree: Tree, expected: Type = NoType)(using Context): Type = tree match
161+
case Let(bindings, res) =>
162+
withScope(Scope()):
163+
for Binding(name, rhs) <- bindings do
164+
val sym = Symbol(name, ctx.owner)
165+
val dctx = ctx.detached
166+
sym.info = new LazyType(using dctx):
167+
override def doComplete()(using Context) =
168+
sym.info = withOwner(sym):
169+
typed(rhs)
170+
ctx.scope.enter(sym)
171+
for sym <- ctx.scope.elements do sym.info
172+
typed(res, expected)
173+
case Ref(name: String) =>
174+
def findIn(c: Context): Symbol =
175+
val sym = c.scope.lookup(name)
176+
if sym.exists || (c eq NoContext) then sym
177+
else findIn(c.outer)
178+
findIn(ctx).info
179+
case Add(x: Tree, y: Tree) =>
180+
typed(x, IntType())
181+
typed(y, IntType())
182+
IntType()
183+
case Length(x: Tree) =>
184+
typed(x, StringType())
185+
IntType()
186+
case Lit(value: Any) =>
187+
value match
188+
case value: Int => IntType()
189+
case value: String => StringType()
190+
case _ =>
191+
report.error(s"Int or String literal expected by $value found")
192+
NoType
193+
194+
object sugar:
195+
extension (tree: Tree)
196+
infix def where(bindings: Binding*) = Let(bindings.toList, tree)
197+
def + (other: Tree) = Add(tree, other)
198+
199+
extension (name: String)
200+
def := (rhs: Tree) = Binding(name, rhs)
201+
202+
import sugar.*
203+
204+
val prog =
205+
Ref("x") + Length(Ref("s")) where (
206+
"x" := Lit(1) + Length(Ref("s")),
207+
"s" := Lit("abc"))
208+
209+
val bad = Ref("x") + Ref("s") where (
210+
"x" := Lit(1),
211+
"s" := Lit("abc"))
212+
213+
val cyclic =
214+
Ref("x") + Length(Ref("s")) where (
215+
"x" := Lit(1) + Ref("x"),
216+
"s" := Lit("abc"))
217+
218+
def compile(tree: Tree)(using Context) =
219+
val run = new Run
220+
inFreshContext: c ?=>
221+
c.run = run
222+
val tp = typed(tree)(using c)
223+
if run.errorCount == 0 then
224+
println(s"OK $tp")
225+
226+
@main def Test =
227+
given Context = NoContext
228+
compile(prog)
229+
compile(bad)
230+
compile(cyclic)

0 commit comments

Comments
 (0)