Skip to content

Commit b5ef79f

Browse files
committed
Merge pull request scala#3236 from xeno-by/topic/wildbox-macros
(2.11.0-M8) whitebox macros are now first typechecked against outerPt
2 parents dbe7a36 + ca2dbe5 commit b5ef79f

File tree

10 files changed

+185
-27
lines changed

10 files changed

+185
-27
lines changed

src/compiler/scala/tools/nsc/typechecker/Macros.scala

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -600,39 +600,46 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
600600
}
601601

602602
/** Expands a term macro used in apply role as `M(2)(3)` in `val x = M(2)(3)`.
603+
* @param outerPt Expected type that comes from enclosing context (something that's traditionally called `pt`).
604+
* @param innerPt Expected type that comes from the signature of a macro def, possibly wildcarded to help type inference.
603605
* @see MacroExpander
604606
*/
605-
def macroExpandApply(typer: Typer, expandee: Tree, mode: Mode, pt: Type): Tree = {
606-
object expander extends TermMacroExpander(APPLY_ROLE, typer, expandee, mode, pt) {
607-
override def onSuccess(expanded0: Tree) = {
608-
def approximate(tp: Type) = {
607+
def macroExpandApply(typer: Typer, expandee: Tree, mode: Mode, outerPt: Type): Tree = {
608+
object expander extends TermMacroExpander(APPLY_ROLE, typer, expandee, mode, outerPt) {
609+
lazy val innerPt = {
610+
val tp = if (isNullaryInvocation(expandee)) expandee.tpe.finalResultType else expandee.tpe
611+
if (isBlackbox(expandee)) tp
612+
else {
609613
// approximation is necessary for whitebox macros to guide type inference
610614
// read more in the comments for onDelayed below
611-
if (isBlackbox(expandee)) tp
615+
val undetparams = tp collect { case tp if tp.typeSymbol.isTypeParameter => tp.typeSymbol }
616+
deriveTypeWithWildcards(undetparams)(tp)
617+
}
618+
}
619+
override def onSuccess(expanded0: Tree) = {
620+
// prematurely annotate the tree with a macro expansion attachment
621+
// so that adapt called indirectly by typer.typed knows that it needs to apply the existential fixup
622+
linkExpandeeAndExpanded(expandee, expanded0)
623+
624+
def typecheck(label: String, tree: Tree, pt: Type): Tree = {
625+
if (tree.isErrorTyped) tree
612626
else {
613-
val undetparams = tp collect { case tp if tp.typeSymbol.isTypeParameter => tp.typeSymbol }
614-
deriveTypeWithWildcards(undetparams)(tp)
627+
if (macroDebugVerbose) println(s"$label (against pt = $pt): $tree")
628+
// `macroExpandApply` is called from `adapt`, where implicit conversions are disabled
629+
// therefore we need to re-enable the conversions back temporarily
630+
val result = typer.context.withImplicitsEnabled(typer.typed(tree, mode, pt))
631+
if (result.isErrorTyped && macroDebugVerbose) println(s"$label has failed: ${typer.context.reportBuffer.errors}")
632+
result
615633
}
616634
}
617-
val macroPt = approximate(if (isNullaryInvocation(expandee)) expandee.tpe.finalResultType else expandee.tpe)
618-
val expanded = if (isBlackbox(expandee)) atPos(enclosingMacroPosition.focus)(Typed(expanded0, TypeTree(macroPt))) else expanded0
619635

620-
// prematurely annotate the tree with a macro expansion attachment
621-
// so that adapt called indirectly by typer.typed knows that it needs to apply the existential fixup
622-
linkExpandeeAndExpanded(expandee, expanded)
623-
624-
// `macroExpandApply` is called from `adapt`, where implicit conversions are disabled
625-
// therefore we need to re-enable the conversions back temporarily
626-
if (macroDebugVerbose) println(s"typecheck #1 (against macroPt = $macroPt): $expanded")
627-
val expanded1 = typer.context.withImplicitsEnabled(typer.typed(expanded, mode, macroPt))
628-
if (expanded1.isErrorTyped) {
629-
if (macroDebugVerbose) println(s"typecheck #1 has failed: ${typer.context.reportBuffer.errors}")
630-
expanded1
636+
if (isBlackbox(expandee)) {
637+
val expanded1 = atPos(enclosingMacroPosition.focus)(Typed(expanded0, TypeTree(innerPt)))
638+
typecheck("blackbox typecheck", expanded1, outerPt)
631639
} else {
632-
if (macroDebugVerbose) println(s"typecheck #2 (against pt = $pt): $expanded1")
633-
val expanded2 = typer.context.withImplicitsEnabled(super.onSuccess(expanded1))
634-
if (macroDebugVerbose && expanded2.isErrorTyped) println(s"typecheck #2 has failed: ${typer.context.reportBuffer.errors}")
635-
expanded2
640+
val expanded1 = expanded0
641+
val expanded2 = typecheck("whitebox typecheck #1", expanded1, outerPt)
642+
typecheck("whitebox typecheck #2", expanded2, innerPt)
636643
}
637644
}
638645
override def onDelayed(delayed: Tree) = {
@@ -686,11 +693,11 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
686693
// Thanks to that the materializer can take a look at what's going on and react accordingly.
687694
val shouldInstantiate = typer.context.undetparams.nonEmpty && !mode.inPolyMode
688695
if (shouldInstantiate) {
689-
if (isBlackbox(expandee)) typer.instantiatePossiblyExpectingUnit(delayed, mode, pt)
696+
if (isBlackbox(expandee)) typer.instantiatePossiblyExpectingUnit(delayed, mode, outerPt)
690697
else {
691698
forced += delayed
692-
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), pt, keepNothings = false)
693-
macroExpandApply(typer, delayed, mode, pt)
699+
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), outerPt, keepNothings = false)
700+
macroExpandApply(typer, delayed, mode, outerPt)
694701
}
695702
} else delayed
696703
}

test/files/run/t6992.check

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

test/files/run/t6992/Macros_1.scala

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import scala.language.experimental.macros
2+
import scala.reflect.macros.Context
3+
4+
object Macros {
5+
def foo(name: String): Any = macro foo_impl
6+
def foo_impl(c: Context)(name: c.Expr[String]) = {
7+
import c.universe._
8+
9+
val Literal(Constant(lit: String)) = name.tree
10+
val anon = newTypeName(c.fresh)
11+
12+
c.Expr(Block(
13+
ClassDef(
14+
Modifiers(Flag.FINAL), anon, Nil, Template(
15+
Nil, noSelfType, List(
16+
DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))),
17+
TypeDef(Modifiers(), TypeName(lit), Nil, TypeTree(typeOf[Int]))
18+
)
19+
)
20+
),
21+
Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
22+
))
23+
}
24+
25+
def bar(name: String): Any = macro bar_impl
26+
def bar_impl(c: Context)(name: c.Expr[String]) = {
27+
import c.universe._
28+
29+
val Literal(Constant(lit: String)) = name.tree
30+
val anon = newTypeName(c.fresh)
31+
32+
c.Expr(Block(
33+
ClassDef(
34+
Modifiers(Flag.FINAL), anon, Nil, Template(
35+
Nil, noSelfType, List(
36+
DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))),
37+
DefDef(
38+
Modifiers(), TermName(lit), Nil, Nil, TypeTree(),
39+
c.literal(42).tree
40+
)
41+
)
42+
)
43+
),
44+
Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
45+
))
46+
}
47+
48+
def baz(name: String): Any = macro baz_impl
49+
def baz_impl(c: Context)(name: c.Expr[String]) = {
50+
import c.universe._
51+
52+
val Literal(Constant(lit: String)) = name.tree
53+
val anon = newTypeName(c.fresh)
54+
val wrapper = newTypeName(c.fresh)
55+
56+
c.Expr(Block(
57+
ClassDef(
58+
Modifiers(), anon, Nil, Template(
59+
Nil, emptyValDef, List(
60+
DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))),
61+
DefDef(
62+
Modifiers(), TermName(lit), Nil, Nil, TypeTree(),
63+
c.literal(42).tree
64+
)
65+
)
66+
)
67+
),
68+
ClassDef(
69+
Modifiers(Flag.FINAL), wrapper, Nil,
70+
Template(Ident(anon) :: Nil, noSelfType, DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))) :: Nil)
71+
),
72+
Apply(Select(New(Ident(wrapper)), nme.CONSTRUCTOR), Nil)
73+
))
74+
}
75+
}

test/files/run/t6992/Test_2.scala

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import scala.language.reflectiveCalls
2+
3+
object Test extends App {
4+
val foo = Macros.foo("T")
5+
println(scala.reflect.runtime.universe.weakTypeOf[foo.T].typeSymbol.typeSignature)
6+
7+
val bar = Macros.bar("test")
8+
println(bar.test)
9+
10+
val baz = Macros.baz("test")
11+
println(baz.test)
12+
}

test/files/run/t8048a.check

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Some(2)

test/files/run/t8048a/Macros_1.scala

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import scala.reflect.macros.WhiteboxContext
2+
import scala.language.experimental.macros
3+
4+
object Macros {
5+
def impl(c: WhiteboxContext) = {
6+
import c.universe._
7+
q"if (true) Some(2) else None"
8+
}
9+
10+
def foo: Any = macro impl
11+
}

test/files/run/t8048a/Test_2.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
object Test extends App {
2+
val x: Option[Int] = Macros.foo
3+
println(x)
4+
}

test/files/run/t8048b.check

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

test/files/run/t8048b/Macros_1.scala

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
// see the following discussions to understand what's being tested here:
2+
// * https://issues.scala-lang.org/browse/SI-6992
3+
// * https://issues.scala-lang.org/browse/SI-8048
4+
// * http://stackoverflow.com/questions/14370842/getting-a-structural-type-with-an-anonymous-classs-methods-from-a-macro
5+
// * http://stackoverflow.com/questions/18480707/method-cannot-be-accessed-in-macro-generated-class/18485004#18485004
6+
// * https://groups.google.com/forum/#!topic/scala-internals/eXQt-BPm4i8
7+
8+
import scala.language.experimental.macros
9+
import scala.reflect.macros.WhiteboxContext
10+
11+
object Macros {
12+
def impl1(c: WhiteboxContext) = {
13+
import c.universe._
14+
q"""
15+
trait Foo { def x = 2 }
16+
new Foo {}
17+
"""
18+
}
19+
def foo1: Any = macro impl1
20+
21+
def impl2(c: WhiteboxContext) = {
22+
import c.universe._
23+
q"""
24+
class Foo { def x = 2 }
25+
new Foo
26+
"""
27+
}
28+
def foo2: Any = macro impl2
29+
30+
def impl3(c: WhiteboxContext) = {
31+
import c.universe._
32+
q"""
33+
new { def x = 2 }
34+
"""
35+
}
36+
def foo3: Any = macro impl3
37+
}

test/files/run/t8048b/Test_2.scala

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
object Test extends App {
2+
println(Macros.foo1.x)
3+
println(Macros.foo2.x)
4+
println(Macros.foo3.x)
5+
}

0 commit comments

Comments
 (0)