Skip to content

Commit 00fd323

Browse files
committed
Fix #4801: WIP
1 parent c9b2a0f commit 00fd323

File tree

7 files changed

+81
-67
lines changed

7 files changed

+81
-67
lines changed

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

Lines changed: 47 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,15 @@ package dotty.tools.dotc
22
package transform
33

44
import core._
5-
import Decorators._, Flags._, Types._, Contexts._, Symbols._, Constants._
5+
import Decorators._
6+
import Flags._
7+
import Types._
8+
import Contexts._
9+
import Symbols._
10+
import Constants._
611
import Flags._
712
import ast.Trees._
8-
import ast.{TreeTypeMap, untpd}
13+
import ast.{TreeTypeMap, tpd, untpd}
914
import util.Positions._
1015
import tasty.TreePickler.Hole
1116
import SymUtils._
@@ -16,6 +21,7 @@ import typer.Implicits.SearchFailureType
1621
import scala.collection.mutable
1722
import dotty.tools.dotc.core.StdNames._
1823
import dotty.tools.dotc.core.quoted._
24+
import dotty.tools.dotc.util.SourcePosition
1925

2026

2127
/** Translates quoted terms and types to `unpickle` method calls.
@@ -78,7 +84,7 @@ class ReifyQuotes extends MacroTransformWithImplicits {
7884
if (ctx.compilationUnit.containsQuotesOrSplices) super.run
7985

8086
protected def newTransformer(implicit ctx: Context): Transformer =
81-
new Reifier(inQuote = false, null, 0, new LevelInfo, new mutable.ListBuffer[Tree])
87+
new Reifier(inQuote = false, null, 0, new LevelInfo, new mutable.ListBuffer[Tree], null)
8288

8389
private class LevelInfo {
8490
/** A map from locally defined symbols to the staging levels of their definitions */
@@ -108,16 +114,22 @@ class ReifyQuotes extends MacroTransformWithImplicits {
108114
* and `l == -1` is code inside a top level splice (in an inline method).
109115
* @param levels a stacked map from symbols to the levels in which they were defined
110116
* @param embedded a list of embedded quotes (if `inSplice = true`) or splices (if `inQuote = true`
117+
* @param inlinedAtPos if non null, the reifier is inside an inlined call with position `inlinedAtPos`
111118
*/
112119
private class Reifier(inQuote: Boolean, val outer: Reifier, val level: Int, levels: LevelInfo,
113-
val embedded: mutable.ListBuffer[Tree]) extends ImplicitsTransformer {
120+
val embedded: mutable.ListBuffer[Tree], inlinedAtPos: SourcePosition) extends ImplicitsTransformer {
114121
import levels._
115122
assert(level >= -1)
116123

117124
/** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */
118125
def nested(isQuote: Boolean): Reifier = {
119126
val nestedEmbedded = if (level > 1 || (level == 1 && isQuote)) embedded else new mutable.ListBuffer[Tree]
120-
new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, nestedEmbedded)
127+
new Reifier(isQuote, this, if (isQuote) level + 1 else level - 1, levels, nestedEmbedded, inlinedAtPos)
128+
}
129+
130+
def inlined(inlinedAt: SourcePosition): Reifier = {
131+
assert(level == 0)
132+
new Reifier(true, this, 0, levels, embedded, inlinedAt)
121133
}
122134

123135
/** We are in a `~(...)` context that is not shadowed by a nested `'(...)` */
@@ -126,6 +138,8 @@ class ReifyQuotes extends MacroTransformWithImplicits {
126138
/** We are not in a `~(...)` or a `'(...)` */
127139
def isRoot: Boolean = outer == null
128140

141+
def isInlined: Boolean = inlinedAtPos != null
142+
129143
/** A map from type ref T to expressions of type `quoted.Type[T]`".
130144
* These will be turned into splices using `addTags` and represent type variables
131145
* that can be possibly healed.
@@ -230,10 +244,6 @@ class ReifyQuotes extends MacroTransformWithImplicits {
230244
!sym.is(Param) || levelOK(sym.owner)
231245
}
232246

233-
/** Issue a "splice outside quote" error unless we are in the body of an inline method */
234-
def spliceOutsideQuotes(pos: Position)(implicit ctx: Context): Unit =
235-
ctx.error(i"splice outside quotes", pos)
236-
237247
/** Try to heal phase-inconsistent reference to type `T` using a local type definition.
238248
* @return None if successful
239249
* @return Some(msg) if unsuccessful where `msg` is a potentially empty error message
@@ -292,7 +302,7 @@ class ReifyQuotes extends MacroTransformWithImplicits {
292302
outer.checkType(pos).foldOver(acc, tp)
293303
}
294304
else {
295-
if (tp.isTerm) spliceOutsideQuotes(pos)
305+
if (tp.isTerm) ctx.error(i"splice outside quotes", pos)
296306
tp
297307
}
298308
case tp: NamedType =>
@@ -418,14 +428,34 @@ class ReifyQuotes extends MacroTransformWithImplicits {
418428
val body1 = nested(isQuote = false).transform(splice.qualifier)
419429
body1.select(splice.name)
420430
}
421-
else if (!inQuote && level == 0 && !ctx.owner.is(Inline)) {
422-
spliceOutsideQuotes(splice.pos)
423-
splice
424-
}
425-
else {
431+
else if (level == 1) {
426432
val (body1, quotes) = nested(isQuote = false).split(splice.qualifier)
427433
makeHole(body1, quotes, splice.tpe).withPos(splice.pos)
428434
}
435+
else if (level == -1) {
436+
// TODO add test
437+
ctx.error("Cannot splice inside a top-level splice", splice.pos)
438+
splice
439+
}
440+
else if (isInlined) { // level 0 in an inline call
441+
val evaluatedSplice = Splicer.splice(splice.qualifier, inlinedAtPos, macroClassLoader).withPos(splice.pos)
442+
if (ctx.reporter.hasErrors) splice else transform(evaluatedSplice)
443+
} else if (ctx.owner.is(Inline)) { // level 0 in an inline definition
444+
if (!Splicer.canBeSpliced(splice.qualifier))
445+
ctx.error( // TODO adapt error message
446+
"""Malformed inline macro.
447+
|
448+
|Expected the ~ to be at the top of the RHS:
449+
| inline def foo(x: X, ..., y: Y): Int = ~impl(x, ... '(y))
450+
|
451+
|The contents of the splice must call a static method. Arguments must be quoted or inlined.
452+
""".stripMargin, splice.pos)
453+
splice
454+
}
455+
else { // level 0
456+
ctx.error(i"splice outside quotes or inline method", splice.pos)
457+
splice
458+
}
429459
}
430460

431461
/** Transforms the contents of a nested splice
@@ -550,37 +580,10 @@ class ReifyQuotes extends MacroTransformWithImplicits {
550580
val last = enteredSyms
551581
stats.foreach(markDef)
552582
mapOverTree(last)
553-
case Inlined(call, bindings, InlineSplice(spliced)) =>
554-
val tree2 =
555-
if (level == 0) {
556-
val evaluatedSplice = Splicer.splice(spliced, tree.pos, macroClassLoader).withPos(tree.pos)
557-
if (ctx.reporter.hasErrors) EmptyTree
558-
else transform(cpy.Inlined(tree)(call, bindings, evaluatedSplice))
559-
}
560-
else super.transform(tree)
561-
562-
// due to value-discarding which converts an { e } into { e; () })
563-
if (tree.tpe =:= defn.UnitType) Block(tree2 :: Nil, Literal(Constant(())))
564-
else tree2
583+
case tree: Inlined if !isInlined && level == 0 =>
584+
inlined(tree.pos).transform(tree)
565585
case _: Import =>
566586
tree
567-
case tree: DefDef if tree.symbol.is(Macro) && level == 0 =>
568-
markDef(tree)
569-
tree.rhs match {
570-
case InlineSplice(_) =>
571-
mapOverTree(enteredSyms) // Ignore output, only check PCP
572-
cpy.DefDef(tree)(rhs = defaultValue(tree.rhs.tpe))
573-
case _ =>
574-
ctx.error(
575-
"""Malformed inline macro.
576-
|
577-
|Expected the ~ to be at the top of the RHS:
578-
| inline def foo(x: X, ..., y: Y): Int = ~impl(x, ... '(y))
579-
|
580-
|The contents of the splice must call a static method. Arguments must be quoted or inlined.
581-
""".stripMargin, tree.rhs.pos)
582-
EmptyTree
583-
}
584587
case _ =>
585588
markDef(tree)
586589
checkLevel(mapOverTree(enteredSyms))
@@ -614,18 +617,6 @@ class ReifyQuotes extends MacroTransformWithImplicits {
614617
acc.select("::".toTermName).appliedToType(tpe).appliedTo(x)
615618
}
616619
}
617-
618-
/** InlineSplice is used to detect cases where the expansion
619-
* consists of a (possibly multiple & nested) block or a sole expression.
620-
*/
621-
object InlineSplice {
622-
def unapply(tree: Tree)(implicit ctx: Context): Option[Tree] = tree match {
623-
case Select(qual, _) if tree.symbol.isSplice && Splicer.canBeSpliced(qual) => Some(qual)
624-
case Block(List(stat), Literal(Constant(()))) => unapply(stat)
625-
case Block(Nil, expr) => unapply(expr)
626-
case _ => None
627-
}
628-
}
629620
}
630621
}
631622

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

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import dotty.tools.dotc.tastyreflect.TastyImpl
2121

2222
import scala.util.control.NonFatal
2323
import dotty.tools.dotc.util.Positions.Position
24+
import dotty.tools.dotc.util.SourcePosition
2425

2526
import scala.reflect.ClassTag
2627

@@ -34,7 +35,7 @@ object Splicer {
3435
*
3536
* See: `ReifyQuotes`
3637
*/
37-
def splice(tree: Tree, pos: Position, classLoader: ClassLoader)(implicit ctx: Context): Tree = tree match {
38+
def splice(tree: Tree, pos: SourcePosition, classLoader: ClassLoader)(implicit ctx: Context): Tree = tree match {
3839
case Quoted(quotedTree) => quotedTree
3940
case _ =>
4041
val interpreter = new Interpreter(pos, classLoader)
@@ -70,7 +71,7 @@ object Splicer {
7071
}
7172

7273
/** Tree interpreter that evaluates the tree */
73-
private class Interpreter(pos: Position, classLoader: ClassLoader)(implicit ctx: Context) extends AbstractInterpreter {
74+
private class Interpreter(pos: SourcePosition, classLoader: ClassLoader)(implicit ctx: Context) extends AbstractInterpreter {
7475

7576
type Res = Object
7677

@@ -210,7 +211,7 @@ object Splicer {
210211
}
211212

212213
/** Exception that stops interpretation if some issue is found */
213-
private class StopInterpretation(val msg: String, val pos: Position) extends Exception
214+
private class StopInterpretation(val msg: String, val pos: SourcePosition) extends Exception
214215

215216
}
216217

tests/neg/quote-exception/Macro_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ object Macro_1 {
44
inline def foo(inline b: Boolean): Unit = ~fooImpl(b)
55
def fooImpl(b: Boolean): Expr[Unit] =
66
if (b) '(println("foo(true)"))
7-
else ???
7+
else ???
88
}

tests/neg/quote-macro-splice.scala

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

33
object Test {
44

5-
inline def foo1: Int = { // error
5+
inline def foo1: Int = {
66
println()
7-
~impl(1.toExpr)
7+
~impl(1.toExpr) // error
88
}
99

10-
inline def foo2: Int = { // error
11-
~impl(1.toExpr)
12-
~impl(2.toExpr)
10+
inline def foo2: Int = {
11+
~impl(1.toExpr) // error
12+
~impl(2.toExpr) // error
1313
}
1414

15-
inline def foo3: Int = { // error
15+
inline def foo3: Int = {
1616
val a = 1
1717
~impl('(a))
1818
}
1919

20-
inline def foo4: Int = { // error
20+
inline def foo4: Int = {
2121
~impl('(1))
2222
1
2323
}

tests/run/i4801.check

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

tests/run/i4801/Macros_1.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.quoted._
2+
3+
object Macro {
4+
5+
inline def foo(b: Boolean): Int = {
6+
if (b) ~bar(true)
7+
else ~bar(false)
8+
}
9+
10+
def bar(b: Boolean): Expr[Int] = if (b) '(1) else '(0)
11+
}

tests/run/i4801/Test_2.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
object Test {
2+
def main(args: Array[String]): Unit = {
3+
println(Macro.foo(true))
4+
println(Macro.foo(false))
5+
val x: Boolean = true
6+
println(Macro.foo(x))
7+
}
8+
}

0 commit comments

Comments
 (0)