Skip to content

Commit

Permalink
only add cats-tagless instances if they don't already exist on the co…
Browse files Browse the repository at this point in the history
…mpanion object
  • Loading branch information
bpholt committed Sep 29, 2021
1 parent a9f9029 commit dbfa8e7
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 34 deletions.
3 changes: 0 additions & 3 deletions scalafix/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,10 @@ blank line at the end of the `SimpleService` object with these lines:

```scala


implicit def SimpleServiceInReaderT[F[_]]: SimpleService[({type Λ0] = _root_.cats.data.ReaderT[F, SimpleService[F], β0]})#Λ] =
_root_.cats.tagless.Derive.readerT[SimpleService, F]

implicit val SimpleServiceFunctorK: _root_.cats.tagless.FunctorK[SimpleService] = _root_.cats.tagless.Derive.functorK[SimpleService]


```

(Newlines and whitespace matter to the tests!)
Original file line number Diff line number Diff line change
Expand Up @@ -1068,11 +1068,8 @@ object SimpleService extends _root_.com.twitter.finagle.thrift.GeneratedThriftSe
}
}


implicit def SimpleServiceInReaderT[F[_]]: SimpleService[({type Λ0] = _root_.cats.data.ReaderT[F, SimpleService[F], β0]})#Λ] =
_root_.cats.tagless.Derive.readerT[SimpleService, F]

implicit val SimpleServiceFunctorK: _root_.cats.tagless.FunctorK[SimpleService] = _root_.cats.tagless.Derive.functorK[SimpleService]


}
2 changes: 1 addition & 1 deletion scalafix/regenerate-inputs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ libraryDependencies ++= {
(Compile / scroogeThriftOutputFolder) := file("src/main/scala")
__EOF__

sbt compile
sbt scroogeGen
find "../output/${GENERATED_SOURCES}" -name "*.scala" -delete
cp "${GENERATED_SOURCES}"/* "../output/${GENERATED_SOURCES}/"
find src/main/scala -name "*.scala" -print0 | \
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,45 +33,121 @@ class AddCatsTaglessInstances extends SemanticRule("AddCatsTaglessInstances") {
.map(_.toPatch)
}

object TraitWithTypeConstructor {
def unapply(subtree: Tree): Option[Defn.Trait] =
PartialFunction.condOpt(subtree) {
case term@Defn.Trait(_, _, List(Type.Param(_, _, List(_: Type.Param), _, _, _)), _, _) => term
}
}

case class ThriftService(alg: Defn.Trait,
companion: Option[Defn.Object]) {
def code: String =
s""" implicit def ${alg.name}InReaderT[F[_]]: ${alg.name}[({type Λ[β0] = _root_.cats.data.ReaderT[F, ${alg.name}[F], β0]})#Λ] =
| _root_.cats.tagless.Derive.readerT[${alg.name}, F]
|
| implicit val ${alg.name}FunctorK: _root_.cats.tagless.FunctorK[${alg.name}] = _root_.cats.tagless.Derive.functorK[${alg.name}]
|
|""".stripMargin
private def allInstances: List[ImplicitInstance] = List(
ImplicitInstance.AlgebraInKleisli(alg.name.value),
ImplicitInstance.FunctorK(alg.name.value),
)

private def code(instances: List[ImplicitInstance]): String =
instances.map(_.code).mkString("", "\n", "\n")

def toPatch: Patch =
companion match {
case None =>
val companionString =
s"""|
|object ${alg.name} {
|$code
|object ${alg.name} {
|${code(allInstances)}
|}""".stripMargin

Patch.addRight(alg, companionString)
case Some(companion) =>
companion.tokens.last match {
case brace@Token.RightBrace() => Patch.addLeft(brace,
s"""
|$code
|""".stripMargin)
case other => Patch.addRight(other,
s""" {
|$code
|
|}""".stripMargin)
}
val algName = alg.name.value

val existingInstances = companion.collect {
case ImplicitDefAlgInReaderT(`algName`) => ImplicitInstance.AlgebraInKleisli(algName)
case ImplicitValFunctorK(`algName`) => ImplicitInstance.FunctorK(algName)
}.toSet[ImplicitInstance]

val instancesToAdd = allInstances.filterNot(existingInstances)

if (instancesToAdd.isEmpty) Patch.empty
else
companion.tokens.last match {
case brace@Token.RightBrace() =>
Patch.addLeft(brace, code(instancesToAdd))
case other => Patch.addRight(other,
s""" {
|${code(instancesToAdd)}
|}""".stripMargin)
}
}
}

sealed trait ImplicitInstance {
def code: String
}
object ImplicitInstance {
case class AlgebraInKleisli(name: String) extends ImplicitInstance {
def code: String =
s""" implicit def ${name}InReaderT[F[_]]: $name[({type Λ[β0] = _root_.cats.data.ReaderT[F, $name[F], β0]})#Λ] =
| _root_.cats.tagless.Derive.readerT[$name, F]
|""".stripMargin
}

case class FunctorK(name: String) extends ImplicitInstance {
def code: String =
s" implicit val ${name}FunctorK: _root_.cats.tagless.FunctorK[$name] = _root_.cats.tagless.Derive.functorK[$name]"
}
}

object TraitWithTypeConstructor {
def unapply(subtree: Tree): Option[Defn.Trait] =
PartialFunction.condOpt(subtree) {
case term@Defn.Trait(_, _, TypeParamWithSingleHole(_), _, _) => term
}
}

object TypeParamWithSingleHole {
def unapply(list: List[Type.Param]): Option[String] =
PartialFunction.condOpt(list) {
case List(Type.Param(_, Type.Name(name), List(_: Type.Param), _, _, _)) => name
}
}

object SingleTypeParameter {
def unapply(tree: List[Type.Param]): Option[String] =
PartialFunction.condOpt(tree) {
case List(Type.Param(_, Type.Name(name), List(), _, _, _)) => name
}
}

object ImplicitValFunctorK {
def unapply(tree: Defn.Val): Option[String] =
PartialFunction.condOpt(tree) {
case Defn.Val(List(Mod.Implicit()), _, Some(FunctorK(name)), _) => name
}
}

object ImplicitDefAlgInReaderT {
def unapply(tree: Defn.Def): Option[String] =
PartialFunction.condOpt(tree) {
case Defn.Def(List(Mod.Implicit()), _, TypeParamWithSingleHole(passedEffect), _, Some(Type.Apply(Type.Name(algName), TypeProjectionOfReaderT(effect, alg))), _) if passedEffect == effect && algName == alg =>
algName
}
}

object TypeProjectionOfReaderT {
def unapply(tree: List[Type]): Option[(String, String)] =
PartialFunction.condOpt(tree) {
case List(Type.Project(Type.Refine(_, List(Defn.Type(_, _, SingleTypeParameter(projType), ReaderT(kleisliEffect, algName, outputType)))), _)) if projType == outputType =>
(kleisliEffect, algName)
}
}

object ReaderT {
def unapply(tree: Type.Apply): Option[(String, String, String)] =
PartialFunction.condOpt(tree) {
case Type.Apply(Type.Select(_, Type.Name("ReaderT")), List(Type.Name(kleisliEffect), Type.Apply(Type.Name(algName), List(Type.Name(algEffect))), Type.Name(outputType))) if kleisliEffect == algEffect =>
(kleisliEffect, algName, outputType)
}
}

object FunctorK {
def unapply(tree: Type.Apply): Option[String] =
PartialFunction.condOpt(tree) {
case Type.Apply(Type.Select(_, Type.Name("FunctorK")), List(Type.Name(name))) => name
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ object AddCatsTaglessInstancesTest extends App {
f"""trait SimpleService[F[_]]
|object SimpleService extends _root_.com.twitter.finagle.thrift.GeneratedThriftService { self =>
| val annotations: immutable$$Map[String, String] = immutable$$Map.empty
|
| implicit def SimpleServiceInReaderT[F[_]]: SimpleService[({type Λ[β0] = _root_.cats.data.ReaderT[F, SimpleService[F], β0]})#Λ] =
| _root_.cats.tagless.Derive.readerT[SimpleService, F]
|
| implicit val SimpleServiceFunctorK: _root_.cats.tagless.FunctorK[SimpleService] = _root_.cats.tagless.Derive.functorK[SimpleService]
|}
|trait Bar
|trait Baz[F]
Expand Down

0 comments on commit dbfa8e7

Please sign in to comment.