Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[backport] SI-7470 implements fundep materialization
Backports 21a8c6c from the 2.11.x branch under -Xfundep-materialization as per Miles Sabin's request. Thanks Miles!
- Loading branch information
Showing
19 changed files
with
214 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import language.experimental.macros | ||
import scala.reflect.macros.Context | ||
|
||
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: Context): 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()))) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
-Xfundep-materialization |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
import scala.reflect.macros.Context | ||
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: Context)(ttag: c.WeakTypeTag[T]) = { | ||
import c.universe._ | ||
val ttag0 = ttag; | ||
{ | ||
// When we're expanding implicitly[C[Nothing]], the type inferencer will see | ||
// that foo[T] returns C[T] and that we request an implicit of type C[Nothing]. | ||
// | ||
// Then the type inferencer will try to match C[T] against C[Nothing] and infer everything it can infer | ||
// from that match, but not more (e.g. if we were returning Iso[T, U] and the type we were looking at was Iso[Foo, L], | ||
// we wouldn't want U to be auto-inferred to Nothing, as it usually happens with normal methods, | ||
// but would rather want it to remain unknown, so that our macro could take a stab at inferring it: | ||
// see the comments in this commit for more information). | ||
// | ||
// Equipped with common sense, in our case of C[T] and C[Nothing] we would expect T to be inferred as Nothing, and then we | ||
// would expect T in the corresponding macro invocation to be Nothing. Unfortunately it is not that simple. | ||
// | ||
// Internally the type inferencer uses Nothing as a dummy value, which stands for "don't know how to | ||
// infer this type parameter". In the Iso example, matching Iso[T, U] against Iso[Foo, L] would result in | ||
// T being inferred as Foo and U being inferred as Nothing (!!). Then the type inferencer will think: | ||
// "Aha! U ended up being Nothing. This means that I failed to infer it, | ||
// therefore the result of my work is: T -> Foo, U -> still unknown". | ||
// | ||
// That's all very good and works very well until Nothing is a genuine result of type inference, | ||
// as in our original example of inferring T in C[T] from C[Nothing]. In that case, the inferencer becomes confused | ||
// and here in the macro implementation we get weakTypeOf[T] equal to some dummy type carrying a type parameter | ||
// instead of Nothing. | ||
// | ||
// This eccentric behavior of the type inferencer is a long-standing problem in scalac, | ||
// so the best one can do for now until it's fixed is to work around, manually converting | ||
// suspicious T's into Nothings. Of course, this means that we would have to approximate, | ||
// because there's no way to know whether having T here stands for a failed attempt to infer Nothing | ||
// or for a failed attempt to infer anything, but at least we're in full control of making the best | ||
// of this sad situation. | ||
implicit def ttag: WeakTypeTag[T] = { | ||
val tpe = ttag0.tpe | ||
val sym = tpe.typeSymbol.asType | ||
if (sym.isParameter && !sym.isSkolem) TypeTag.Nothing.asInstanceOf[TypeTag[T]] | ||
else ttag0 | ||
} | ||
reify(C[T](c.literal(weakTypeOf[T].toString).splice)) | ||
} | ||
} | ||
} |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
C(Int) | ||
C(String) | ||
C(Nothing) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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]]) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
(23,foo,true) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
-Xfundep-materialization |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
import language.experimental.macros | ||
import scala.reflect.macros.Context | ||
|
||
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: Context): 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()))) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} | ||
} |
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
import scala.language.experimental.macros | ||
import scala.reflect.macros.Context | ||
|
||
trait MappedRow | ||
trait RowMapper[T <: MappedRow] | ||
object RowMapper { | ||
implicit def mapper[T <: MappedRow]: RowMapper[T] = macro impl[T] | ||
def impl[T <: MappedRow : c.WeakTypeTag](c: Context) = c.universe.reify(new RowMapper[T]{}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
class RowA extends MappedRow | ||
class RowB extends MappedRow | ||
|
||
object Test extends App { | ||
implicitly[RowMapper[RowA]] | ||
implicitly[RowMapper[RowB]] | ||
} |