From c97792e227db4822420c8f3e095af46cf25d5396 Mon Sep 17 00:00:00 2001 From: Flavio Brasil Date: Wed, 27 Sep 2023 23:48:53 -0700 Subject: [PATCH] wip direct syntax hygiene --- .../src/main/scala-2/kyo/Validate.scala | 57 ++++-- kyo-direct/src/test/scala-2/hygieneTest.scala | 193 ++++++++++++++++++ 2 files changed, 230 insertions(+), 20 deletions(-) create mode 100644 kyo-direct/src/test/scala-2/hygieneTest.scala diff --git a/kyo-direct/src/main/scala-2/kyo/Validate.scala b/kyo-direct/src/main/scala-2/kyo/Validate.scala index b2d8555c1..2b2875d4a 100644 --- a/kyo-direct/src/main/scala-2/kyo/Validate.scala +++ b/kyo-direct/src/main/scala-2/kyo/Validate.scala @@ -10,7 +10,7 @@ private[kyo] object Validate { import c.universe._ def fail(t: Tree, msg: String) = - c.abort(t.pos, msg + ": " + t) + c.abort(t.pos, msg) def pure(tree: Tree) = !Trees.exists(c)(tree) { @@ -20,32 +20,49 @@ private[kyo] object Validate { Trees.traverse(c)(tree) { case q"$pack.await[$t, $s]($v)" => case q"$pack.defer[$t]($v)" => - fail(tree, "nested defer blocks aren't supported") + fail(tree, "Nested `defer` blocks are not allowed.") case tree if (tree.tpe.typeSymbol == c.typeOf[(Any > Any)].typeSymbol) => - fail(tree, "effectful computation must be in an await block") + fail(tree, "Effectful computation must be inside an `await` block.") case tree @ q"var $name = $body" => - fail(tree, "`var` is not allowed in a defer block") + fail(tree, "`var` declarations are not allowed inside a `defer` block.") case tree @ q"return $v" => - fail(tree, "`return` are not allowed in a defer block") + fail(tree, "`return` statements are not allowed inside a `defer` block.") case q"$mods val $name = $body" if mods.hasFlag(Flag.LAZY) => - fail(tree, "`lazy val` is not allowed in a defer block") + fail(tree, "`lazy val` declarations are not allowed inside a `defer` block.") case tree @ q"(..$params) => $body" if (!pure(body)) => - fail(tree, "functions can't use await blocks") + fail(tree, "Lambda functions containing `await` are not supported.") + case tree @ q"$mods def $method[..$t](...$params): $r = $body" if (!pure(body)) => + fail(tree, "`def` declarations containing `await` are not supported.") + case tree @ q"try $block catch { case ..$cases }" => + fail(tree, "`try/catch` blocks are not supported inside a `defer` block.") + case tree @ q"try $block catch { case ..$cases } finally $finalizer" => + fail(tree, "`try/catch` blocks are not supported inside a `defer` block.") case tree @ q"$method[..$t](...$values)" if values.size > 0 && method.symbol.isMethod => - val pit = method.symbol.asMethod.paramLists.flatten.iterator - val vit = values.flatten.iterator - while (pit.hasNext && vit.hasNext) { - val param = pit.next() - val value = vit.next() - (param.asTerm.isByNameParam, value) match { - case (true, t) if (!pure(t)) => - c.abort(t.pos, "await can't be used in a by-name param: " + t) - case other => () - } + method.symbol.asMethod.paramLists.flatten.foreach { + param => + if (param.asTerm.isByNameParam) + fail(tree, "`await` cannot be used in by-name parameters.") } - values.flatten.foreach(Validate(c)(_)) - case tree @ q"$mods def $method[..$t](...$params): $r = $body" if (!pure(body)) => - fail(tree, "can't use await in a `def`") + case tree @ q"new $t(..$args)" if (args.size > 0) => + t.tpe.decls.foreach { + case m: MethodSymbol if m.paramLists.flatten.exists(_.asTerm.isByNameParam) => + fail(tree, "Constructors with by-name parameters are not supported.") + case _ => + } + case tree @ q"$value match { case ..$cases }" if tree.symbol != null && tree.symbol.isMacro => + fail(tree, "`match` expressions using macros are not supported.") + case tree @ q"throw $expr" => + fail(tree, "Throwing exceptions is not supported inside a `defer` block.") + case tree @ q"$mods class $tpname[..$tparams] $ctorMods(...$paramss) extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => + fail(tree, "`class` declarations are not supported inside a `defer` block.") + case tree @ q"for (...$enumerators) yield $expr" => + fail(tree, "`for yield` comprehensions are not allowed inside a `defer` block.") + case tree @ q"for (...$enumerators) $expr" => + fail(tree, "`for` comprehensions are not allowed inside a `defer` block.") + case tree @ q"$mods trait $tpname[..$tparams] extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => + fail(tree, "`trait` declarations are not supported inside a `defer` block.") + case tree @ q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$stats }" => + fail(tree, "`object` declarations are not supported inside a `defer` block.") } } } diff --git a/kyo-direct/src/test/scala-2/hygieneTest.scala b/kyo-direct/src/test/scala-2/hygieneTest.scala new file mode 100644 index 000000000..2aedb58b8 --- /dev/null +++ b/kyo-direct/src/test/scala-2/hygieneTest.scala @@ -0,0 +1,193 @@ +import kyo._ +import kyo.ios._ +import kyo.direct._ +import kyo.TestSupport._ +import org.scalatest.Assertions +import org.scalatest.freespec.AnyFreeSpec + +class hygieneTest extends AnyFreeSpec with Assertions { + + "use of var" in { + assertDoesNotCompile(""" + defer { + var x = 1 + await(IOs(1)) + } + """) + } + + "use of return" in { + assertDoesNotCompile(""" + defer { + return 42 + await(IOs(1)) + } + """) + } + + "nested defer block" in { + assertDoesNotCompile(""" + defer { + defer { + await(IOs(1)) + } + } + """) + } + + "lazy val" in { + assertDoesNotCompile(""" + defer { + lazy val x = 10 + await(IOs(1)) + } + """) + } + + "function containing await" in { + assertDoesNotCompile(""" + defer { + def foo() = await(IOs(1)) + foo() + } + """) + } + + "try/catch" in { + assertDoesNotCompile(""" + defer { + try { + await(IOs(1)) + } catch { + case _: Exception => await(IOs(2)) + } + } + """) + } + + "class declaration" in { + assertDoesNotCompile(""" + defer { + class A(val x: Int) + await(IOs(1)) + } + """) + } + + "object declaration" in { + assertDoesNotCompile(""" + defer { + object A + await(IOs(1)) + } + """) + } + + "trait declaration" in { + assertDoesNotCompile(""" + defer { + trait A + await(IOs(1)) + } + """) + } + + "for-comprehension" in { + assertDoesNotCompile(""" + defer { + for { + x <- await(IOs(1)) + y <- await(IOs(2)) + } yield x + y + } + """) + } + + "throw expression" in { + assertDoesNotCompile(""" + defer { + throw new RuntimeException("Error!") + await(IOs(1)) + } + """) + } + "try without catch or finally" in { + assertDoesNotCompile(""" + defer { + try { + await(IOs(1)) + } + } + """) + } + + "try with only finally" in { + assertDoesNotCompile(""" + defer { + try { + await(IOs(1)) + } finally { + println("Cleanup") + } + } + """) + } + + "by-name parameters" in { + assertDoesNotCompile(""" + defer { + def foo(x: => Int) = x + 1 + foo(await(IOs(1))) + } + """) + } + + "new instance with by-name parameter" in { + assertDoesNotCompile(""" + defer { + class A(x: => Int) + new A(await(IOs(1))) + } + """) + } + + "match expression without cases" in { + assertDoesNotCompile(""" + defer { + await(IOs(1)) match {} + } + """) + } + + "for-comprehension without yield" in { + assertDoesNotCompile(""" + defer { + for { + x <- await(IOs(1)) + y <- await(IOs(2)) + } x + y + } + """) + } + + "nested functions" in { + assertDoesNotCompile(""" + defer { + def outer() = { + def inner() = await(IOs(1)) + inner() + } + outer() + } + """) + } + + "lambdas with await" in { + assertDoesNotCompile(""" + defer { + val f = (x: Int) => await(IOs(1)) + x + f(10) + } + """) + } +}