Skip to content

Commit 35c7dcb

Browse files
Merge pull request #6804 from dotty-staging/fix-#5840
Fix #5840: Give more informative error message
2 parents 96618c1 + 49fff11 commit 35c7dcb

11 files changed

+87
-63
lines changed

compiler/src/dotty/tools/dotc/transform/Splicer.scala

Lines changed: 27 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -72,9 +72,9 @@ object Splicer {
7272
*
7373
* See: `Staging`
7474
*/
75-
def canBeSpliced(tree: Tree)(implicit ctx: Context): Boolean = tree match {
76-
case Quoted(_) => true
77-
case _ => (new CanBeInterpreted).apply(tree)
75+
def checkValidMacroBody(tree: Tree)(implicit ctx: Context): Unit = tree match {
76+
case Quoted(_) => // ok
77+
case _ => (new CheckValidMacroBody).apply(tree)
7878
}
7979

8080
/** Tree interpreter that evaluates the tree */
@@ -276,27 +276,37 @@ object Splicer {
276276
}
277277

278278
/** Tree interpreter that tests if tree can be interpreted */
279-
private class CanBeInterpreted(implicit ctx: Context) extends AbstractInterpreter {
279+
private class CheckValidMacroBody(implicit ctx: Context) extends AbstractInterpreter {
280280

281-
type Result = Boolean
281+
type Result = Unit
282282

283-
def apply(tree: Tree): Boolean = interpretTree(tree)(Map.empty)
283+
def apply(tree: Tree): Unit = interpretTree(tree)(Map.empty)
284284

285-
protected def interpretQuote(tree: tpd.Tree)(implicit env: Env): Boolean = true
286-
protected def interpretTypeQuote(tree: tpd.Tree)(implicit env: Env): Boolean = true
287-
protected def interpretLiteral(value: Any)(implicit env: Env): Boolean = true
288-
protected def interpretVarargs(args: List[Boolean])(implicit env: Env): Boolean = args.forall(identity)
289-
protected def interpretTastyContext()(implicit env: Env): Boolean = true
290-
protected def interpretQuoteContext()(implicit env: Env): Boolean = true
291-
protected def interpretStaticMethodCall(module: Symbol, fn: Symbol, args: => List[Boolean])(implicit env: Env): Boolean = args.forall(identity)
292-
protected def interpretModuleAccess(fn: Symbol)(implicit env: Env): Boolean = true
293-
protected def interpretNew(fn: Symbol, args: => List[Boolean])(implicit env: Env): Boolean = args.forall(identity)
285+
protected def interpretQuote(tree: tpd.Tree)(implicit env: Env): Unit = ()
286+
protected def interpretTypeQuote(tree: tpd.Tree)(implicit env: Env): Unit = ()
287+
protected def interpretLiteral(value: Any)(implicit env: Env): Unit = ()
288+
protected def interpretVarargs(args: List[Unit])(implicit env: Env): Unit = ()
289+
protected def interpretTastyContext()(implicit env: Env): Unit = ()
290+
protected def interpretQuoteContext()(implicit env: Env): Unit = ()
291+
protected def interpretStaticMethodCall(module: Symbol, fn: Symbol, args: => List[Unit])(implicit env: Env): Unit = args.foreach(identity)
292+
protected def interpretModuleAccess(fn: Symbol)(implicit env: Env): Unit = ()
293+
protected def interpretNew(fn: Symbol, args: => List[Unit])(implicit env: Env): Unit = args.foreach(identity)
294294

295-
def unexpectedTree(tree: tpd.Tree)(implicit env: Env): Boolean = {
295+
def unexpectedTree(tree: tpd.Tree)(implicit env: Env): Unit = {
296296
// Assuming that top-level splices can only be in inline methods
297297
// and splices are expanded at inline site, references to inline values
298298
// will be known literal constant trees.
299-
tree.symbol.is(Inline)
299+
if (!tree.symbol.is(Inline))
300+
ctx.error(
301+
"""Malformed macro.
302+
|
303+
|Expected the splice ${...} to contain a single call to a static method.
304+
|
305+
|Where parameters may be:
306+
| * Quoted paramers or fields
307+
| * References to inline parameters
308+
| * Literal values of primitive types
309+
""".stripMargin, tree.sourcePos)
300310
}
301311
}
302312

compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala

Lines changed: 20 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import Trees._
77
import core._
88
import Flags._
99
import Symbols._
10+
import Flags._
1011
import Types._
1112
import Decorators._
1213
import NameKinds._
@@ -245,28 +246,6 @@ object PrepareInlineable {
245246

246247
def checkInlineMacro(sym: Symbol, rhs: Tree, pos: SourcePosition)(implicit ctx: Context) = {
247248
if (!ctx.isAfterTyper) {
248-
249-
/** InlineSplice is used to detect cases where the expansion
250-
* consists of a (possibly multiple & nested) block or a sole expression.
251-
*/
252-
object InlineSplice {
253-
def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match {
254-
case Spliced(code) =>
255-
if (!Splicer.canBeSpliced(code)) {
256-
ctx.error(
257-
"Malformed macro call. The contents of the splice ${...} must call a static method and arguments must be quoted or inline.",
258-
tree.sourcePos)
259-
} else if (code.symbol.flags.is(Inline)) {
260-
ctx.error("Macro cannot be implemented with an `inline` method", code.sourcePos)
261-
}
262-
Some(code)
263-
case Block(List(stat), Literal(Constants.Constant(()))) => unapply(stat)
264-
case Block(Nil, expr) => unapply(expr)
265-
case Typed(expr, _) => unapply(expr)
266-
case _ => None
267-
}
268-
}
269-
270249
var isMacro = false
271250
new TreeMapWithStages(freshStagingContext) {
272251
override protected def transformSplice(body: tpd.Tree, splice: tpd.Tree)(implicit ctx: Context): tpd.Tree = {
@@ -279,10 +258,24 @@ object PrepareInlineable {
279258

280259
if (isMacro) {
281260
sym.setFlag(Macro)
282-
if (level == 0)
283-
rhs match {
284-
case InlineSplice(_) =>
261+
if (level == 0) {
262+
def isValidMacro(tree: Tree)(implicit ctx: Context): Unit = tree match {
263+
case Spliced(code) =>
264+
if (code.symbol.flags.is(Inline))
265+
ctx.error("Macro cannot be implemented with an `inline` method", code.sourcePos)
266+
Splicer.checkValidMacroBody(code)
285267
new PCPCheckAndHeal(freshStagingContext).transform(rhs) // Ignore output, only check PCP
268+
269+
case Block(List(stat), Literal(Constants.Constant(()))) => isValidMacro(stat)
270+
case Block(Nil, expr) => isValidMacro(expr)
271+
case Typed(expr, _) => isValidMacro(expr)
272+
case Block(DefDef(nme.ANON_FUN, _, _, _, _) :: Nil, Closure(_, fn, _)) if fn.symbol.info.isImplicitMethod =>
273+
// TODO Suppot this pattern
274+
ctx.error(
275+
"""Macros using a return type of the form `foo(): given X => Y` are not yet supported.
276+
|
277+
|Place the implicit as an argument (`foo() given X: Y`) to overcome this limitation.
278+
|""".stripMargin, tree.sourcePos)
286279
case _ =>
287280
ctx.error(
288281
"""Malformed macro.
@@ -294,6 +287,8 @@ object PrepareInlineable {
294287
| * All arguments must be quoted or inline
295288
""".stripMargin, pos)
296289
}
290+
isValidMacro(rhs)
291+
}
297292
}
298293
}
299294
}

tests/neg-macros/quote-complex-top-splice.scala

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,23 @@ import scala.quoted._
44

55
object Test {
66

7-
inline def foo1: Unit = ${ // error
8-
val x = 1
7+
inline def foo1: Unit = ${
8+
val x = 1 // error
99
impl(x)
1010
}
1111

12-
inline def foo2: Unit = ${ impl({ // error
13-
val x = 1
12+
inline def foo2: Unit = ${ impl({
13+
val x = 1 // error
1414
x
1515
}) }
1616

17-
inline def foo3: Unit = ${ impl({ // error
18-
println("foo3")
17+
inline def foo3: Unit = ${ impl({
18+
println("foo3") // error
1919
3
2020
}) }
2121

22-
inline def foo4: Unit = ${ // error
23-
println("foo4")
22+
inline def foo4: Unit = ${
23+
println("foo4") // error
2424
impl(1)
2525
}
2626

tests/neg-macros/quote-interpolator-core-old.scala

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import scala.quoted.autolift._
66
object FInterpolation {
77

88
implicit class FInterpolatorHelper(val sc: StringContext) extends AnyVal {
9-
inline def ff(arg1: Any): String = ${fInterpolation(sc, Seq('arg1))} // error: Inline macro method must be a static method
10-
inline def ff(arg1: Any, arg2: Any): String = ${fInterpolation(sc, Seq('arg1, 'arg2))} // error: Inline macro method must be a static method
11-
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
9+
inline def ff(arg1: Any): String = ${fInterpolation(sc, Seq('arg1))} // error // error
10+
inline def ff(arg1: Any, arg2: Any): String = ${fInterpolation(sc, Seq('arg1, 'arg2))} // error // error
11+
inline def ff(arg1: Any, arg2: Any, arg3: Any): String = ${fInterpolation(sc, Seq('arg1, 'arg2, 'arg3))} // error // error
1212
// ...
1313
}
1414

tests/neg-macros/quote-splice-interpret-1.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@
22
import scala.quoted._
33

44
object Macros {
5-
inline def isZero(inline n: Int): Boolean = ${ // error
6-
if (n == 0) 'true
5+
inline def isZero(inline n: Int): Boolean = ${
6+
if (n == 0) 'true // error
77
else 'false
88
}
99
}

tests/neg/i4433.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
object Foo {
3-
inline def g(inline p: Int => Boolean): Boolean = ${ // error
4-
if(p(5)) 'true
3+
inline def g(inline p: Int => Boolean): Boolean = ${
4+
if (p(5)) 'true // error
55
else 'false
66
}
77
}

tests/neg/i4493-b.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class Index[K]
22
object Index {
3-
inline def succ[K](x: K): Unit = ${ // error
4-
implicit val t: quoted.Type[K] = '[K]
3+
inline def succ[K](x: K): Unit = ${
4+
implicit val t: quoted.Type[K] = '[K] // error
55
'{new Index[K]}
66
}
77
}

tests/neg/i4493.scala

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
class Index[K]
22
object Index {
3-
inline def succ[K]: Unit = ${ // error
4-
implicit val t: quoted.Type[K] = '[K]
3+
inline def succ[K]: Unit = ${
4+
implicit val t: quoted.Type[K] = '[K] // error
55
'{new Index[K]}
66
}
77
}

tests/neg/i5840.check

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
-- Error: tests/neg/i5840.scala:8:28 -----------------------------------------------------------------------------------
2+
8 | inline def i5_2[T](n: T): Contextual[T] = ${ foo('n) } // error: Macros using `given X => Y` return types are not yet supported
3+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
4+
| Macros using a return type of the form `foo(): given X => Y` are not yet supported.
5+
|
6+
| Place the implicit as an argument (`foo() given X: Y`) to overcome this limitation.

tests/neg/i5840.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.quoted._
2+
object Test {
3+
4+
type Contextual[T] = given QuoteContext => T
5+
6+
inline def i5_1[T](n: T)(implicit thisCtx: QuoteContext): T = ${ foo('n) } // OK
7+
8+
inline def i5_2[T](n: T): Contextual[T] = ${ foo('n) } // error: Macros using `given X => Y` return types are not yet supported
9+
10+
def foo[T](x: Expr[T]) = x
11+
}

tests/neg/i6783.scala

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import scala.quoted._
22

3-
inline def test(f: (Int, Int) => Int) = ${ // error: Malformed macro
4-
testImpl((a: Expr[Int], b: Expr[Int]) => '{ f(${a}, ${b}) })
3+
inline def test(f: (Int, Int) => Int) = ${
4+
testImpl(
5+
(a: Expr[Int], b: Expr[Int]) => '{ f(${a}, ${b}) } // error: Malformed macro
6+
)
57
}
68

79
def testImpl(f: (Expr[Int], Expr[Int]) => Expr[Int]): Expr[Int] = ???

0 commit comments

Comments
 (0)