Skip to content

Instantly share code, notes, and snippets.

@mrdziuban
Last active May 23, 2018 12:02
Show Gist options
  • Save mrdziuban/f3389368ab1cdeb7f8bd06c4b45e080c to your computer and use it in GitHub Desktop.
Save mrdziuban/f3389368ab1cdeb7f8bd06c4b45e080c to your computer and use it in GitHub Desktop.
Incorrect behavior with type class derivation macro
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])
// 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
*/
#!/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