Skip to content

Fix #5840: Give more informative error message #6804

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Jul 4, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 27 additions & 17 deletions compiler/src/dotty/tools/dotc/transform/Splicer.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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)
}
}

Expand Down
45 changes: 20 additions & 25 deletions compiler/src/dotty/tools/dotc/typer/PrepareInlineable.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Trees._
import core._
import Flags._
import Symbols._
import Flags._
import Types._
import Decorators._
import NameKinds._
Expand Down Expand Up @@ -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 = {
Expand All @@ -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.
Expand All @@ -294,6 +287,8 @@ object PrepareInlineable {
| * All arguments must be quoted or inline
""".stripMargin, pos)
}
isValidMacro(rhs)
}
}
}
}
Expand Down
16 changes: 8 additions & 8 deletions tests/neg-macros/quote-complex-top-splice.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}

Expand Down
6 changes: 3 additions & 3 deletions tests/neg-macros/quote-interpolator-core-old.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
// ...
}

Expand Down
4 changes: 2 additions & 2 deletions tests/neg-macros/quote-splice-interpret-1.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
4 changes: 2 additions & 2 deletions tests/neg/i4433.scala
Original file line number Diff line number Diff line change
@@ -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
}
}
4 changes: 2 additions & 2 deletions tests/neg/i4493-b.scala
Original file line number Diff line number Diff line change
@@ -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]}
}
}
4 changes: 2 additions & 2 deletions tests/neg/i4493.scala
Original file line number Diff line number Diff line change
@@ -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]}
}
}
6 changes: 6 additions & 0 deletions tests/neg/i5840.check
Original file line number Diff line number Diff line change
@@ -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.
11 changes: 11 additions & 0 deletions tests/neg/i5840.scala
Original file line number Diff line number Diff line change
@@ -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
}
6 changes: 4 additions & 2 deletions tests/neg/i6783.scala
Original file line number Diff line number Diff line change
@@ -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] = ???