Skip to content

Commit a3b3341

Browse files
committed
whitebox macros are now first typechecked against outerPt
Even though whitebox macros are supposed to be used to produce expansions that refine advertised return types of their macro definitions, sometimes those more precise types aren’t picked up by the typechecker. It all started with Travis generating structural types with macros and noticing that typer needs an extra nudge in order to make generated members accessible to the outside world. I didn’t understand the mechanism of the phenomenon back then, and after some time I just gave up. Afterwards, when this issue had been brought up again in a different StackOverflow question, we discussed it at reflection meeting, figured out that typedBlock provides some special treatment to anonymous classes, and it became clear that the first macro typecheck (the one that types the expansion against the return type of the corresponding macro def) is at fault here. The thing is that if we have a block that stands for a desugard anonymous class instantiation, and we typecheck it with expected type different from WildcardType, then typer isn’t going to include decls of the anonymous class in the resulting structural type: https://github.com/scala/scala/blob/master/src/compiler/scala/tools/nsc/typechecker/Typers.scala#L2350. I tried to figure it out at https://groups.google.com/forum/#!topic/scala-internals/eXQt-BPm4i8, but couldn’t dispel the mystery, so again I just gave up. But today I had a profound WAT experience that finally tipped the scales. It turns out that if we typecheck an if, providing a suitable pt, then the resulting type of an if is going to be that pt, even though the lub of the branch types might be more precise. I’m sure that reasons for this behavior are also beyond my understanding, so I decided to sidestep this problem. upd. Here’s Jason’s clarification: Doing thing differently would require us to believe that "'Tis better to have lubbed and lost than never to have lubbed at all." But the desire for efficiency trumps such sentimentality. Now expansions of whitebox macros are first typechecked against outerPt, the expected type that comes from the enclosing context, before being typechecked against innerPt, the expected type that comes from the return type of the macro def. This means that now outerPt provides the correct expected type for the initial, most important typecheck, which makes types more precise.
1 parent bd615c6 commit a3b3341

File tree

10 files changed

+153
-2
lines changed

10 files changed

+153
-2
lines changed

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -639,8 +639,8 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
639639
typecheck("blackbox typecheck #2", expanded1, outerPt)
640640
} else {
641641
val expanded1 = expanded0
642-
val expanded2 = typecheck("whitebox typecheck #1", expanded1, innerPt)
643-
typecheck("whitebox typecheck #2", expanded2, outerPt)
642+
val expanded2 = typecheck("whitebox typecheck #1", expanded1, outerPt)
643+
typecheck("whitebox typecheck #2", expanded2, innerPt)
644644
}
645645
}
646646
override def onDelayed(delayed: Tree) = {

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)