From 79f32d4365343cddea352b06849e5a4c2e1dd3e0 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Wed, 8 May 2019 18:51:57 +0200 Subject: [PATCH] Add regression test Show how it is possible to test error reporting within a macro without an additional framework --- .../Macros_1.scala | 71 +++++++++++++++++++ .../Test_2.scala | 24 +++++++ 2 files changed, 95 insertions(+) create mode 100644 tests/run-macros/tasty-string-interpolation-reporter-test/Macros_1.scala create mode 100644 tests/run-macros/tasty-string-interpolation-reporter-test/Test_2.scala diff --git a/tests/run-macros/tasty-string-interpolation-reporter-test/Macros_1.scala b/tests/run-macros/tasty-string-interpolation-reporter-test/Macros_1.scala new file mode 100644 index 000000000000..622947491904 --- /dev/null +++ b/tests/run-macros/tasty-string-interpolation-reporter-test/Macros_1.scala @@ -0,0 +1,71 @@ +import scala.quoted._ +import scala.quoted.autolift._ +import scala.quoted.matching._ +import scala.tasty.Reflection + +import scala.language.implicitConversions + +object Foo { + implicit object StringContextOps { + inline def (ctx: => StringContext) foo (args: => Any*): String = ${ Macro.foo('ctx, 'args) } + } +} + + +object TestFooErrors { // Defined in tests + implicit object StringContextOps { + inline def (ctx: => StringContext) foo (args: => Any*): List[(Int, Int, Int, String)] = ${ Macro.fooErrors('ctx, 'args) } + } +} + +object Macro { + + def foo(sc: Expr[StringContext], argsExpr: Expr[Seq[Any]]) given (reflect: Reflection): Expr[String] = { + (sc, argsExpr) match { + case ('{ StringContext(${ExprSeq(parts)}: _*) }, ExprSeq(args)) => + val reporter = new Reporter { + def errorOnPart(msg: String, partIdx: Int): Unit = { + import reflect._ + error(msg, parts(partIdx).unseal.pos) + } + } + fooCore(parts, args, reporter) + } + } + + def fooErrors(sc: Expr[StringContext], argsExpr: Expr[Seq[Any]]) given (reflect: Reflection): Expr[List[(Int, Int, Int, String)]] = { + (sc, argsExpr) match { + case ('{ StringContext(${ExprSeq(parts)}: _*) }, ExprSeq(args)) => + val errors = List.newBuilder[Expr[(Int, Int, Int, String)]] + val reporter = new Reporter { + def errorOnPart(msg: String, partIdx: Int): Unit = { + import reflect._ + val pos = parts(partIdx).unseal.pos + errors += '{ Tuple4($partIdx, ${pos.start}, ${pos.end}, $msg) } + } + } + fooCore(parts, args, reporter) // Discard result + errors.result().toExprOfList + } + + + } + + + private def fooCore(parts: Seq[Expr[String]], args: Seq[Expr[Any]], reporter: Reporter) given Reflection: Expr[String] = { + for ((part, idx) <- parts.zipWithIndex) { + val Const(v: String) = part + if (v.contains("#")) + reporter.errorOnPart("Cannot use #", idx) + } + + '{ StringContext(${parts.toList.toExprOfList}: _*).s(${args.toList.toExprOfList}: _*) } + } + + + trait Reporter { + def errorOnPart(msg: String, partIdx: Int): Unit + } + + +} diff --git a/tests/run-macros/tasty-string-interpolation-reporter-test/Test_2.scala b/tests/run-macros/tasty-string-interpolation-reporter-test/Test_2.scala new file mode 100644 index 000000000000..8b9424bfecb0 --- /dev/null +++ b/tests/run-macros/tasty-string-interpolation-reporter-test/Test_2.scala @@ -0,0 +1,24 @@ + +object Test { + + def main(args: Array[String]): Unit = { + posTests() + negTests() + } + + def posTests() = { + import Foo._ + assertEquals(foo"abc${"123"}def", "abc123def") + } + + def negTests() = { + import TestFooErrors._ + assertEquals(foo"a#c${"123"}def", List((0, 256, 259, "Cannot use #"))) + assertEquals(foo"abc${"123"}#ef", List((1, 342, 345, "Cannot use #"))) + assertEquals(foo"a#c${"123"}#ef", List((0, 406, 409, "Cannot use #"), (1, 417, 420, "Cannot use #"))) + } + + def assertEquals(actual: Any, expected: Any): Unit = { + assert(actual == expected, s"actual: $actual\nbut expected: $expected") + } +}