Skip to content

Commit 33919f2

Browse files
committed
Expand macros in typer
This adds support for whitebox macros
1 parent 68ad6b3 commit 33919f2

29 files changed

+299
-111
lines changed

compiler/src/dotty/tools/dotc/ast/Desugar.scala

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1313,10 +1313,6 @@ object desugar {
13131313
val desugared = tree match {
13141314
case SymbolLit(str) =>
13151315
Literal(Constant(scala.Symbol(str)))
1316-
case Splice(expr) => // TODO move to typer and track level
1317-
Select(expr, nme.splice)
1318-
case TypSplice(expr) => // TODO move to typer and track level
1319-
Select(expr, tpnme.splice)
13201316
case InterpolatedString(id, segments) =>
13211317
val strs = segments map {
13221318
case ts: Thicket => ts.trees.head
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package dotty.tools.dotc.core
2+
3+
import dotty.tools.dotc.core.Contexts._
4+
import dotty.tools.dotc.util.Property
5+
6+
import scala.collection.mutable
7+
8+
object StagingContext {
9+
10+
/** A key to be used in a context property that tracks the quoteation level */
11+
private val QuotationLevel = new Property.Key[Int]
12+
13+
/** All enclosing calls that are currently inlined, from innermost to outermost. */
14+
def level(implicit ctx: Context): Int =
15+
ctx.property(QuotationLevel).getOrElse(0)
16+
17+
/** Context with an incremented quotation level. */
18+
def quoteContext(implicit ctx: Context): Context =
19+
ctx.fresh.setProperty(QuotationLevel, level + 1)
20+
21+
/** Context with a decremented quotation level. */
22+
def spliceContext(implicit ctx: Context): Context =
23+
ctx.fresh.setProperty(QuotationLevel, level - 1)
24+
25+
}

compiler/src/dotty/tools/dotc/core/quoted/PickledQuotes.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ object PickledQuotes {
145145
case Block(stats, expr) =>
146146
seq(stats, rec(expr)).withSpan(fn.span)
147147
case _ =>
148-
fn.select(nme.apply).appliedToArgs(argRefs())
148+
fn.select(nme.apply).appliedToArgs(argRefs()).withSpan(fn.span)
149149
}
150150
Block(argVals, rec(fn))
151151
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ class ReifyQuotes extends MacroTransform {
105105
private class QuoteReifier(outer: QuoteReifier, capturers: mutable.HashMap[Symbol, Tree => Tree],
106106
val embedded: Embedded, val owner: Symbol)(@constructorOnly ictx: Context) extends TreeMapWithStages(ictx) { self =>
107107

108-
import TreeMapWithStages._
108+
import StagingContext._
109109

110110
/** A nested reifier for a quote (if `isQuote = true`) or a splice (if not) */
111111
def nested(isQuote: Boolean)(implicit ctx: Context): QuoteReifier = {

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

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import dotty.tools.dotc.ast.tpd
88
import dotty.tools.dotc.ast.Trees._
99
import dotty.tools.dotc.core.Contexts._
1010
import dotty.tools.dotc.core.Decorators._
11-
import dotty.tools.dotc.core.Flags._
11+
import dotty.tools.dotc.core.Flags.{Method => MethodFlag, _}
1212
import dotty.tools.dotc.core.NameKinds.FlatName
1313
import dotty.tools.dotc.core.Names.{Name, TermName}
1414
import dotty.tools.dotc.core.StdNames._
@@ -95,7 +95,7 @@ object Splicer {
9595
}
9696

9797
protected def interpretQuote(tree: Tree)(implicit env: Env): Object =
98-
new scala.quoted.Exprs.TastyTreeExpr(tree)
98+
new scala.quoted.Exprs.TastyTreeExpr(Inlined(EmptyTree, Nil, tree).withSpan(tree.span))
9999

100100
protected def interpretTypeQuote(tree: Tree)(implicit env: Env): Object =
101101
new scala.quoted.Types.TreeType(tree)
@@ -312,7 +312,13 @@ object Splicer {
312312

313313
protected final def interpretTree(tree: Tree)(implicit env: Env): Result = tree match {
314314
case Apply(TypeApply(fn, _), quoted :: Nil) if fn.symbol == defn.QuotedExpr_apply =>
315-
interpretQuote(quoted)
315+
val quoted1 = quoted match {
316+
case quoted: Ident if quoted.symbol.is(InlineProxy) && quoted.symbol.is(MethodFlag) => // inline proxy for by-name parameter
317+
quoted.symbol.defTree.asInstanceOf[DefDef].rhs
318+
case Inlined(EmptyTree, _, quoted) => quoted
319+
case _ => quoted
320+
}
321+
interpretQuote(quoted1)
316322

317323
case TypeApply(fn, quoted :: Nil) if fn.symbol == defn.QuotedType_apply =>
318324
interpretTypeQuote(quoted)
@@ -336,6 +342,8 @@ object Splicer {
336342
interpretStaticMethodCall(module, fn.symbol, args.map(arg => interpretTree(arg)))
337343
} else if (env.contains(fn.name)) {
338344
env(fn.name)
345+
} else if (tree.symbol.is(InlineProxy)) {
346+
interpretTree(tree.symbol.defTree.asInstanceOf[ValOrDefDef].rhs)
339347
} else {
340348
unexpectedTree(tree)
341349
}

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

Lines changed: 14 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import dotty.tools.dotc.core.Decorators._
99
import dotty.tools.dotc.core.Flags._
1010
import dotty.tools.dotc.core.quoted._
1111
import dotty.tools.dotc.core.NameKinds._
12+
import dotty.tools.dotc.core.StagingContext._
1213
import dotty.tools.dotc.core.StdNames._
1314
import dotty.tools.dotc.core.Symbols._
1415
import dotty.tools.dotc.core.tasty.TreePickler.Hole
@@ -89,16 +90,6 @@ object Staging {
8990
import tpd._
9091
import PCPCheckAndHeal._
9192

92-
/** Classloader used for loading macros */
93-
private[this] var myMacroClassLoader: java.lang.ClassLoader = _
94-
private def macroClassLoader(implicit ctx: Context): ClassLoader = {
95-
if (myMacroClassLoader == null) {
96-
val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL)
97-
myMacroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader)
98-
}
99-
myMacroClassLoader
100-
}
101-
10293
override def transform(tree: Tree)(implicit ctx: Context): Tree = tree match {
10394
case tree: DefDef if tree.symbol.is(Inline) && level > 0 => EmptyTree
10495
case _ => checkLevel(super.transform(tree))
@@ -122,25 +113,19 @@ object Staging {
122113
if (splice1.isType) splice1
123114
else addSpliceCast(splice1)
124115
}
125-
else if (enclosingInlineds.nonEmpty) { // level 0 in an inlined call
126-
val spliceCtx = ctx.outer // drop the last `inlineContext`
127-
val pos: SourcePosition = spliceCtx.source.atSpan(enclosingInlineds.head.span)
128-
val evaluatedSplice = Splicer.splice(splice.qualifier, pos, macroClassLoader)(spliceCtx)
129-
if (ctx.reporter.hasErrors) splice else transform(evaluatedSplice.withSpan(splice.span))
130-
}
131-
else if (!ctx.owner.isInlineMethod) { // level 0 outside an inline method
132-
ctx.error(i"splice outside quotes or inline method", splice.sourcePos)
133-
splice
134-
}
135-
else if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition
136-
transform(splice.qualifier)(spliceContext) // Just check PCP
137-
splice
138-
}
139-
else { // level 0 inside an inline definition
140-
ctx.error(
141-
"Malformed macro call. The contents of the ~ must call a static method and arguments must be quoted or inline.",
142-
splice.sourcePos)
143-
splice
116+
else {
117+
assert(!enclosingInlineds.nonEmpty, "unexpanded macro")
118+
assert(ctx.owner.isInlineMethod)
119+
if (Splicer.canBeSpliced(splice.qualifier)) { // level 0 inside an inline definition
120+
transform(splice.qualifier)(spliceContext) // Just check PCP
121+
splice
122+
}
123+
else { // level 0 inside an inline definition
124+
ctx.error(
125+
"Malformed macro call. The contents of the $ must call a static method and arguments must be quoted or inline.",
126+
splice.sourcePos)
127+
splice
128+
}
144129
}
145130
}
146131

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

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import dotty.tools.dotc.core.quoted._
1010
import dotty.tools.dotc.core.NameKinds._
1111
import dotty.tools.dotc.core.Types._
1212
import dotty.tools.dotc.core.Contexts._
13+
import dotty.tools.dotc.core.StagingContext._
1314
import dotty.tools.dotc.core.StdNames._
1415
import dotty.tools.dotc.core.Symbols._
1516
import dotty.tools.dotc.core.tasty.TreePickler.Hole
@@ -129,21 +130,6 @@ object TreeMapWithStages {
129130
/** A key to be used in a context property that caches the `levelOf` mapping */
130131
private val LevelOfKey = new Property.Key[mutable.HashMap[Symbol, Int]]
131132

132-
/** A key to be used in a context property that tracks the quoteation level */
133-
private val QuotationLevel = new Property.Key[Int]
134-
135-
/** All enclosing calls that are currently inlined, from innermost to outermost. */
136-
def level(implicit ctx: Context): Int =
137-
ctx.property(QuotationLevel).getOrElse(0)
138-
139-
/** Context with an incremented quotation level. */
140-
def quoteContext(implicit ctx: Context): Context =
141-
ctx.fresh.setProperty(QuotationLevel, level + 1)
142-
143-
/** Context with a decremented quotation level. */
144-
def spliceContext(implicit ctx: Context): Context =
145-
ctx.fresh.setProperty(QuotationLevel, level - 1)
146-
147133
/** Initial context for a StagingTransformer transformation. */
148134
def freshStagingContext(implicit ctx: Context): Context =
149135
ctx.fresh.setProperty(LevelOfKey, new mutable.HashMap[Symbol, Int])

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@ abstract class Lifter {
4747
var liftedType = expr.tpe.widen
4848
if (liftedFlags.is(Method)) liftedType = ExprType(liftedType)
4949
val lifted = ctx.newSymbol(ctx.owner, name, liftedFlags | Synthetic, liftedType, coord = spanCoord(expr.span))
50-
defs += liftedDef(lifted, expr).withSpan(expr.span)
50+
val ddef = liftedDef(lifted, expr).withSpan(expr.span)
51+
lifted.defTree = ddef
52+
defs += ddef
5153
ref(lifted.termRef).withSpan(expr.span.focus)
5254
}
5355

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

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package dotty.tools
22
package dotc
33
package typer
44

5-
import ast._
5+
import ast.{TreeInfo, tpd, _}
66
import Trees._
77
import core._
88
import Flags._
@@ -25,6 +25,7 @@ import dotty.tools.dotc.util.{SimpleIdentityMap, SimpleIdentitySet, SourceFile,
2525
import collection.mutable
2626
import reporting.trace
2727
import util.Spans.Span
28+
import dotty.tools.dotc.transform.{Splicer, TreeMapWithStages}
2829

2930
object Inliner {
3031
import tpd._
@@ -104,7 +105,7 @@ object Inliner {
104105
else if (enclosingInlineds.length < ctx.settings.XmaxInlines.value) {
105106
val body = bodyToInline(tree.symbol) // can typecheck the tree and thereby produce errors
106107
if (ctx.reporter.hasErrors) tree
107-
else new Inliner(tree, body).inlined(pt)
108+
else new Inliner(tree, body).inlined(pt, tree.sourcePos)
108109
}
109110
else
110111
errorTree(
@@ -381,7 +382,7 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
381382
}
382383

383384
/** The Inlined node representing the inlined call */
384-
def inlined(pt: Type): Tree = {
385+
def inlined(pt: Type, sourcePos: SourcePosition): Tree = {
385386

386387
if (callTypeArgs.length == 1)
387388
if (inlinedMethod == defn.Compiletime_constValue) {
@@ -487,7 +488,13 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
487488
trace(i"inlining $call", inlining, show = true) {
488489

489490
// The normalized bindings collected in `bindingsBuf`
490-
bindingsBuf.transform(reducer.normalizeBinding(_)(inlineCtx))
491+
bindingsBuf.transform { binding =>
492+
val transformedBinding = reducer.normalizeBinding(binding)(inlineCtx)
493+
// Set trees to symbols allow macros to see the definition tree.
494+
// This is used by `underlyingArgument`.
495+
transformedBinding.symbol.defTree = transformedBinding
496+
transformedBinding
497+
}
491498

492499
// Run a typing pass over the inlined tree. See InlineTyper for details.
493500
val expansion1 = inlineTyper.typed(expansion, pt)(inlineCtx)
@@ -949,11 +956,25 @@ class Inliner(call: tpd.Tree, rhsToInline: tpd.Tree)(implicit ctx: Context) {
949956
override def typedSelect(tree: untpd.Select, pt: Type)(implicit ctx: Context): Tree = {
950957
assert(tree.hasType, tree)
951958
val qual1 = typed(tree.qualifier, selectionProto(tree.name, pt, this))
952-
val res = untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt)
959+
val res =
960+
if (tree.symbol == defn.QuotedExpr_splice && StagingContext.level == 0) expandMacro(qual1, tree.span)
961+
else untpd.cpy.Select(tree)(qual1, tree.name).withType(tree.typeOpt)
953962
ensureAccessible(res.tpe, tree.qualifier.isInstanceOf[untpd.Super], tree.sourcePos)
954963
res
955964
}
956965

966+
private def expandMacro(body: Tree, span: Span)(implicit ctx: Context) = {
967+
assert(StagingContext.level == 0)
968+
// TODO cache macro classloader
969+
val urls = ctx.settings.classpath.value.split(java.io.File.pathSeparatorChar).map(cp => java.nio.file.Paths.get(cp).toUri.toURL)
970+
val macroClassLoader = new java.net.URLClassLoader(urls, getClass.getClassLoader)
971+
972+
val inlinedFrom = enclosingInlineds.last
973+
val evaluatedSplice = Splicer.splice(body, inlinedFrom.sourcePos, macroClassLoader)(ctx.withSource(inlinedFrom.source))
974+
if (ctx.reporter.hasErrors) EmptyTree
975+
else evaluatedSplice.withSpan(span)
976+
}
977+
957978
override def typedIf(tree: untpd.If, pt: Type)(implicit ctx: Context): Tree =
958979
typed(tree.cond, defn.BooleanType) match {
959980
case cond1 @ ConstantValue(b: Boolean) =>

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

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ import config.Printers.{gadts, typr}
3737
import rewrites.Rewrites.patch
3838
import NavigateAST._
3939
import dotty.tools.dotc.transform.{PCPCheckAndHeal, Staging, TreeMapWithStages}
40+
import dotty.tools.dotc.core.StagingContext._
4041
import transform.SymUtils._
4142
import transform.TypeUtils._
4243
import reporting.trace
@@ -1567,7 +1568,7 @@ class Typer extends Namer
15671568

15681569
if (isMacro) {
15691570
sym.setFlag(Macro)
1570-
if (TreeMapWithStages.level == 0)
1571+
if (level == 0)
15711572
rhs1 match {
15721573
case InlineSplice(_) =>
15731574
new PCPCheckAndHeal(freshStagingContext).transform(rhs1) // Ignore output, only check PCP
@@ -1960,14 +1961,33 @@ class Typer extends Namer
19601961
}
19611962
}
19621963

1963-
/** Translate `'(expr)`/`'{ expr* }` into `scala.quoted.Expr.apply(expr)` and `'[T]` into `scala.quoted.Type.apply[T]`
1964+
/** Translate '{ t }` into `scala.quoted.Expr.apply(t)` and `'[T]` into `scala.quoted.Type.apply[T]`
19641965
* while tracking the quotation level in the context.
19651966
*/
19661967
def typedQuote(tree: untpd.Quote, pt: Type)(implicit ctx: Context): Tree = track("typedQuote") {
1967-
if (tree.t.isType)
1968-
typedTypeApply(untpd.TypeApply(untpd.ref(defn.QuotedType_applyR), List(tree.t)), pt)(TreeMapWithStages.quoteContext).withSpan(tree.span)
1969-
else
1970-
typedApply(untpd.Apply(untpd.ref(defn.QuotedExpr_applyR), tree.t), pt)(TreeMapWithStages.quoteContext).withSpan(tree.span)
1968+
val tree1 =
1969+
if (tree.t.isType)
1970+
typedTypeApply(untpd.TypeApply(untpd.ref(defn.QuotedType_applyR), List(tree.t)), pt)(quoteContext)
1971+
else
1972+
typedApply(untpd.Apply(untpd.ref(defn.QuotedExpr_applyR), tree.t), pt)(quoteContext)
1973+
tree1.withSpan(tree.span)
1974+
}
1975+
1976+
/** Translate `${ t: Expr[T] }` into expresiion `t.splice` while tracking the quotation level in the context */
1977+
def typedSplice(tree: untpd.Splice, pt: Type)(implicit ctx: Context): Tree = track("typedSplice") {
1978+
checkSpliceOutsideQuote(tree)
1979+
typedSelect(untpd.Select(tree.expr, nme.splice), pt)(spliceContext).withSpan(tree.span)
1980+
}
1981+
1982+
/** Translate ${ t: Type[T] }` into type `t.splice` while tracking the quotation level in the context */
1983+
def typedTypSplice(tree: untpd.TypSplice, pt: Type)(implicit ctx: Context): Tree = track("typedTypSplice") {
1984+
checkSpliceOutsideQuote(tree)
1985+
typedSelect(untpd.Select(tree.expr, tpnme.splice), pt)(spliceContext).withSpan(tree.span)
1986+
}
1987+
1988+
private def checkSpliceOutsideQuote(tree: untpd.Tree)(implicit ctx: Context): Unit = {
1989+
if (level == 0 && !ctx.owner.isInlineMethod)
1990+
ctx.error("splice outside quotes or inline method", tree.sourcePos)
19711991
}
19721992

19731993
/** Retrieve symbol attached to given tree */
@@ -2061,6 +2081,8 @@ class Typer extends Namer
20612081
case tree @ untpd.PostfixOp(qual, Ident(nme.WILDCARD)) => typedAsFunction(tree, pt)
20622082
case untpd.EmptyTree => tpd.EmptyTree
20632083
case tree: untpd.Quote => typedQuote(tree, pt)
2084+
case tree: untpd.Splice => typedSplice(tree, pt)
2085+
case tree: untpd.TypSplice => typedTypSplice(tree, pt)
20642086
case _ => typedUnadapted(desugar(tree), pt, locked)
20652087
}
20662088

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import scala.quoted._
2+
3+
object Macros {
4+
inline def defaultOf(inline str: String) <: Any = ${ defaultOfImpl(str) }
5+
def defaultOfImpl(str: String): Expr[Any] = str match {
6+
case "int" => '{1}
7+
case "string" => '{"a"}
8+
}
9+
}

tests/neg/quote-whitebox/Test_2.scala

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import Macros._
2+
3+
object Test {
4+
def main(args: Array[String]): Unit = {
5+
val a: String = defaultOf("int") // error
6+
val b: Int = defaultOf("string") // error
7+
}
8+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
2+
import Asserts._
3+
4+
object Test {
5+
def main(args: Array[String]): Unit = {
6+
macroAssert(true === "cde")
7+
macroAssert("acb" === "cde") // error
8+
}
9+
10+
}

0 commit comments

Comments
 (0)