Skip to content

Commit 6038bac

Browse files
xeno-byadriaanm
authored andcommitted
blackbox restriction #2: can't guide type inference
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.
1 parent a2b523a commit 6038bac

File tree

12 files changed

+100
-5
lines changed

12 files changed

+100
-5
lines changed

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

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -678,17 +678,20 @@ trait Macros extends FastTrack with MacroRuntimes with Traces with Helpers {
678678
// e.g. for Foo it will be Int :: String :: Boolean :: HNil), there's no way to convey this information
679679
// to the typechecker. Therefore the typechecker will infer Nothing for L, which is hardly what we want.
680680
//
681-
// =========== THE SOLUTION ===========
681+
// =========== THE SOLUTION (ENABLED ONLY FOR WHITEBOX MACROS) ===========
682682
//
683683
// To give materializers a chance to say their word before vanilla inference kicks in,
684684
// we infer as much as possible (e.g. in the example above even though L is hopeless, C still can be inferred to Foo)
685685
// and then trigger macro expansion with the undetermined type parameters still there.
686686
// Thanks to that the materializer can take a look at what's going on and react accordingly.
687687
val shouldInstantiate = typer.context.undetparams.nonEmpty && !mode.inPolyMode
688688
if (shouldInstantiate) {
689-
forced += delayed
690-
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), pt, keepNothings = false)
691-
macroExpandApply(typer, delayed, mode, pt)
689+
if (isBlackbox(expandee)) typer.instantiatePossiblyExpectingUnit(delayed, mode, pt)
690+
else {
691+
forced += delayed
692+
typer.infer.inferExprInstance(delayed, typer.context.extractUndetparams(), pt, keepNothings = false)
693+
macroExpandApply(typer, delayed, mode, pt)
694+
}
692695
} else delayed
693696
}
694697
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Test_2.scala:7: Iso.materializeIso is not a valid implicit value for Iso[Test.Foo,L] because:
2+
hasMatchingSymbol reported error: type mismatch;
3+
found : Iso[Test.Foo,(Int, String, Boolean)]
4+
required: Iso[Test.Foo,Nothing]
5+
Note: (Int, String, Boolean) >: Nothing, but trait Iso is invariant in type U.
6+
You may wish to define U as -U instead. (SLS 4.5)
7+
val equiv = foo(Foo(23, "foo", true))
8+
^
9+
Test_2.scala:7: error: could not find implicit value for parameter iso: Iso[Test.Foo,L]
10+
val equiv = foo(Foo(23, "foo", true))
11+
^
12+
one error found
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
-Xlog-implicits
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
import scala.language.experimental.macros
2+
import scala.reflect.macros.BlackboxContext
3+
4+
trait Iso[T, U] {
5+
def to(t : T) : U
6+
// def from(u : U) : T
7+
}
8+
9+
object Iso {
10+
implicit def materializeIso[T, U]: Iso[T, U] = macro impl[T, U]
11+
def impl[T: c.WeakTypeTag, U: c.WeakTypeTag](c: BlackboxContext): c.Expr[Iso[T, U]] = {
12+
import c.universe._
13+
import definitions._
14+
import Flag._
15+
16+
val sym = c.weakTypeOf[T].typeSymbol
17+
if (!sym.isClass || !sym.asClass.isCaseClass) c.abort(c.enclosingPosition, s"$sym is not a case class")
18+
val fields = sym.typeSignature.declarations.toList.collect{ case x: TermSymbol if x.isVal && x.isCaseAccessor => x }
19+
20+
def mkTpt() = {
21+
val core = Ident(TupleClass(fields.length) orElse UnitClass)
22+
if (fields.length == 0) core
23+
else AppliedTypeTree(core, fields map (f => TypeTree(f.typeSignature)))
24+
}
25+
26+
def mkFrom() = {
27+
if (fields.length == 0) Literal(Constant(Unit))
28+
else Apply(Ident(newTermName("Tuple" + fields.length)), fields map (f => Select(Ident(newTermName("f")), newTermName(f.name.toString.trim))))
29+
}
30+
31+
val evidenceClass = ClassDef(Modifiers(FINAL), newTypeName("$anon"), List(), Template(
32+
List(AppliedTypeTree(Ident(newTypeName("Iso")), List(Ident(sym), mkTpt()))),
33+
emptyValDef,
34+
List(
35+
DefDef(Modifiers(), nme.CONSTRUCTOR, List(), List(List()), TypeTree(), Block(List(Apply(Select(Super(This(tpnme.EMPTY), tpnme.EMPTY), nme.CONSTRUCTOR), List())), Literal(Constant(())))),
36+
DefDef(Modifiers(), newTermName("to"), List(), List(List(ValDef(Modifiers(PARAM), newTermName("f"), Ident(sym), EmptyTree))), TypeTree(), mkFrom()))))
37+
c.Expr[Iso[T, U]](Block(List(evidenceClass), Apply(Select(New(Ident(newTypeName("$anon"))), nme.CONSTRUCTOR), List())))
38+
}
39+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
C(Int)
2+
C(String)
3+
C(Nothing)
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// For the full version of the test, take a look at run/t5923a
2+
3+
import scala.reflect.macros.BlackboxContext
4+
import language.experimental.macros
5+
6+
case class C[T](t: String)
7+
object C {
8+
implicit def foo[T]: C[T] = macro Macros.impl[T]
9+
}
10+
11+
object Macros {
12+
def impl[T: c.WeakTypeTag](c: BlackboxContext) = {
13+
import c.universe._
14+
reify(C[T](c.literal(weakTypeOf[T].toString).splice))
15+
}
16+
}
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(implicitly[C[Int]])
3+
println(implicitly[C[String]])
4+
println(implicitly[C[Nothing]])
5+
}

test/files/run/t5923c/Macros_1.scala renamed to test/files/run/macro-whitebox-fundep-materialization/Macros_1.scala

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import language.experimental.macros
1+
import scala.language.experimental.macros
22
import scala.reflect.macros.WhiteboxContext
33

44
trait Iso[T, U] {
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// see the comments for macroExpandApply.onDelayed for an explanation of what's tested here
2+
object Test extends App {
3+
case class Foo(i: Int, s: String, b: Boolean)
4+
def foo[C, L](c: C)(implicit iso: Iso[C, L]): L = iso.to(c)
5+
6+
{
7+
val equiv = foo(Foo(23, "foo", true))
8+
def typed[T](t: => T) {}
9+
typed[(Int, String, Boolean)](equiv)
10+
println(equiv)
11+
}
12+
}

test/files/run/t5923c.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// see neg/macro-blackbox-fundep-materialization and run/macro-whitebox-fundep-materialization
2+
object Test extends App {
3+
// do nothing
4+
}

0 commit comments

Comments
 (0)