Skip to content

Commit aa8d7ea

Browse files
committed
Modify inline macro parameter semantics to allow inline functions
1 parent 429c0a4 commit aa8d7ea

File tree

7 files changed

+40
-27
lines changed

7 files changed

+40
-27
lines changed

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

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -66,16 +66,19 @@ import dotty.tools.dotc.core.quoted._
6666
* ```
6767
* to
6868
* ```
69-
* inline def foo[T1, ...](inline x1: X, ..., y1: Y, ....): Seq[Any] => Object = { (args: Seq[Any]) => {
69+
* inline def foo[T1, ...](inline x1: X, ..., y1: Y, ..., inline f1: X => Y): Seq[Any] => Object = { (args: Seq[Any]) => {
7070
* val T1$1 = args(0).asInstanceOf[Type[T1]]
7171
* ...
7272
* val x1$1 = args(0).asInstanceOf[X]
7373
* ...
7474
* val y1$1 = args(1).asInstanceOf[Expr[Y]]
7575
* ...
76-
* { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... } ... }
76+
* { ... x1$1 .... '{ ... T1$1.unary_~ ... x1$1.toExpr.unary_~ ... y1$1.unary_~ ... f1$1.unary_~ ... } ... }
7777
* }
7878
* ```
79+
* Where `inline` parameters with type Boolean, Byte, Short, Int, Long, Float, Double, Char and String are
80+
* passed as their actual runtime value. See `isStage0Value`.
81+
*
7982
* Note: the parameters of `foo` are kept for simple overloading resolution but they are not used in the body of `foo`.
8083
*
8184
* 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 {
243246
def levelOK(sym: Symbol)(implicit ctx: Context): Boolean = levelOf.get(sym) match {
244247
case Some(l) =>
245248
l == level ||
246-
sym.is(Inline) && sym.owner.is(Macro) && sym.info.isValueType && l - 1 == level
249+
l == 1 && level == 0 && isStage0Value(sym)
247250
case None =>
248251
!sym.is(Param) || levelOK(sym.owner)
249252
}
@@ -374,8 +377,8 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
374377
else ref(defn.QuotedExpr_apply).appliedToType(body1.tpe.widen).appliedTo(body1)
375378
}
376379
else body match {
377-
case body: RefTree if isCaptured(body, level + 1) =>
378-
if (body.symbol.is(Inline)) {
380+
case body: RefTree if isCaptured(body.symbol, level + 1) =>
381+
if (isStage0Value(body.symbol)) {
379382
// Optimization: avoid the full conversion when capturing inlined `x`
380383
// in '{ x } to '{ x$1.toExpr.unary_~ } and go directly to `x$1.toExpr`
381384
liftValue(capturers(body.symbol)(body))
@@ -476,7 +479,7 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
476479
val tpw = tree.tpe.widen
477480
val argTpe =
478481
if (tree.isType) defn.QuotedTypeType.appliedTo(tpw)
479-
else if (tree.symbol.is(Inline)) tpw // inlined term
482+
else if (isStage0Value(tree.symbol)) tpw
480483
else defn.QuotedExprType.appliedTo(tpw)
481484
val selectArg = arg.select(nme.apply).appliedTo(Literal(Constant(i))).asInstance(argTpe)
482485
val capturedArg = SyntheticValDef(UniqueName.fresh(tree.symbol.name.toTermName).toTermName, selectArg)
@@ -509,11 +512,11 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
509512
}
510513

511514
/** Returns true if this tree will be captured by `makeLambda` */
512-
private def isCaptured(tree: RefTree, level: Int)(implicit ctx: Context): Boolean = {
515+
private def isCaptured(sym: Symbol, level: Int)(implicit ctx: Context): Boolean = {
513516
// Check phase consistency and presence of capturer
514-
( (level == 1 && levelOf.get(tree.symbol).contains(1)) ||
515-
(level == 0 && tree.symbol.is(Inline))
516-
) && capturers.contains(tree.symbol)
517+
( (level == 1 && levelOf.get(sym).contains(1)) ||
518+
(level == 0 && isStage0Value(sym))
519+
) && capturers.contains(sym)
517520
}
518521

519522
/** Transform `tree` and return the resulting tree and all `embedded` quotes
@@ -550,13 +553,13 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
550553
splice(ref(splicedType).select(tpnme.UNARY_~))
551554
case tree: Select if tree.symbol.isSplice =>
552555
splice(tree)
553-
case tree: RefTree if isCaptured(tree, level) =>
556+
case tree: RefTree if isCaptured(tree.symbol, level) =>
554557
val capturer = capturers(tree.symbol)
555558
def captureAndSplice(t: Tree) =
556559
splice(t.select(if (tree.isTerm) nme.UNARY_~ else tpnme.UNARY_~))
557-
if (tree.symbol.is(Inline) && level == 0) capturer(tree)
558-
else if (tree.symbol.is(Inline)) captureAndSplice(liftValue(capturer(tree)))
559-
else captureAndSplice(capturer(tree))
560+
if (!isStage0Value(tree.symbol)) captureAndSplice(capturer(tree))
561+
else if (level == 0) capturer(tree)
562+
else captureAndSplice(liftValue(capturer(tree)))
560563
case Block(stats, _) =>
561564
val last = enteredSyms
562565
stats.foreach(markDef)
@@ -604,10 +607,6 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
604607
""".stripMargin, tree.rhs.pos)
605608
EmptyTree
606609
}
607-
case tree: ValDef if tree.symbol.is(Param | Inline) && tree.symbol.owner.is(Macro) &&
608-
defn.isFunctionClass(tree.tpe.widen.typeSymbol) =>
609-
ctx.error("Macro parameters of function type cannot be `inline`", tree.pos)
610-
EmptyTree
611610
case _ =>
612611
markDef(tree)
613612
checkLevel(mapOverTree(enteredSyms))
@@ -629,6 +628,9 @@ class ReifyQuotes extends MacroTransformWithImplicits with InfoTransformer {
629628
}
630629
}
631630

631+
private def isStage0Value(sym: Symbol)(implicit ctx: Context): Boolean =
632+
sym.is(Inline) && sym.owner.is(Macro) && !defn.isFunctionType(sym.info)
633+
632634
private def liftList(list: List[Tree], tpe: Type)(implicit ctx: Context): Tree = {
633635
list.foldRight[Tree](ref(defn.NilModule)) { (x, acc) =>
634636
acc.select("::".toTermName).appliedToType(tpe).appliedTo(x)

docs/docs/reference/principled-meta-programming.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -394,9 +394,10 @@ point. To reflect this, we loosen the phase consistency requirements
394394
as follows:
395395

396396
- If `x` is an inline value (or an inline parameter of an inline
397-
function), it can be accessed in all contexts where the number of
398-
splices minus the number of quotes between use and definition is
399-
either 0 or 1.
397+
function) of type Boolean, Byte, Short, Int, Long, Float, Double,
398+
Char or String, it can be accessed in all contexts where the number
399+
of splices minus the number of quotes between use and definition
400+
is either 0 or 1.
400401

401402
### Relationship with Staging
402403

tests/neg/i4431.scala

Lines changed: 0 additions & 4 deletions
This file was deleted.

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/run/i4431.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
abc42

tests/run/i4431/quoted_1.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import scala.quoted._
2+
3+
object Macros {
4+
inline def h(inline f: Int => String): String = ~ '(f(42))
5+
}

tests/run/i4431/quoted_2.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import scala.quoted._
2+
import Macros._
3+
4+
object Test {
5+
def main(args: Array[String]): Unit = {
6+
println(h(x => "abc" + x))
7+
}
8+
}

0 commit comments

Comments
 (0)