From 62909555097db0a8117fcc5f7875af795b2e07b9 Mon Sep 17 00:00:00 2001 From: odersky Date: Sun, 13 Nov 2022 15:51:14 +0100 Subject: [PATCH 1/2] Add test to simulate arena allocation of contexts in dotc --- .../run-custom-args/captures/minicheck.check | 6 + .../run-custom-args/captures/minicheck.scala | 230 ++++++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 tests/run-custom-args/captures/minicheck.check create mode 100644 tests/run-custom-args/captures/minicheck.scala diff --git a/tests/run-custom-args/captures/minicheck.check b/tests/run-custom-args/captures/minicheck.check new file mode 100644 index 000000000000..7ff6ab94af4c --- /dev/null +++ b/tests/run-custom-args/captures/minicheck.check @@ -0,0 +1,6 @@ +OK IntType() +ERROR: Type error + found : StringType() + expected: IntType() + for : Ref(s) +ERROR: cyclic reference involving x diff --git a/tests/run-custom-args/captures/minicheck.scala b/tests/run-custom-args/captures/minicheck.scala new file mode 100644 index 000000000000..e963a7031ba6 --- /dev/null +++ b/tests/run-custom-args/captures/minicheck.scala @@ -0,0 +1,230 @@ + +// A mini typechecker to experiment with arena allocated contexts +import compiletime.uninitialized +import annotation.{experimental, tailrec, constructorOnly} +import collection.mutable +import language.`3.3` + +case class Symbol(name: String, initOwner: Symbol | Null) extends caps.Pure: + def owner = initOwner.nn + private var myInfo: Type = uninitialized + def infoOrCompleter: Type = myInfo + def info: Type = + infoOrCompleter match + case completer: LazyType => + myInfo = NoType + completer.complete() + info + case NoType => + throw TypeError(s"cyclic reference involving $name") + case tp => + tp + def info_=(tp: Type) = myInfo = tp + def exists: Boolean = true + def orElse(alt: => Symbol): Symbol = this + +object NoSymbol extends Symbol("", null): + override def owner = assert(false, "NoSymbol.owner") + override def infoOrCompleter = NoType + override def exists: Boolean = false + override def orElse(alt: => Symbol): Symbol = alt + +abstract class Type extends caps.Pure: + def exists = true + def show: String +case class IntType()(using @constructorOnly c: Context) extends Type: + def show = "Int" +case class StringType()(using @constructorOnly c: Context) extends Type: + def show = "String" +case object NoType extends Type: + override def exists = false + def show = "" + +abstract class LazyType(using DetachedContext) extends Type: + def complete(): Unit = doComplete() + def doComplete()(using Context): Unit + def show = "?" + +enum Tree: + case Let(bindings: List[Binding], res: Tree) + case Ref(name: String) + case Add(x: Tree, y: Tree) + case Length(x: Tree) + case Lit(value: Any) + +case class Binding(name: String, rhs: Tree) + +class Scope: + private val elems = mutable.Map[String, Symbol]() + def enter(sym: Symbol)(using Context): Unit = + if elems.contains(sym.name) then + report.error(s"duplicate definition: ${sym.name}") + elems(sym.name) = sym + def lookup(name: String): Symbol = + elems.getOrElse(name, NoSymbol) + def elements: Iterator[Symbol] = elems.valuesIterator + +object EmptyScope extends Scope + +class TypeError(val msg: String) extends Exception + +class Run: + var errorCount = 0 + +object report: + def error(msg: -> String)(using Context) = + ctx.run.errorCount += 1 + println(s"ERROR: $msg") + +abstract class Ctx: + def outer: Context + def owner: Symbol + def scope: Scope + def run: Run + def detached: DetachedContext + +type Context = {*} Ctx + +abstract class DetachedContext extends Ctx: + def outer: DetachedContext + +class FreshCtx(val level: Int) extends DetachedContext: + var outer: FreshCtx = uninitialized + var owner: Symbol = uninitialized + var scope: Scope = uninitialized + var run: Run = uninitialized + def initFrom(other: Context): this.type = + outer = other.asInstanceOf[FreshCtx] + owner = other.owner + scope = other.scope + run = other.run + this + def detached: DetachedContext = + var c = this + while c.level >= 0 && (ctxStack(c.level) eq c) do + ctxStack(c.level) = FreshCtx(c.level) + c = c.outer + this + +object NoContext extends FreshCtx(-1): + owner = NoSymbol + scope = EmptyScope + +type FreshContext = {*} FreshCtx + +def ctx(using c: Context): {c} Ctx = c + +// !cc! it does not work if ctxStack is an Array[FreshContext] instead. +var ctxStack = Array.tabulate(16)(new FreshCtx(_)) +var curLevel = 0 + +def inFreshContext[T](op: FreshContext ?-> T)(using Context): T = + if curLevel == ctxStack.length then + val prev = ctxStack + ctxStack = new Array[FreshCtx](curLevel * 2) + Array.copy(prev, 0, ctxStack, 0, prev.length) + for level <- curLevel until ctxStack.length do + ctxStack(level) = FreshCtx(level) + val result = ctxStack(curLevel).initFrom(ctx) + curLevel += 1 + try op(using result) + finally curLevel -= 1 + +def withOwner[T](owner: Symbol)(op: Context ?-> T)(using Context): T = + val prev = ctx + inFreshContext: c ?=> + c.owner = owner + op + +def withScope[T](scope: Scope)(op: Context ?-> T)(using Context): T = + val prev = ctx + inFreshContext: c ?=> + c.scope = scope + op + +def typed(tree: Tree, expected: Type = NoType)(using Context): Type = + try + val tp = typedUnadapted(tree, expected) + if expected.exists && tp != expected then + report.error( + s"""Type error + | found : $tp + | expected: $expected + | for : $tree""".stripMargin) + tp + catch case ex: TypeError => + report.error(ex.msg) + NoType + +import Tree.* +def typedUnadapted(tree: Tree, expected: Type = NoType)(using Context): Type = tree match + case Let(bindings, res) => + withScope(Scope()): + for Binding(name, rhs) <- bindings do + val sym = Symbol(name, ctx.owner) + val dctx = ctx.detached + sym.info = new LazyType(using dctx): + override def doComplete()(using Context) = + sym.info = withOwner(sym): + typed(rhs) + ctx.scope.enter(sym) + try typed(res, expected) + finally for sym <- ctx.scope.elements do sym.info + case Ref(name: String) => + def findIn(c: Context): Symbol = + val sym = c.scope.lookup(name) + if sym.exists || (c eq NoContext) then sym + else findIn(c.outer) + findIn(ctx).info + case Add(x: Tree, y: Tree) => + typed(x, IntType()) + typed(y, IntType()) + IntType() + case Length(x: Tree) => + typed(x, StringType()) + IntType() + case Lit(value: Any) => + value match + case value: Int => IntType() + case value: String => StringType() + case _ => + report.error(s"Int or String literzal expected by $value found") + NoType + +object sugar: + extension (tree: Tree) + infix def where(bindings: Binding*) = Let(bindings.toList, tree) + def + (other: Tree) = Add(tree, other) + + extension (name: String) + def := (rhs: Tree) = Binding(name, rhs) + +import sugar.* + +val prog = + Ref("x") + Length(Ref("s")) where ( + "x" := Lit(1), + "s" := Lit("abc")) + +val bad = Ref("x") + Ref("s") where ( + "x" := Lit(1), + "s" := Lit("abc")) + +val cyclic = + Ref("x") + Length(Ref("s")) where ( + "x" := Lit(1) + Ref("x"), + "s" := Lit("abc")) + +def compile(tree: Tree)(using Context) = + val run = new Run + inFreshContext: c ?=> + c.run = run + val tp = typed(tree)(using c) + if run.errorCount == 0 then + println(s"OK $tp") + +@main def Test = + given Context = NoContext + compile(prog) + compile(bad) + compile(cyclic) From a7338db2dce6ec5cfe2b66b8cb9e26f956695814 Mon Sep 17 00:00:00 2001 From: odersky Date: Mon, 14 Nov 2022 18:28:56 +0100 Subject: [PATCH 2/2] Make some operations inline methods --- .../run-custom-args/captures/minicheck.scala | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/run-custom-args/captures/minicheck.scala b/tests/run-custom-args/captures/minicheck.scala index e963a7031ba6..344e021493e5 100644 --- a/tests/run-custom-args/captures/minicheck.scala +++ b/tests/run-custom-args/captures/minicheck.scala @@ -112,13 +112,13 @@ object NoContext extends FreshCtx(-1): type FreshContext = {*} FreshCtx -def ctx(using c: Context): {c} Ctx = c +inline def ctx(using c: Context): {c} Ctx = c // !cc! it does not work if ctxStack is an Array[FreshContext] instead. var ctxStack = Array.tabulate(16)(new FreshCtx(_)) var curLevel = 0 -def inFreshContext[T](op: FreshContext ?-> T)(using Context): T = +private def freshContext(using Context): FreshContext = if curLevel == ctxStack.length then val prev = ctxStack ctxStack = new Array[FreshCtx](curLevel * 2) @@ -127,17 +127,17 @@ def inFreshContext[T](op: FreshContext ?-> T)(using Context): T = ctxStack(level) = FreshCtx(level) val result = ctxStack(curLevel).initFrom(ctx) curLevel += 1 - try op(using result) - finally curLevel -= 1 + result -def withOwner[T](owner: Symbol)(op: Context ?-> T)(using Context): T = - val prev = ctx +inline def inFreshContext[T](inline op: FreshContext ?-> T)(using Context): T = + try op(using freshContext) finally curLevel -= 1 + +inline def withOwner[T](owner: Symbol)(inline op: Context ?-> T)(using Context): T = inFreshContext: c ?=> c.owner = owner op -def withScope[T](scope: Scope)(op: Context ?-> T)(using Context): T = - val prev = ctx +inline def withScope[T](scope: Scope)(inline op: Context ?-> T)(using Context): T = inFreshContext: c ?=> c.scope = scope op @@ -168,8 +168,8 @@ def typedUnadapted(tree: Tree, expected: Type = NoType)(using Context): Type = t sym.info = withOwner(sym): typed(rhs) ctx.scope.enter(sym) - try typed(res, expected) - finally for sym <- ctx.scope.elements do sym.info + for sym <- ctx.scope.elements do sym.info + typed(res, expected) case Ref(name: String) => def findIn(c: Context): Symbol = val sym = c.scope.lookup(name) @@ -188,7 +188,7 @@ def typedUnadapted(tree: Tree, expected: Type = NoType)(using Context): Type = t case value: Int => IntType() case value: String => StringType() case _ => - report.error(s"Int or String literzal expected by $value found") + report.error(s"Int or String literal expected by $value found") NoType object sugar: @@ -203,7 +203,7 @@ import sugar.* val prog = Ref("x") + Length(Ref("s")) where ( - "x" := Lit(1), + "x" := Lit(1) + Length(Ref("s")), "s" := Lit("abc")) val bad = Ref("x") + Ref("s") where (