Skip to content

Commit

Permalink
wip direct syntax hygiene
Browse files Browse the repository at this point in the history
  • Loading branch information
fwbrasil committed Sep 28, 2023
1 parent 1714690 commit c97792e
Show file tree
Hide file tree
Showing 2 changed files with 230 additions and 20 deletions.
57 changes: 37 additions & 20 deletions kyo-direct/src/main/scala-2/kyo/Validate.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -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.")
}
}
}
193 changes: 193 additions & 0 deletions kyo-direct/src/test/scala-2/hygieneTest.scala
Original file line number Diff line number Diff line change
@@ -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)
}
""")
}
}

0 comments on commit c97792e

Please sign in to comment.