Skip to content

Commit

Permalink
whitebox macros are now first typechecked against outerPt
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
xeno-by committed Dec 10, 2013
1 parent bd615c6 commit a3b3341
Show file tree
Hide file tree
Showing 10 changed files with 153 additions and 2 deletions.
4 changes: 2 additions & 2 deletions src/compiler/scala/tools/nsc/typechecker/Macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -639,8 +639,8 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
typecheck("blackbox typecheck #2", expanded1, outerPt)
} else {
val expanded1 = expanded0
val expanded2 = typecheck("whitebox typecheck #1", expanded1, innerPt)
typecheck("whitebox typecheck #2", expanded2, outerPt)
val expanded2 = typecheck("whitebox typecheck #1", expanded1, outerPt)
typecheck("whitebox typecheck #2", expanded2, innerPt)
}
}
override def onDelayed(delayed: Tree) = {
Expand Down
3 changes: 3 additions & 0 deletions test/files/run/t6992.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Int
42
42
75 changes: 75 additions & 0 deletions test/files/run/t6992/Macros_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import scala.language.experimental.macros
import scala.reflect.macros.Context

object Macros {
def foo(name: String): Any = macro foo_impl
def foo_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._

val Literal(Constant(lit: String)) = name.tree
val anon = newTypeName(c.fresh)

c.Expr(Block(
ClassDef(
Modifiers(Flag.FINAL), anon, Nil, Template(
Nil, noSelfType, List(
DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))),
TypeDef(Modifiers(), TypeName(lit), Nil, TypeTree(typeOf[Int]))
)
)
),
Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
))
}

def bar(name: String): Any = macro bar_impl
def bar_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._

val Literal(Constant(lit: String)) = name.tree
val anon = newTypeName(c.fresh)

c.Expr(Block(
ClassDef(
Modifiers(Flag.FINAL), anon, Nil, Template(
Nil, noSelfType, List(
DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))),
DefDef(
Modifiers(), TermName(lit), Nil, Nil, TypeTree(),
c.literal(42).tree
)
)
)
),
Apply(Select(New(Ident(anon)), nme.CONSTRUCTOR), Nil)
))
}

def baz(name: String): Any = macro baz_impl
def baz_impl(c: Context)(name: c.Expr[String]) = {
import c.universe._

val Literal(Constant(lit: String)) = name.tree
val anon = newTypeName(c.fresh)
val wrapper = newTypeName(c.fresh)

c.Expr(Block(
ClassDef(
Modifiers(), anon, Nil, Template(
Nil, emptyValDef, List(
DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))),
DefDef(
Modifiers(), TermName(lit), Nil, Nil, TypeTree(),
c.literal(42).tree
)
)
)
),
ClassDef(
Modifiers(Flag.FINAL), wrapper, Nil,
Template(Ident(anon) :: Nil, noSelfType, DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(pendingSuperCall), Literal(Constant(())))) :: Nil)
),
Apply(Select(New(Ident(wrapper)), nme.CONSTRUCTOR), Nil)
))
}
}
12 changes: 12 additions & 0 deletions test/files/run/t6992/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import scala.language.reflectiveCalls

object Test extends App {
val foo = Macros.foo("T")
println(scala.reflect.runtime.universe.weakTypeOf[foo.T].typeSymbol.typeSignature)

val bar = Macros.bar("test")
println(bar.test)

val baz = Macros.baz("test")
println(baz.test)
}
1 change: 1 addition & 0 deletions test/files/run/t8048a.check
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Some(2)
11 changes: 11 additions & 0 deletions test/files/run/t8048a/Macros_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import scala.reflect.macros.WhiteboxContext
import scala.language.experimental.macros

object Macros {
def impl(c: WhiteboxContext) = {
import c.universe._
q"if (true) Some(2) else None"
}

def foo: Any = macro impl
}
4 changes: 4 additions & 0 deletions test/files/run/t8048a/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
object Test extends App {
val x: Option[Int] = Macros.foo
println(x)
}
3 changes: 3 additions & 0 deletions test/files/run/t8048b.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
2
2
2
37 changes: 37 additions & 0 deletions test/files/run/t8048b/Macros_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// see the following discussions to understand what's being tested here:
// * https://issues.scala-lang.org/browse/SI-6992
// * https://issues.scala-lang.org/browse/SI-8048
// * http://stackoverflow.com/questions/14370842/getting-a-structural-type-with-an-anonymous-classs-methods-from-a-macro
// * http://stackoverflow.com/questions/18480707/method-cannot-be-accessed-in-macro-generated-class/18485004#18485004
// * https://groups.google.com/forum/#!topic/scala-internals/eXQt-BPm4i8

import scala.language.experimental.macros
import scala.reflect.macros.WhiteboxContext

object Macros {
def impl1(c: WhiteboxContext) = {
import c.universe._
q"""
trait Foo { def x = 2 }
new Foo {}
"""
}
def foo1: Any = macro impl1

def impl2(c: WhiteboxContext) = {
import c.universe._
q"""
class Foo { def x = 2 }
new Foo
"""
}
def foo2: Any = macro impl2

def impl3(c: WhiteboxContext) = {
import c.universe._
q"""
new { def x = 2 }
"""
}
def foo3: Any = macro impl3
}
5 changes: 5 additions & 0 deletions test/files/run/t8048b/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test extends App {
println(Macros.foo1.x)
println(Macros.foo2.x)
println(Macros.foo3.x)
}

0 comments on commit a3b3341

Please sign in to comment.