diff --git a/build.sbt b/build.sbt index 0d17fc43..e1ddd953 100644 --- a/build.sbt +++ b/build.sbt @@ -80,10 +80,28 @@ lazy val reactivemongo = project dependsOn core settings common lazy val catsTagless = project dependsOn core settings common lazy val pureconfig = project dependsOn core settings common lazy val scalacheck = project dependsOn core settings common +lazy val tests = + project + .dependsOn(core, circe, ciris, tethys, reactivemongo, catsTagless, pureconfig) + .settings(common, skip in publish := true) -lazy val derevo = project in file(".") settings (common, skip in publish := true) aggregate ( - core, cats, circe, circeMagnolia, ciris, tethys, tschema, reactivemongo, catsTagless, pureconfig, scalacheck -) +lazy val derevo = project + .in(file(".")) + .settings(common, skip in publish := true) + .aggregate( + core, + cats, + circe, + circeMagnolia, + ciris, + tethys, + tschema, + reactivemongo, + catsTagless, + pureconfig, + scalacheck, + tests, + ) addCommandAlias("fmt", "all scalafmtSbt scalafmt test:scalafmt") addCommandAlias("checkfmt", "all scalafmtSbtCheck scalafmtCheck test:scalafmtCheck") diff --git a/core/src/main/scala/derevo/Derevo.scala b/core/src/main/scala/derevo/Derevo.scala index 336dd2cb..61004099 100644 --- a/core/src/main/scala/derevo/Derevo.scala +++ b/core/src/main/scala/derevo/Derevo.scala @@ -2,12 +2,17 @@ package derevo import scala.language.higherKinds import scala.reflect.macros.blackbox -import scala.annotation.nowarn +import Derevo._ class Derevo(val c: blackbox.Context) { import c.universe._ - val DelegatingSymbol = typeOf[delegating].typeSymbol - val PhantomSymbol = typeOf[phantom].typeSymbol + + type Newtype = NewtypeP[Tree] + type NameAndTypes = NameAndTypesP[c.Type] + + val DelegatingSymbol = typeOf[delegating].typeSymbol + val PhantomSymbol = typeOf[phantom].typeSymbol + val PassTypeArgsSymbol = typeOf[PassTypeArgs].typeSymbol val instanceDefs = Vector( ) @@ -220,9 +225,11 @@ class Derevo(val c: blackbox.Context) { val resT = mkAppliedType(mode.to, outTyp) + val callWithT = if (mode.passArgs) q"$call[$outTyp]" else call + q""" @java.lang.SuppressWarnings(scala.Array("org.wartremover.warts.All", "scalafix:All", "all")) - implicit def $tn[..$tparams](implicit ..$implicits): $resT = $call + implicit def $tn[..$tparams](implicit ..$implicits): $resT = $callWithT """ } } @@ -232,34 +239,25 @@ class Derevo(val c: blackbox.Context) { case _ => tq"$tc[$arg]" } - private sealed trait Newtype { - def underlying: Tree - } - @nowarn - private final case class NewtypeCls(underlying: Tree) extends Newtype - @nowarn - private final case class NewtypeMod(underlying: Tree, res: Tree) extends Newtype - - @nowarn - private final class NameAndTypes( - val name: String, - val from: Type, - val to: Type, - val newtype: Type, - val drop: Int, - val cascade: Boolean - ) - private def nameAndTypes(obj: Tree): NameAndTypes = { val mangledName = obj.toString.replaceAll("[^\\w]", "_") val name = c.freshName(mangledName) - c.typecheck(obj).tpe match { + val objTyp = c.typecheck(obj).tpe + + val nt = objTyp match { case IsSpecificDerivation(f, t, nt, d) => new NameAndTypes(name, f, t, nt, d, true) case IsDerivation(f, t, nt, d) => new NameAndTypes(name, f, t, nt, d, true) case HKDerivation(f, t, nt, d) => new NameAndTypes(name, f, t, nt, d, false) case _ => abort(s"$obj seems not extending InstanceDef traits") } + + val passArgs = objTyp.baseType(PassTypeArgsSymbol) match { + case TypeRef(_, _, _) => true + case _ => false + } + + nt.copy(passArgs = passArgs) } class DerivationList(ds: IsInstanceDef*) { @@ -288,3 +286,21 @@ class Derevo(val c: blackbox.Context) { ) private def abort(s: String) = c.abort(c.enclosingPosition, s) } + +object Derevo { + private[Derevo] sealed trait NewtypeP[tree] { + def underlying: tree + } + private[Derevo] final case class NewtypeCls[tree](underlying: tree) extends NewtypeP[tree] + private[Derevo] final case class NewtypeMod[tree](underlying: tree, res: tree) extends NewtypeP[tree] + + private[Derevo] final case class NameAndTypesP[typ]( + name: String, + from: typ, + to: typ, + newtype: typ, + drop: Int, + cascade: Boolean, + passArgs: Boolean = false, + ) +} diff --git a/core/src/main/scala/derevo/package.scala b/core/src/main/scala/derevo/package.scala index 89e94614..e010102c 100644 --- a/core/src/main/scala/derevo/package.scala +++ b/core/src/main/scala/derevo/package.scala @@ -5,6 +5,8 @@ package derevo { def macroTransform(annottees: Any*): Any = macro Derevo.deriveMacro } + trait PassTypeArgs + class delegating(to: String, args: Any*) extends StaticAnnotation class phantom extends StaticAnnotation diff --git a/tests/src/main/scala/derevo/tests/Jampa.scala b/tests/src/main/scala/derevo/tests/Jampa.scala new file mode 100644 index 00000000..c34fc62b --- /dev/null +++ b/tests/src/main/scala/derevo/tests/Jampa.scala @@ -0,0 +1,22 @@ +package derevo +package tests + +import scala.language.experimental.macros +import scala.reflect.macros.blackbox + +trait Jampa[_] + +object JJ { + type Of[U[_[_]]] = U[Jampa] +} + +object Jampa extends DerivationKN3[JJ.Of] with PassTypeArgs { + def instance[U[f[_]]]: U[Jampa] = macro jampa[U] + + def jampa[U[f[_]]](c: blackbox.Context)(implicit t: c.WeakTypeTag[U[Any]]): c.Tree = { + import c.universe._ + val u = t.tpe.typeConstructor.typeSymbol + val jampa = typeOf[Jampa[Any]].typeConstructor + q"new $u[$jampa]{}" + } +} diff --git a/tests/src/test/scala/derevo/tests/HigherKindedSuite.scala b/tests/src/test/scala/derevo/tests/HigherKindedSuite.scala new file mode 100644 index 00000000..10866cb4 --- /dev/null +++ b/tests/src/test/scala/derevo/tests/HigherKindedSuite.scala @@ -0,0 +1,18 @@ +package derevo +package tests + +import scala.languageFeature.experimental.macros +import scala.reflect.ClassTag + +import org.scalatest.funsuite.AnyFunSuite + +@derive(Jampa) +trait Goo[G[_]] { + def goo = "goo" +} + +class HigherKindedMacroSuite extends AnyFunSuite { + test("goo should be goo") { + assert(implicitly[Goo[Jampa]].goo === "goo") + } +}