Skip to content

Commit

Permalink
add Scalafix rule to add cats-tagless instances for use with asyncMapK
Browse files Browse the repository at this point in the history
running the Scalafix rule after generating Finagle code with Scrooge
will result in implicit instances being added to the companion objects
of the generated Finagle service traits.
  • Loading branch information
bpholt committed Sep 28, 2021
1 parent be35b8e commit a9f9029
Show file tree
Hide file tree
Showing 20 changed files with 4,768 additions and 4 deletions.
53 changes: 49 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ object FooService {
}
```

These implicit instances can be automatically added to the code generated by Scrooge by running the `AddCatsTaglessInstances` scalafix rule! See below for more detail.

Now you can safely convert your `Future`-based implementation into one in `F[_] : Async`:

```scala
Expand Down Expand Up @@ -64,10 +66,8 @@ trait FooScroogeService[F[_]] {
}

object FooScroogeService {
// these implicits won't be generated by scrooge—but we're working on that!
// for now you'll have to add them yourself, either here or somewhere in the
// implicit scope available to the calls shown below under "Finagle Clients"
// and "Finagle Servers"
// Let Scalafix generate these instances for you!
// Follow the instructions in the `Scalafix Rule` section below.
implicit def FooScroogeServiceReaderT[F[_]]: FooScroogeService[ReaderT[F, FooScroogeService[F], *]] =
Derive.readerT[FooScroogeService, F]
implicit val FooScroogeServiceFunctorK: FunctorK[FooScroogeService] = Derive.functorK[FooScroogeService]
Expand Down Expand Up @@ -100,6 +100,43 @@ val fooImpl: FooScroogeService[IO] = new FooScroogeService[IO] {
val thriftServer: IO[Nothing] = ThriftServer("address", fooImpl)
```

## Scalafix Rule

Add Scalafix to your project's build by [following the instructions](https://scalacenter.github.io/scalafix/docs/users/installation.html#sbt):

1. Add the Scalafix plugin to the project by adding this to `project/plugins.sbt`:
```scala
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31")
```

2. Enable SemanticDB by adding this to `build.sbt`:
```scala
ThisBuild / semanticdbEnabled := true
ThisBuild / semanticdbVersion := scalafixSemanticdb.revision
ThisBuild / scalafixScalaBinaryVersion := CrossVersion.binaryScalaVersion(scalaVersion.value)
ThisBuild / scalafixDependencies += "com.dwolla" %% "finagle-tagless-scalafix" % "0.0.4"
```

3. Run the Scalafix rule automatically after generating the Thrift sources by adding this to `build.sbt`:
```scala
Compile / scalafix / unmanagedSources := (Compile / sources).value
Compile / compile := Def.taskDyn {
val compileOutput = (Compile / compile).value

Def.task {
(Compile / scalafix).toTask(" AddCatsTaglessInstances").value
compileOutput
}
}.value
libraryDependencies ++= {
val catsTaglessV = "0.14.0"
Seq(
"org.typelevel" %% "cats-tagless-core" % catsTaglessV,
"org.typelevel" %% "cats-tagless-macros" % catsTaglessV,
)
}
```

## Artifacts

The Group ID for each artifact is `"com.dwolla"`. All artifacts are published to Maven Central.
Expand Down Expand Up @@ -153,6 +190,14 @@ The Group ID for each artifact is `"com.dwolla"`. All artifacts are published to
<td align="center">Cats Effect 3</td>
</tr>
<tr>
<td><code>"finagle-tagless-scalafix"</code></td>
<td>Automatically adds implicit instances needed by `asyncMapK` to the companion objects of Finagle services generated by Scrooge</td>
<td align="center">All</td>
<td align="center"><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td align="center"><g-emoji class="g-emoji" alias="white_check_mark" fallback-src="https://github.githubassets.com/images/icons/emoji/unicode/2705.png">✅</g-emoji></td>
<td align="center">N/A</td>
</tr>
<tr>
<td><code>"async-utils-twitter-19-4-ce2"</code></td>
<td rowspan="2">Implementation for Twitter <code>Future</code><br>(with <code>"com.twitter" %% "util-core" % "19.4.0"</code>)</td>
<td align="center">Cats Effect 2</td>
Expand Down
60 changes: 60 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ lazy val CatsEffect3V = "3.2.9"
lazy val TwitterUtilsLatestV = "21.8.0"
lazy val TwitterUtils19_4V = "19.4.0"
lazy val CatsTaglessV = "0.14.0"
lazy val libthriftV = "0.10.0"

lazy val SCALA_2_13 = "2.13.6"
lazy val SCALA_2_12 = "2.12.15"
Expand Down Expand Up @@ -252,13 +253,72 @@ lazy val examples = (projectMatrix in file("examples"))
.jvmPlatform(scalaVersions = Scala2Versions)
.dependsOn(`async-utils`)

lazy val `scalafix-rules` = (projectMatrix in file("scalafix/rules"))
.jvmPlatform(scalaVersions = Scala2Versions)
.settings(
moduleName := "finagle-tagless-scalafix",
libraryDependencies ++= Seq(
"ch.epfl.scala" %% "scalafix-core" % _root_.scalafix.sbt.BuildInfo.scalafixVersion,

),
scalacOptions ~= { _.filterNot(_ == "-Xfatal-warnings") },
)

lazy val `scalafix-input` = (project in file("scalafix/input"))
.settings(
publish / skip := true,
scalaVersion := Scala2Versions.head,
libraryDependencies ++= Seq(
"com.twitter" %% "scrooge-core" % TwitterUtilsLatestV,
"com.twitter" %% "finagle-thrift" % TwitterUtilsLatestV,
"org.apache.thrift" % "libthrift" % libthriftV,
),
scalacOptions += "-nowarn",
scalacOptions ~= { _.filterNot(_ == "-Xfatal-warnings") },
semanticdbEnabled := true,
semanticdbVersion := scalafixSemanticdb.revision,
)
.disablePlugins(ScalafixPlugin)

lazy val `scalafix-output` = (project in file("scalafix/output"))
.settings(
publish / skip := true,
scalaVersion := Scala2Versions.head,
libraryDependencies ++= Seq(
"com.twitter" %% "scrooge-core" % TwitterUtilsLatestV,
"com.twitter" %% "finagle-thrift" % TwitterUtilsLatestV,
"org.apache.thrift" % "libthrift" % libthriftV,
"org.typelevel" %% "cats-tagless-core" % CatsTaglessV,
"org.typelevel" %% "cats-tagless-macros" % CatsTaglessV,
),
scalacOptions += "-nowarn",
scalacOptions ~= { _.filterNot(_ == "-Xfatal-warnings") },
)
.disablePlugins(ScalafixPlugin)

lazy val `scalafix-tests` = (projectMatrix in file("scalafix/tests"))
.jvmPlatform(scalaVersions = Scala2Versions)
.settings(
publish / skip := true,
libraryDependencies += "ch.epfl.scala" % "scalafix-testkit" % _root_.scalafix.sbt.BuildInfo.scalafixVersion % Test cross CrossVersion.full,
scalafixTestkitOutputSourceDirectories := (`scalafix-output` / Compile / unmanagedSourceDirectories).value,
scalafixTestkitInputSourceDirectories := (`scalafix-input` / Compile / unmanagedSourceDirectories).value,
scalafixTestkitInputClasspath := (`scalafix-input` / Compile / fullClasspath).value,
scalafixTestkitInputScalacOptions := (`scalafix-input` / Compile / scalacOptions).value,
scalafixTestkitInputScalaVersion := (`scalafix-input` / Compile / scalaVersion).value,
)
.dependsOn(`scalafix-rules`)
.enablePlugins(ScalafixTestkitPlugin)

lazy val `async-utils-root` = (project in file("."))
.aggregate(
Seq(
`async-utils-core`,
`async-utils`,
`async-utils-twitter`,
`async-utils-finagle`,
`scalafix-rules`,
`scalafix-tests`,
examples,
).flatMap(_.projectRefs): _*
)
Expand Down
1 change: 1 addition & 0 deletions project/plugins.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ addSbtPlugin("io.github.davidgregory084" % "sbt-tpolecat" % "0.1.20")
addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.5.9")
addSbtPlugin("com.eed3si9n" % "sbt-projectmatrix" % "0.8.0")
addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.7.0")
addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.9.31")
33 changes: 33 additions & 0 deletions scalafix/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Scalafix Notes

## Scrooge Updates

Scrooge updates roughly monthly, and occasionally the updates introduce
breaking changes in the generated code. (For example, between the `21.5.0`
and `21.8.0` versions, [a new abstract method was added to the `ValidatingThriftStructCodec3`](https://github.com/twitter/scrooge/commit/fdb8f8f2d9cc30d6ca06b117fe7a82003f330ba8#diff-1fb7ff7b4c7a64ec907dfa620403b5b190f9829f3d5e6e0d57d07685dd53411f)
trait, which implementations generated by the older version will be missing.)

As a result, the files in `scalafix/input/src/main/scala/example/thrift` and
`scalafix/output/src/main/scala/example/thrift` may need to be regenerated when
the Scrooge version is updated by running the `regenerate-inputs.sh` script with
the new Scrooge version as the first parameter.

```bash
./regenerate-inputs.sh 21.8.0
```

Then edit `scalafix/output/src/main/scala/example/thrift/SimpleService.scala` and replace the
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!)
Loading

0 comments on commit a9f9029

Please sign in to comment.