diff --git a/compiler/src/dotty/tools/dotc/transform/Splicer.scala b/compiler/src/dotty/tools/dotc/transform/Splicer.scala index 600f9c5063e6..bc371b4ace70 100644 --- a/compiler/src/dotty/tools/dotc/transform/Splicer.scala +++ b/compiler/src/dotty/tools/dotc/transform/Splicer.scala @@ -72,9 +72,9 @@ object Splicer { * * See: `Staging` */ - def canBeSpliced(tree: Tree)(implicit ctx: Context): Boolean = tree match { - case Quoted(_) => true - case _ => (new CanBeInterpreted).apply(tree) + def checkValidMacroBody(tree: Tree)(implicit ctx: Context): Unit = tree match { + case Quoted(_) => // ok + case _ => (new CheckValidMacroBody).apply(tree) } /** Tree interpreter that evaluates the tree */ @@ -276,27 +276,37 @@ object Splicer { } /** Tree interpreter that tests if tree can be interpreted */ - private class CanBeInterpreted(implicit ctx: Context) extends AbstractInterpreter { + private class CheckValidMacroBody(implicit ctx: Context) extends AbstractInterpreter { - type Result = Boolean + type Result = Unit - def apply(tree: Tree): Boolean = interpretTree(tree)(Map.empty) + def apply(tree: Tree): Unit = interpretTree(tree)(Map.empty) - protected def interpretQuote(tree: tpd.Tree)(implicit env: Env): Boolean = true - protected def interpretTypeQuote(tree: tpd.Tree)(implicit env: Env): Boolean = true - protected def interpretLiteral(value: Any)(implicit env: Env): Boolean = true - protected def interpretVarargs(args: List[Boolean])(implicit env: Env): Boolean = args.forall(identity) - protected def interpretTastyContext()(implicit env: Env): Boolean = true - protected def interpretQuoteContext()(implicit env: Env): Boolean = true - protected def interpretStaticMethodCall(module: Symbol, fn: Symbol, args: => List[Boolean])(implicit env: Env): Boolean = args.forall(identity) - protected def interpretModuleAccess(fn: Symbol)(implicit env: Env): Boolean = true - protected def interpretNew(fn: Symbol, args: => List[Boolean])(implicit env: Env): Boolean = args.forall(identity) + protected def interpretQuote(tree: tpd.Tree)(implicit env: Env): Unit = () + protected def interpretTypeQuote(tree: tpd.Tree)(implicit env: Env): Unit = () + protected def interpretLiteral(value: Any)(implicit env: Env): Unit = () + protected def interpretVarargs(args: List[Unit])(implicit env: Env): Unit = () + protected def interpretTastyContext()(implicit env: Env): Unit = () + protected def interpretQuoteContext()(implicit env: Env): Unit = () + protected def interpretStaticMethodCall(module: Symbol, fn: Symbol, args: => List[Unit])(implicit env: Env): Unit = args.foreach(identity) + protected def interpretModuleAccess(fn: Symbol)(implicit env: Env): Unit = () + protected def interpretNew(fn: Symbol, args: => List[Unit])(implicit env: Env): Unit = args.foreach(identity) - def unexpectedTree(tree: tpd.Tree)(implicit env: Env): Boolean = { + def unexpectedTree(tree: tpd.Tree)(implicit env: Env): Unit = { // Assuming that top-level splices can only be in inline methods // and splices are expanded at inline site, references to inline values // will be known literal constant trees. - tree.symbol.is(Inline) + if (!tree.symbol.is(Inline)) + ctx.error( + """Malformed macro. + | + |Expected the splice ${...} to contain a single call to a static method. + | + |Where parameters may be: + | * Quoted paramers or fields + | * References to inline parameters + | * Literal values of primitive types + """.stripMargin, tree.sourcePos) } } diff --git a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala index e74a2c3b3442..61ce226a2ade 100644 --- a/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala +++ b/compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala @@ -7,6 +7,7 @@ import Trees._ import core._ import Flags._ import Symbols._ +import Flags._ import Types._ import Decorators._ import NameKinds._ @@ -245,28 +246,6 @@ object PrepareInlineable { def checkInlineMacro(sym: Symbol, rhs: Tree, pos: SourcePosition)(implicit ctx: Context) = { if (!ctx.isAfterTyper) { - - /** InlineSplice is used to detect cases where the expansion - * consists of a (possibly multiple & nested) block or a sole expression. - */ - object InlineSplice { - def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match { - case Spliced(code) => - if (!Splicer.canBeSpliced(code)) { - ctx.error( - "Malformed macro call. The contents of the splice ${...} must call a static method and arguments must be quoted or inline.", - tree.sourcePos) - } else if (code.symbol.flags.is(Inline)) { - ctx.error("Macro cannot be implemented with an `inline` method", code.sourcePos) - } - Some(code) - case Block(List(stat), Literal(Constants.Constant(()))) => unapply(stat) - case Block(Nil, expr) => unapply(expr) - case Typed(expr, _) => unapply(expr) - case _ => None - } - } - var isMacro = false new TreeMapWithStages(freshStagingContext) { override protected def transformSplice(body: tpd.Tree, splice: tpd.Tree)(implicit ctx: Context): tpd.Tree = { @@ -279,10 +258,24 @@ object PrepareInlineable { if (isMacro) { sym.setFlag(Macro) - if (level == 0) - rhs match { - case InlineSplice(_) => + if (level == 0) { + def isValidMacro(tree: Tree)(implicit ctx: Context): Unit = tree match { + case Spliced(code) => + if (code.symbol.flags.is(Inline)) + ctx.error("Macro cannot be implemented with an `inline` method", code.sourcePos) + Splicer.checkValidMacroBody(code) new PCPCheckAndHeal(freshStagingContext).transform(rhs) // Ignore output, only check PCP + + case Block(List(stat), Literal(Constants.Constant(()))) => isValidMacro(stat) + case Block(Nil, expr) => isValidMacro(expr) + case Typed(expr, _) => isValidMacro(expr) + case Block(DefDef(nme.ANON_FUN, _, _, _, _) :: Nil, Closure(_, fn, _)) if fn.symbol.info.isImplicitMethod => + // TODO Suppot this pattern + ctx.error( + """Macros using a return type of the form `foo(): given X => Y` are not yet supported. + | + |Place the implicit as an argument (`foo() given X: Y`) to overcome this limitation. + |""".stripMargin, tree.sourcePos) case _ => ctx.error( """Malformed macro. @@ -294,6 +287,8 @@ object PrepareInlineable { | * All arguments must be quoted or inline """.stripMargin, pos) } + isValidMacro(rhs) + } } } } diff --git a/tests/neg-macros/quote-complex-top-splice.scala b/tests/neg-macros/quote-complex-top-splice.scala index f0b5b74513b5..9dc996130878 100644 --- a/tests/neg-macros/quote-complex-top-splice.scala +++ b/tests/neg-macros/quote-complex-top-splice.scala @@ -4,23 +4,23 @@ import scala.quoted._ object Test { - inline def foo1: Unit = ${ // error - val x = 1 + inline def foo1: Unit = ${ + val x = 1 // error impl(x) } - inline def foo2: Unit = ${ impl({ // error - val x = 1 + inline def foo2: Unit = ${ impl({ + val x = 1 // error x }) } - inline def foo3: Unit = ${ impl({ // error - println("foo3") + inline def foo3: Unit = ${ impl({ + println("foo3") // error 3 }) } - inline def foo4: Unit = ${ // error - println("foo4") + inline def foo4: Unit = ${ + println("foo4") // error impl(1) } diff --git a/tests/neg-macros/quote-interpolator-core-old.scala b/tests/neg-macros/quote-interpolator-core-old.scala index bc136668bf94..cdca5dbb905f 100644 --- a/tests/neg-macros/quote-interpolator-core-old.scala +++ b/tests/neg-macros/quote-interpolator-core-old.scala @@ -6,9 +6,9 @@ import scala.quoted.autolift._ object FInterpolation { implicit class FInterpolatorHelper(val sc: StringContext) extends AnyVal { - inline def ff(arg1: Any): String = ${fInterpolation(sc, Seq('arg1))} // error: Inline macro method must be a static method - inline def ff(arg1: Any, arg2: Any): String = ${fInterpolation(sc, Seq('arg1, 'arg2))} // error: Inline macro method must be a static method - inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ${fInterpolation(sc, Seq('arg1, 'arg2, 'arg3))} // error: Inline macro method must be a static method + inline def ff(arg1: Any): String = ${fInterpolation(sc, Seq('arg1))} // error // error + inline def ff(arg1: Any, arg2: Any): String = ${fInterpolation(sc, Seq('arg1, 'arg2))} // error // error + inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ${fInterpolation(sc, Seq('arg1, 'arg2, 'arg3))} // error // error // ... } diff --git a/tests/neg-macros/quote-splice-interpret-1.scala b/tests/neg-macros/quote-splice-interpret-1.scala index dacd3886f9fd..72507b281215 100644 --- a/tests/neg-macros/quote-splice-interpret-1.scala +++ b/tests/neg-macros/quote-splice-interpret-1.scala @@ -2,8 +2,8 @@ import scala.quoted._ object Macros { - inline def isZero(inline n: Int): Boolean = ${ // error - if (n == 0) 'true + inline def isZero(inline n: Int): Boolean = ${ + if (n == 0) 'true // error else 'false } } diff --git a/tests/neg/i4433.scala b/tests/neg/i4433.scala index 7da5fadcc7ad..8327bbad25b4 100644 --- a/tests/neg/i4433.scala +++ b/tests/neg/i4433.scala @@ -1,7 +1,7 @@ object Foo { - inline def g(inline p: Int => Boolean): Boolean = ${ // error - if(p(5)) 'true + inline def g(inline p: Int => Boolean): Boolean = ${ + if (p(5)) 'true // error else 'false } } diff --git a/tests/neg/i4493-b.scala b/tests/neg/i4493-b.scala index aae0ba99230a..235f14c0a4a9 100644 --- a/tests/neg/i4493-b.scala +++ b/tests/neg/i4493-b.scala @@ -1,7 +1,7 @@ class Index[K] object Index { - inline def succ[K](x: K): Unit = ${ // error - implicit val t: quoted.Type[K] = '[K] + inline def succ[K](x: K): Unit = ${ + implicit val t: quoted.Type[K] = '[K] // error '{new Index[K]} } } diff --git a/tests/neg/i4493.scala b/tests/neg/i4493.scala index 74efc5c99a8a..852ac0d44d54 100644 --- a/tests/neg/i4493.scala +++ b/tests/neg/i4493.scala @@ -1,7 +1,7 @@ class Index[K] object Index { - inline def succ[K]: Unit = ${ // error - implicit val t: quoted.Type[K] = '[K] + inline def succ[K]: Unit = ${ + implicit val t: quoted.Type[K] = '[K] // error '{new Index[K]} } } diff --git a/tests/neg/i5840.check b/tests/neg/i5840.check new file mode 100644 index 000000000000..bed4ba80c3dd --- /dev/null +++ b/tests/neg/i5840.check @@ -0,0 +1,6 @@ +-- Error: tests/neg/i5840.scala:8:28 ----------------------------------------------------------------------------------- +8 | inline def i5_2[T](n: T): Contextual[T] = ${ foo('n) } // error: Macros using `given X => Y` return types are not yet supported + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | Macros using a return type of the form `foo(): given X => Y` are not yet supported. + | + | Place the implicit as an argument (`foo() given X: Y`) to overcome this limitation. diff --git a/tests/neg/i5840.scala b/tests/neg/i5840.scala new file mode 100644 index 000000000000..40feb9af73a4 --- /dev/null +++ b/tests/neg/i5840.scala @@ -0,0 +1,11 @@ +import scala.quoted._ +object Test { + + type Contextual[T] = given QuoteContext => T + + inline def i5_1[T](n: T)(implicit thisCtx: QuoteContext): T = ${ foo('n) } // OK + + inline def i5_2[T](n: T): Contextual[T] = ${ foo('n) } // error: Macros using `given X => Y` return types are not yet supported + + def foo[T](x: Expr[T]) = x +} diff --git a/tests/neg/i6783.scala b/tests/neg/i6783.scala index 55af88234692..10d2e796e669 100644 --- a/tests/neg/i6783.scala +++ b/tests/neg/i6783.scala @@ -1,7 +1,9 @@ import scala.quoted._ -inline def test(f: (Int, Int) => Int) = ${ // error: Malformed macro - testImpl((a: Expr[Int], b: Expr[Int]) => '{ f(${a}, ${b}) }) +inline def test(f: (Int, Int) => Int) = ${ + testImpl( + (a: Expr[Int], b: Expr[Int]) => '{ f(${a}, ${b}) } // error: Malformed macro + ) } def testImpl(f: (Expr[Int], Expr[Int]) => Expr[Int]): Expr[Int] = ???