Last active
May 23, 2018 12:02
-
-
Save mrdziuban/f3389368ab1cdeb7f8bd06c4b45e080c to your computer and use it in GitHub Desktop.
Incorrect behavior with type class derivation macro
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
import scala.language.experimental.macros | |
import scala.reflect.macros.blackbox.Context | |
trait Csv[A] { def apply(a: A): Seq[String] } | |
object Csv { | |
implicit def deriveCsv[A]: Csv[A] = macro CsvMacro.csvImpl[A] | |
implicit val csvStr: Csv[String] = new Csv[String] { def apply(s: String) = Seq(s) } | |
implicit def csvSeq[A](implicit ca: Csv[A]): Csv[Seq[A]] = new Csv[Seq[A]] { | |
def apply(a: Seq[A]) = a.flatMap(ca(_)) | |
} | |
} | |
object CsvMacro { | |
def csvImpl[A: c.WeakTypeTag](c: Context): c.Expr[Csv[A]] = { | |
import c.universe._ | |
val tpe = c.weakTypeOf[A] | |
def fail = c.abort(c.enclosingPosition, s"Cannot derive Csv[$tpe]. Type is neither a sealed trait nor a case class.") | |
println(s"Deriving Csv[$tpe]") | |
if (tpe.typeSymbol.isClass) { | |
val klass = tpe.typeSymbol.asClass | |
if (klass.isCaseClass || (klass.isTrait && klass.isSealed)) { | |
val accumulated = tpe.decls.collect { | |
case m: MethodSymbol if m.isCaseAccessor => | |
q"_root_.scala.Predef.implicitly[Csv[${m.returnType.asSeenFrom(tpe, tpe.typeSymbol)}]].apply(x.${m.name})" | |
}.reduceLeft((acc, x) => q"$acc ++ $x") | |
c.Expr[Csv[A]](q"new Csv[$tpe] { def apply(x: $tpe): Seq[String] = $accumulated }") | |
} else fail | |
} else fail | |
} | |
} | |
case class Foo(s: String) | |
case class TypeParam[A](a: A) | |
case class Wrapper(tp: TypeParam[Foo]) |
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
// Derivation for Seq[Wrapper] has incorrect behavior | |
implicitly[Csv[Seq[Wrapper]]] | |
/* | |
Deriving Csv[Wrapper] | |
Deriving Csv[Seq[Wrapper]] | |
<console>:18: error: Cannot derive Csv[Seq[Wrapper]]. Type is neither a sealed trait nor a case class. | |
implicitly[Csv[Seq[Wrapper]]] | |
*/ | |
// Derivation for Seq[Wrapper] works correctly when there is an implicit Csv[Wrapper] in scope | |
implicit val w = implicitly[Csv[Wrapper]] | |
/* | |
Deriving Csv[Wrapper] | |
Deriving Csv[TypeParam[Foo]] | |
Deriving Csv[Foo] | |
w: CsvMacro.Csv[Wrapper] = $anon$1@712594f4 | |
*/ | |
implicitly[Csv[Seq[Wrapper]]] | |
/* | |
res1: CsvMacro.Csv[Seq[Wrapper]] = csv$$$Lambda$1644/1243911696@ebbbe20 | |
*/ |
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
#!/usr/bin/env sh | |
# To load the code in CsvDerivation.scala, run | |
# $ curl -sL https://gist.github.com/mrdziuban/f3389368ab1cdeb7f8bd06c4b45e080c/raw/load.sh | bash | |
# Then run the commands shown in IncorrectBehavior.scala | |
test -e $HOME/.coursier/coursier || \ | |
(mkdir -p $HOME/.coursier && \ | |
curl -sL -o $HOME/.coursier/coursier https://git.io/vgvpD && \ | |
chmod +x $HOME/.coursier/coursier) | |
tmpfile="$(mktemp)" | |
curl -sL -o $tmpfile https://gist.github.com/mrdziuban/f3389368ab1cdeb7f8bd06c4b45e080c/raw/CsvDerivation.scala | |
$HOME/.coursier/coursier launch -q -M 'ammonite.Main' -P \ | |
com.lihaoyi:ammonite_2.12.6:1.1.2 \ | |
-- --predef $tmpfile < /dev/tty |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment