From 429c0a4e610f61a52c8061280b938861bda1fba6 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 4 May 2018 12:49:20 +0200 Subject: [PATCH 1/3] Fix #4431: Disallow macro parameters that are inline functions --- compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 4 ++++ tests/neg/i4431.scala | 4 ++++ tests/neg/i4433.scala | 7 +++++++ 3 files changed, 15 insertions(+) create mode 100644 tests/neg/i4431.scala create mode 100644 tests/neg/i4433.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 3751c1a4d4a7..be9174897cc2 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -604,6 +604,10 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { """.stripMargin, tree.rhs.pos) EmptyTree } + case tree: ValDef if tree.symbol.is(Param | Inline) && tree.symbol.owner.is(Macro) && + defn.isFunctionClass(tree.tpe.widen.typeSymbol) => + ctx.error("Macro parameters of function type cannot be `inline`", tree.pos) + EmptyTree case _ => markDef(tree) checkLevel(mapOverTree(enteredSyms)) diff --git a/tests/neg/i4431.scala b/tests/neg/i4431.scala new file mode 100644 index 000000000000..d67eb7cf30d1 --- /dev/null +++ b/tests/neg/i4431.scala @@ -0,0 +1,4 @@ + +object Foo { + inline def h(inline f: Int => String): String = ~ '(f(42)) // error +} diff --git a/tests/neg/i4433.scala b/tests/neg/i4433.scala new file mode 100644 index 000000000000..1c7eb18d9785 --- /dev/null +++ b/tests/neg/i4433.scala @@ -0,0 +1,7 @@ + +object Foo { + inline def g(inline p: Int => Boolean): Boolean = ~{ // error + if(p(5)) '(true) + else '(false) + } +} From aa8d7ea7a9f6002361c851aac7c3f5cc58c6c521 Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Fri, 4 May 2018 13:42:28 +0200 Subject: [PATCH 2/3] Modify inline macro parameter semantics to allow inline functions --- .../tools/dotc/transform/ReifyQuotes.scala | 38 ++++++++++--------- .../reference/principled-meta-programming.md | 7 ++-- tests/neg/i4431.scala | 4 -- tests/neg/i4433.scala | 4 +- tests/run/i4431.check | 1 + tests/run/i4431/quoted_1.scala | 5 +++ tests/run/i4431/quoted_2.scala | 8 ++++ 7 files changed, 40 insertions(+), 27 deletions(-) delete mode 100644 tests/neg/i4431.scala create mode 100644 tests/run/i4431.check create mode 100644 tests/run/i4431/quoted_1.scala create mode 100644 tests/run/i4431/quoted_2.scala diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index be9174897cc2..312105a00d70 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -66,16 +66,19 @@ import dotty.tools.dotc.core.quoted._ * ``` * to * ``` - * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (args: Seq[Any]) => { + * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ..., inline f1: X => Y): Seq[Any] => Object = { (args: Seq[Any]) => { * val T1$1 = args(0).asInstanceOf[Type[T1]] * ... * val x1$1 = args(0).asInstanceOf[X] * ... * val y1$1 = args(1).asInstanceOf[Expr[Y]] * ... - * { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } + * { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... f1$1.unary_~ ... } ... } * } * ``` + * Where `inline` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are + * passed as their actual runtime value. See `isStage0Value`. + * * Note: the parameters of `foo` are kept for simple overloading resolution but they are not used in the body of `foo`. * * At inline site we will call reflectively the static method `foo` with dummy parameters, which will return a @@ -243,7 +246,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { def levelOK(sym: Symbol)(implicit ctx: Context): Boolean = levelOf.get(sym) match { case Some(l) => l == level || - sym.is(Inline) && sym.owner.is(Macro) && sym.info.isValueType && l - 1 == level + l == 1 && level == 0 && isStage0Value(sym) case None => !sym.is(Param) || levelOK(sym.owner) } @@ -374,8 +377,8 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { else ref(defn.QuotedExpr_apply).appliedToType(body1.tpe.widen).appliedTo(body1) } else body match { - case body: RefTree if isCaptured(body, level + 1) => - if (body.symbol.is(Inline)) { + case body: RefTree if isCaptured(body.symbol, level + 1) => + if (isStage0Value(body.symbol)) { // Optimization: avoid the full conversion when capturing inlined `x` // in '{ x } to '{ x$1.toExpr.unary_~ } and go directly to `x$1.toExpr` liftValue(capturers(body.symbol)(body)) @@ -476,7 +479,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { val tpw = tree.tpe.widen val argTpe = if (tree.isType) defn.QuotedTypeType.appliedTo(tpw) - else if (tree.symbol.is(Inline)) tpw // inlined term + else if (isStage0Value(tree.symbol)) tpw else defn.QuotedExprType.appliedTo(tpw) val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argTpe) val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg) @@ -509,11 +512,11 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { } /** Returns true if this tree will be captured by `makeLambda` */ - private def isCaptured(tree: RefTree, level: Int)(implicit ctx: Context): Boolean = { + private def isCaptured(sym: Symbol, level: Int)(implicit ctx: Context): Boolean = { // Check phase consistency and presence of capturer - ( (level == 1 && levelOf.get(tree.symbol).contains(1)) || - (level == 0 && tree.symbol.is(Inline)) - ) && capturers.contains(tree.symbol) + ( (level == 1 && levelOf.get(sym).contains(1)) || + (level == 0 && isStage0Value(sym)) + ) && capturers.contains(sym) } /** Transform `tree` and return the resulting tree and all `embedded` quotes @@ -550,13 +553,13 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { splice(ref(splicedType).select(tpnme.UNARY_~)) case tree: Select if tree.symbol.isSplice => splice(tree) - case tree: RefTree if isCaptured(tree, level) => + case tree: RefTree if isCaptured(tree.symbol, level) => val capturer = capturers(tree.symbol) def captureAndSplice(t: Tree) = splice(t.select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~)) - if (tree.symbol.is(Inline) && level == 0) capturer(tree) - else if (tree.symbol.is(Inline)) captureAndSplice(liftValue(capturer(tree))) - else captureAndSplice(capturer(tree)) + if (!isStage0Value(tree.symbol)) captureAndSplice(capturer(tree)) + else if (level == 0) capturer(tree) + else captureAndSplice(liftValue(capturer(tree))) case Block(stats, _) => val last = enteredSyms stats.foreach(markDef) @@ -604,10 +607,6 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { """.stripMargin, tree.rhs.pos) EmptyTree } - case tree: ValDef if tree.symbol.is(Param | Inline) && tree.symbol.owner.is(Macro) && - defn.isFunctionClass(tree.tpe.widen.typeSymbol) => - ctx.error("Macro parameters of function type cannot be `inline`", tree.pos) - EmptyTree case _ => markDef(tree) checkLevel(mapOverTree(enteredSyms)) @@ -629,6 +628,9 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer { } } + private def isStage0Value(sym: Symbol)(implicit ctx: Context): Boolean = + sym.is(Inline) && sym.owner.is(Macro) && !defn.isFunctionType(sym.info) + private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = { list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) => acc.select("::".toTermName).appliedToType(tpe).appliedTo(x) diff --git a/docs/docs/reference/principled-meta-programming.md b/docs/docs/reference/principled-meta-programming.md index 84abc9a234a9..c77c8c238b17 100644 --- a/docs/docs/reference/principled-meta-programming.md +++ b/docs/docs/reference/principled-meta-programming.md @@ -394,9 +394,10 @@ point. To reflect this, we loosen the phase consistency requirements as follows: - If `x` is an inline value (or an inline parameter of an inline -function), it can be accessed in all contexts where the number of -splices minus the number of quotes between use and definition is -either 0 or 1. + function) of type Boolean, Byte, Short, Int, Long, Float, Double, + Char or String, it can be accessed in all contexts where the number + of splices minus the number of quotes between use and definition + is either 0 or 1. ### Relationship with Staging diff --git a/tests/neg/i4431.scala b/tests/neg/i4431.scala deleted file mode 100644 index d67eb7cf30d1..000000000000 --- a/tests/neg/i4431.scala +++ /dev/null @@ -1,4 +0,0 @@ - -object Foo { - inline def h(inline f: Int => String): String = ~ '(f(42)) // error -} diff --git a/tests/neg/i4433.scala b/tests/neg/i4433.scala index 1c7eb18d9785..49237a6e8ac5 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/run/i4431.check b/tests/run/i4431.check new file mode 100644 index 000000000000..520e6a167da0 --- /dev/null +++ b/tests/run/i4431.check @@ -0,0 +1 @@ +abc42 diff --git a/tests/run/i4431/quoted_1.scala b/tests/run/i4431/quoted_1.scala new file mode 100644 index 000000000000..b4e89dbbc512 --- /dev/null +++ b/tests/run/i4431/quoted_1.scala @@ -0,0 +1,5 @@ +import scala.quoted._ + +object Macros { + inline def h(inline f: Int => String): String = ~ '(f(42)) +} diff --git a/tests/run/i4431/quoted_2.scala b/tests/run/i4431/quoted_2.scala new file mode 100644 index 000000000000..00cdb38258d1 --- /dev/null +++ b/tests/run/i4431/quoted_2.scala @@ -0,0 +1,8 @@ +import scala.quoted._ +import Macros._ + +object Test { + def main(args: Array[String]): Unit = { + println(h(x => "abc" + x)) + } +} From b75714f88dabdc8fe41447ffdc648d31d9e6a76a Mon Sep 17 00:00:00 2001 From: Nicolas Stucki Date: Tue, 8 May 2018 13:50:32 +0200 Subject: [PATCH 3/3] Update doc --- compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala index 312105a00d70..9ac5e09c2d77 100644 --- a/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala +++ b/compiler/src/dotty/tools/dotc/transform/ReifyQuotes.scala @@ -66,18 +66,19 @@ import dotty.tools.dotc.core.quoted._ * ``` * to * ``` - * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ..., inline f1: X => Y): Seq[Any] => Object = { (args: Seq[Any]) => { + * inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (args: Seq[Any]) => { * val T1$1 = args(0).asInstanceOf[Type[T1]] * ... * val x1$1 = args(0).asInstanceOf[X] * ... * val y1$1 = args(1).asInstanceOf[Expr[Y]] * ... - * { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... f1$1.unary_~ ... } ... } + * { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... } * } * ``` * Where `inline` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are - * passed as their actual runtime value. See `isStage0Value`. + * passed as their actual runtime value. See `isStage0Value`. Other `inline` arguments such as functions are handled + * like `y1: Y`. * * Note: the parameters of `foo` are kept for simple overloading resolution but they are not used in the body of `foo`. *