Skip to content

Commit

Permalink
Macros create implicits with an explicit return type, dropped Cached …
Browse files Browse the repository at this point in the history
…support
  • Loading branch information
MateuszKubuszok committed Sep 10, 2018
1 parent 3be95fc commit 72647aa
Show file tree
Hide file tree
Showing 6 changed files with 34 additions and 270 deletions.
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ Test("a").show // "Test(a = a)"
You can also test it with ammonite like:

```scala
import $ivy.`io.scalaland::catnip:0.2.1`, io.scalaland.catnip._, cats._, cats.implicits._
import $ivy.`io.scalaland::catnip:0.4.0`, io.scalaland.catnip._, cats._, cats.implicits._
interp.load.plugin.ivy("org.scalamacros" % "paradise_2.12.4" % "2.1.1")

@Semi(Eq, Monoid, Functor) final case class Test[A](a: A)
Expand All @@ -51,17 +51,11 @@ Test("a") |+| Test("b") // Test("ab")
Test("1").map(_.toInt) // Test(1)
```

If you want to replace semi automatic derivation with cached automatic derivation
use `@Cached` instead of `@Semi` (see a list of supported type classes below).

## Implemented

* `@Semi`: `cats.Eq`, `cats.PartialOrder`, `cats.Order`, `cats.Hash`,
`cats.Functor`, `cats.Foldable`, `cats.Show`, `cats.Monoid`, `cats.MonoidK`,
`cats.Semigroup`, `cats.SemigroupK`, `alleycats.Empty`,
* `@Cached`: `cats.Eq`, `cats.PartialOrder`, `cats.Order`, `cats.Hash`,
`cats.Functor`, `cats.Show`, `cats.MonoidK`, `cats.Semigroup`,
`cats.SemigroupK`.
`cats.Semigroup`, `cats.SemigroupK`, `alleycats.Empty`.

## Internals

Expand Down Expand Up @@ -104,8 +98,6 @@ the content of `derive.semi.conf`. (Some merge strategy for resources I guess?
That and making sure that compiler _sees_ the resources, since if you define them
in the same project you want compiler to use them it is not the case).

Same for `@Cached` and [`derive.cached.conf`](modules/catnip/src/main/resources/derive.cached.conf).

## Limitations

Type checker complains if you use type aliases from the same compilation unit
Expand Down

This file was deleted.

9 changes: 0 additions & 9 deletions modules/catnip/src/main/resources/derive.cached.conf

This file was deleted.

18 changes: 0 additions & 18 deletions modules/catnip/src/main/scala/io/scalaland/catnip/Cached.scala

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,5 @@ class Semi(typeClasses: Any*) extends StaticAnnotation {

private object Semi {

def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = DerivedImpl.impl(DerivedImpl.Type.Semi)(c)(annottees)
def impl(c: Context)(annottees: c.Expr[Any]*): c.Expr[Any] = DerivedImpl.impl(c)(annottees)
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,34 +14,37 @@ private[catnip] class DerivedImpl(config: Map[String, (String, List[String])])(v
private def str2TypeConstructor(typeClassName: String): Type =
c.typecheck(c.parse(s"null: $typeClassName[Nothing]")).tpe.dealias.typeConstructor

private def isParametried(name: TypeName): String => Boolean =
s"""(^|[,\\[])$name([,\\]]|$$)""".r.pattern.asPredicate.test _

private def buildDerivation(classDef: ClassDef, typeClassName: TypeClass): ValOrDefDef = classDef match {
case q"""$_ trait $name[..${params: Seq[TypeDef] }]
extends { ..$_ }
with ..$_ { $_ => ..$_ }""" =>
withTraceLog("Derivation expanded") {
withTraceLog(s"Derivation expanded for $name trait") {
val fType = str2TypeConstructor(typeClassName.toString)
val implName = TermName(s"_derived_${fType.toString.replace('.', '_')}")
lazy val aType = if (params.nonEmpty) tq"$name[..${params.map(_.name)}]" else tq"$name"
val body = c.parse(s"${config(fType.toString)._1}[$aType]")
q"""implicit val $implName = $body""": ValDef
lazy val aType = if (params.nonEmpty) TypeName(tq"$name[..${params.map(_.name)}]".toString) else name
val body = c.parse(s"{ ${config(fType.toString)._1}[$aType] }")
val returnType = tq"$fType[$aType]"
// TODO: figure out, why this doesn't work
// q"""implicit val $implName: $returnType = $body""": ValDef
c.parse(s"""implicit val $implName: $returnType = $body""").asInstanceOf[ValDef]
}
case q"""$_ class $name[..${params: Seq[TypeDef] }] $_(...${ctorParams: Seq[Seq[ValDef]] })
extends { ..$_ }
with ..$_ { $_ => ..$_ }""" =>
withTraceLog("Derivation expanded") {
withTraceLog(s"Derivation expanded for $name class") {
val fType = str2TypeConstructor(typeClassName.toString)
val otherReqTCs = config(fType.toString)._2.map(str2TypeConstructor)
val needKind = scala.util.Try(c.typecheck(c.parse(s"null: $fType[List]"))).isSuccess
val implName = TermName(s"_derived_${fType.toString.replace('.', '_')}")
lazy val aType = if (params.nonEmpty) tq"$name[..${params.map(_.name)}]" else tq"$name"
lazy val argTypes = ctorParams.flatten
.groupBy(_.tpt.toString)
.flatMap(_._2.headOption.toList)
.map(_.tpt.toString)
lazy val usedParams = if (needKind) Nil else params.map(_.name).filter { name =>
val isParametrized = s"""(^|[,\\[])$name([,\\]]|$$)""".r.pattern.asPredicate.test _
argTypes.exists(isParametrized)
}
lazy val aType = TypeName(if (params.nonEmpty) tq"$name[..${params.map(_.name)}]".toString else name.toString)
lazy val argTypes =
ctorParams.flatten.groupBy(_.tpt.toString).flatMap(_._2.headOption.toList).map(_.tpt.toString)
lazy val usedParams =
if (needKind) Nil
else params.map(_.name).filter(name => argTypes.exists(isParametried(name)))
val providerArgs = usedParams
.flatMap { p =>
(fType :: otherReqTCs).map(tpe => s"${tpe.toString.replace('.', '_')}_$p: $tpe[$p]")
Expand All @@ -52,10 +55,16 @@ private[catnip] class DerivedImpl(config: Map[String, (String, List[String])])(v
(fType :: otherReqTCs).map(tpe => s"${tpe.toString.replace('.', '_')}_$p.hashCode;")
}
.mkString("")
val body =
c.parse(s"$suppressUnused${config(fType.toString)._1}[${if (needKind) name else aType}]")
if (usedParams.isEmpty) q"""implicit val $implName = $body""": ValDef
else q"""implicit def $implName[..$params](implicit ..$providerArgs) = $body""": DefDef
val tcForType = if (needKind) name else aType
val body = c.parse(s"{ $suppressUnused${config(fType.toString)._1}[$tcForType] }")
val returnType = tq"$fType[$tcForType]"
// TODO: figure out, why this doesn't work
// if (usedParams.isEmpty) q"""implicit val $implName: $returnType = $body""": ValDef
// else q"""implicit def $implName[..$params](implicit ..$providerArgs): $returnType = $body""": DefDef
if (usedParams.isEmpty) c.parse(s"""implicit val $implName: $returnType = $body""").asInstanceOf[ValDef]
else
c.parse(q"""implicit def $implName[..$params](implicit ..$providerArgs): $returnType = $body""".toString)
.asInstanceOf[DefDef]
}
}

Expand Down Expand Up @@ -88,19 +97,13 @@ private[catnip] class DerivedImpl(config: Map[String, (String, List[String])])(v
case Expr(classDef: ClassDef) :: Nil =>
c.Expr(q"""$classDef
${createCompanion(classDef, typeClasses)}""")
case got => c.abort(c.enclosingPosition, s"@Semi or @Cached can only annotate class, got: $got")
case got => c.abort(c.enclosingPosition, s"@Semi can only annotate class, got: $got")
}
}
}

private[catnip] object DerivedImpl {

sealed trait Type
object Type {
case object Semi extends Type
case object Cached extends Type
}

private def loadConfig(name: String) =
scala.io.Source
.fromURL(getClass.getClassLoader.getResources(name).nextElement)
Expand All @@ -117,11 +120,8 @@ private[catnip] object DerivedImpl {
}
.toMap

private val mappings: Map[Type, Map[String, (String, List[String])]] = Map(
Type.Semi -> loadConfig("derive.semi.conf"),
Type.Cached -> loadConfig("derive.cached.conf")
)
private val mappings: Map[String, (String, List[String])] = loadConfig("derive.semi.conf")

def impl(derivedType: DerivedImpl.Type)(c: Context)(annottees: Seq[c.Expr[Any]]): c.Expr[Any] =
new DerivedImpl(mappings(derivedType))(c)(annottees).derive().asInstanceOf[c.Expr[Any]]
def impl(c: Context)(annottees: Seq[c.Expr[Any]]): c.Expr[Any] =
new DerivedImpl(mappings)(c)(annottees).derive().asInstanceOf[c.Expr[Any]]
}

0 comments on commit 72647aa

Please sign in to comment.