Skip to content

Commit

Permalink
Remove macro in FunctionK.lift implementation
Browse files Browse the repository at this point in the history
Instead, use the trick proposed by Alex in
typelevel#2553 (comment),
this is perhaps not ideal but it seems simpler than maintaining two
separate source files for Scala 2 and Dotty, just for this one function.
Additionally, since Dotty is able to call into non-macro methods in
Scala 2 dependencies, this makes FunctionK.lift usable from
Dotty without cats havin to publish Dotty artifacts.

Co-authored-by: Alexander Konovalov <alex.knvl@gmail.com>
  • Loading branch information
smarter and beer-avalanche committed Jun 30, 2019
1 parent 64eac53 commit 5c1bd11
Showing 1 changed file with 8 additions and 79 deletions.
87 changes: 8 additions & 79 deletions core/src/main/scala/cats/arrow/FunctionK.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package cats
package arrow

import scala.reflect.macros.blackbox.Context

import cats.data.{EitherK, Tuple2K}

Expand Down Expand Up @@ -78,85 +77,15 @@ object FunctionK {
* val lifted: FunctionK[List, Option] = FunctionK.lift(headOption)
* }}}
*
* Note: This method has a macro implementation that returns a new
* `FunctionK` instance as follows:
*
* {{{
* new FunctionK[F, G] {
* def apply[A](fa: F[A]): G[A] = f(fa)
* }
* }}}
*
* Additionally, the type parameters on `f` must not be specified.
* Note: The weird `τ[F, G]` parameter is there to compensate for
* the lack of polymorphic function types in Scala 2.
*/
def lift[F[_], G[_]](f: (F[α] => G[α]) forSome { type α }): FunctionK[F, G] =
macro FunctionKMacros.lift[F, G]

}

private[arrow] object FunctionKMacros {

def lift[F[_], G[_]](c: Context)(
f: c.Expr[(F[α] => G[α]) forSome { type α }]
)(
implicit evF: c.WeakTypeTag[F[_]],
evG: c.WeakTypeTag[G[_]]
): c.Expr[FunctionK[F, G]] =
c.Expr[FunctionK[F, G]](new Lifter[c.type](c).lift[F, G](f.tree))
// ^^note: extra space after c.type to appease scalastyle

private[this] class Lifter[C <: Context](val c: C) {
import c.universe._

def lift[F[_], G[_]](tree: Tree)(
implicit evF: c.WeakTypeTag[F[_]],
evG: c.WeakTypeTag[G[_]]
): Tree = unblock(tree) match {
case q"($param) => $trans[..$typeArgs](${arg: Ident})" if param.name == arg.name =>
typeArgs
.collect { case tt: TypeTree => tt }
.find(tt => Option(tt.original).isDefined)
.foreach { param =>
c.abort(param.pos, s"type parameter $param must not be supplied when lifting function $trans to FunctionK")
}

val F = punchHole(evF.tpe)
val G = punchHole(evG.tpe)

q"""
new _root_.cats.arrow.FunctionK[$F, $G] {
def apply[A](fa: $F[A]): $G[A] = $trans(fa)
}
"""
case other =>
c.abort(other.pos, s"Unexpected tree $other when lifting to FunctionK")
}

private[this] def unblock(tree: Tree): Tree = tree match {
case Block(Nil, expr) => expr
case _ => tree
}

private[this] def punchHole(tpe: Type): Tree = tpe match {
case PolyType(undet :: Nil, underlying: TypeRef) =>
val α = TypeName("α")
def rebind(typeRef: TypeRef): Tree =
if (typeRef.sym == undet) tq""
else {
val args = typeRef.args.map {
case ref: TypeRef => rebind(ref)
case arg => tq"$arg"
}
tq"${typeRef.sym}[..$args]"
}
val rebound = rebind(underlying)
tq"""({type λ[] = $rebound})#λ"""
case TypeRef(pre, sym, Nil) =>
tq"$sym"
case _ =>
c.abort(c.enclosingPosition, s"Unexpected type $tpe when lifting to FunctionK")
def lift[F[_], G[_]](f: F[τ[F, G]] => G[τ[F, G]]): FunctionK[F, G] =
new FunctionK[F, G] {
def apply[A](fa: F[A]): G[A] =
f.asInstanceOf[F[A] => G[A]](fa)
}

}

/** Used in the signature of `lift` to emulate a polymorphic function type */
private[this] sealed trait τ[F[_], G[_]]
}

0 comments on commit 5c1bd11

Please sign in to comment.