Skip to content

Commit

Permalink
blackbox restriction #2: can't guide type inference
Browse files Browse the repository at this point in the history
When an application of a blackbox macro still has undetermined type
parameters after Scala’s type inference algorithm has finished working,
these type parameters are inferred forcedly, in exactly the same manner
as type inference happens for normal methods.

This makes it impossible for blackbox macros to influence type inference,
prohibiting fundep materialization.
  • Loading branch information
xeno-by authored and adriaanm committed Nov 13, 2013
1 parent a2b523a commit 6038bac
Show file tree
Hide file tree
Showing 12 changed files with 100 additions and 5 deletions.
11 changes: 7 additions & 4 deletions src/compiler/scala/tools/nsc/typechecker/Macros.scala
Original file line number Diff line number Diff line change
Expand Up @@ -678,17 +678,20 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
// e.g. for Foo it will be Int :: String :: Boolean :: HNil), there's no way to convey this information
// to the typechecker. Therefore the typechecker will infer Nothing for L, which is hardly what we want.
//
// =========== THE SOLUTION ===========
// =========== THE SOLUTION (ENABLED ONLY FOR WHITEBOX MACROS) ===========
//
// To give materializers a chance to say their word before vanilla inference kicks in,
// we infer as much as possible (e.g. in the example above even though L is hopeless, C still can be inferred to Foo)
// and then trigger macro expansion with the undetermined type parameters still there.
// Thanks to that the materializer can take a look at what's going on and react accordingly.
val shouldInstantiate = typer.context.undetparams.nonEmpty && !mode.inPolyMode
if (shouldInstantiate) {
forced += delayed
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), pt, keepNothings = false)
macroExpandApply(typer, delayed, mode, pt)
if (isBlackbox(expandee)) typer.instantiatePossiblyExpectingUnit(delayed, mode, pt)
else {
forced += delayed
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), pt, keepNothings = false)
macroExpandApply(typer, delayed, mode, pt)
}
} else delayed
}
}
Expand Down
12 changes: 12 additions & 0 deletions test/files/neg/macro-blackbox-fundep-materialization.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Test_2.scala:7: Iso.materializeIso is not a valid implicit value for Iso[Test.Foo,L] because:
hasMatchingSymbol reported error: type mismatch;
found : Iso[Test.Foo,(Int, String, Boolean)]
required: Iso[Test.Foo,Nothing]
Note: (Int, String, Boolean) >: Nothing, but trait Iso is invariant in type U.
You may wish to define U as -U instead. (SLS 4.5)
val equiv = foo(Foo(23, "foo", true))
^
Test_2.scala:7: error: could not find implicit value for parameter iso: Iso[Test.Foo,L]
val equiv = foo(Foo(23, "foo", true))
^
one error found
1 change: 1 addition & 0 deletions test/files/neg/macro-blackbox-fundep-materialization.flags
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
-Xlog-implicits
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import scala.language.experimental.macros
import scala.reflect.macros.BlackboxContext

trait Iso[T, U] {
def to(t : T) : U
// def from(u : U) : T
}

object Iso {
implicit def materializeIso[T, U]: Iso[T, U] = macro impl[T, U]
def impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: BlackboxContext): c.Expr[Iso[T, U]] = {
import c.universe._
import definitions._
import Flag._

val sym = c.weakTypeOf[T].typeSymbol
if (!sym.isClass || !sym.asClass.isCaseClass) c.abort(c.enclosingPosition, s"$sym is not a case class")
val fields = sym.typeSignature.declarations.toList.collect{ case x: TermSymbol if x.isVal && x.isCaseAccessor => x }

def mkTpt() = {
val core = Ident(TupleClass(fields.length) orElse UnitClass)
if (fields.length == 0) core
else AppliedTypeTree(core, fields map (f => TypeTree(f.typeSignature)))
}

def mkFrom() = {
if (fields.length == 0) Literal(Constant(Unit))
else Apply(Ident(newTermName("Tuple" + fields.length)), fields map (f => Select(Ident(newTermName("f")), newTermName(f.name.toString.trim))))
}

val evidenceClass = ClassDef(Modifiers(FINAL), newTypeName("$anon"), List(), Template(
List(AppliedTypeTree(Ident(newTypeName("Iso")), List(Ident(sym), mkTpt()))),
emptyValDef,
List(
DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))),
DefDef(Modifiers(), newTermName("to"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("f"), Ident(sym), EmptyTree))), TypeTree(), mkFrom()))))
c.Expr[Iso[T, U]](Block(List(evidenceClass), Apply(Select(New(Ident(newTypeName("$anon"))), nme.CONSTRUCTOR), List())))
}
}
File renamed without changes.
3 changes: 3 additions & 0 deletions test/files/run/macro-blackbox-materialization.check
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
C(Int)
C(String)
C(Nothing)
16 changes: 16 additions & 0 deletions test/files/run/macro-blackbox-materialization/Macros_1.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// For the full version of the test, take a look at run/t5923a

import scala.reflect.macros.BlackboxContext
import language.experimental.macros

case class C[T](t: String)
object C {
implicit def foo[T]: C[T] = macro Macros.impl[T]
}

object Macros {
def impl[T: c.WeakTypeTag](c: BlackboxContext) = {
import c.universe._
reify(C[T](c.literal(weakTypeOf[T].toString).splice))
}
}
5 changes: 5 additions & 0 deletions test/files/run/macro-blackbox-materialization/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
object Test extends App {
println(implicitly[C[Int]])
println(implicitly[C[String]])
println(implicitly[C[Nothing]])
}
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import language.experimental.macros
import scala.language.experimental.macros
import scala.reflect.macros.WhiteboxContext

trait Iso[T, U] {
Expand Down
12 changes: 12 additions & 0 deletions test/files/run/macro-whitebox-fundep-materialization/Test_2.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// see the comments for macroExpandApply.onDelayed for an explanation of what's tested here
object Test extends App {
case class Foo(i: Int, s: String, b: Boolean)
def foo[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.to(c)

{
val equiv = foo(Foo(23, "foo", true))
def typed[T](t: => T) {}
typed[(Int, String, Boolean)](equiv)
println(equiv)
}
}
4 changes: 4 additions & 0 deletions test/files/run/t5923c.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// see neg/macro-blackbox-fundep-materialization and run/macro-whitebox-fundep-materialization
object Test extends App {
// do nothing
}

0 comments on commit 6038bac

Please sign in to comment.