Skip to content

Commit

Permalink
Add support for Generic materialized in companion object of nested …
Browse files Browse the repository at this point in the history
…case class (#1286)

Co-authored-by: Georgi Krastev <[email protected]>
  • Loading branch information
DmytroMitin and joroKr21 authored Apr 25, 2024
1 parent 977f354 commit df313df
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 1 deletion.
26 changes: 26 additions & 0 deletions build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,30 @@ lazy val plugin = project.in(file("plugin"))
crossScalaVersions := Seq(Scala213, Scala212)
)

lazy val macroAnnotationSettings = Seq(
scalacOptions ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, v)) if v >= 13 => Seq("-Ymacro-annotations")
case _ => Nil
}
},
libraryDependencies ++= {
CrossVersion.partialVersion(scalaVersion.value) match {
case Some((2, v)) if v <= 12 =>
Seq(compilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full))
case _ => Nil
}
},
)

lazy val coreTestMacros = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.crossType(CrossType.Full)
.settings(moduleName := "core-test-macros")
.settings(commonSettings)
.settings(noPublishSettings)
.configureCross(buildInfoSetup)
.settings(macroAnnotationSettings)

lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.crossType(CrossType.Full)
.configureCross(configureJUnit)
Expand All @@ -119,6 +143,8 @@ lazy val core = crossProject(JSPlatform, JVMPlatform, NativePlatform)
.settings(Compile / sourceManaged := baseDirectory.value.getParentFile / "shared" / "src" / "main" / "managed")
.settings(Compile / sourceGenerators += (Compile / sourceManaged).map(Boilerplate.gen).taskValue)
.settings(mimaSettings)
.dependsOn(coreTestMacros % "test->compile")
.settings(macroAnnotationSettings)

lazy val coreJVM = core.jvm
lazy val coreJS = core.js
Expand Down
6 changes: 5 additions & 1 deletion core/shared/src/main/scala/shapeless/generic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -916,9 +916,13 @@ trait CaseClassMacros extends ReprTypes with CaseClassMacrosVersionSpecifics {
// case 3: case class
case tpe if tpe.typeSymbol.asClass.isCaseClass =>
val companion = patchedCompanionSymbolOf(tpe.typeSymbol)
val apply = companion.typeSignature.member(TermName("apply"))
val unapply = companion.typeSignature.member(TermName("unapply"))
val fields = fieldsOf(tpe)
FromTo(fromApply(fields), if (unapply.isSynthetic) toUnapply(fields) else toGetters(fields))
FromTo(
if (apply == NoSymbol) fromConstructor(fields) else fromApply(fields),
if (unapply.isSynthetic) toUnapply(fields) else toGetters(fields)
)
// case 4: exactly one matching public apply/unapply
case HasApplyUnapply(args) =>
FromTo(fromApply(args), toUnapply(args))
Expand Down
7 changes: 7 additions & 0 deletions core/shared/src/test/scala/shapeless/generic.scala
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,13 @@ package GenericTestsAux {
final case class InTap[A, -B](in: B => A) extends Tap[A]
final case class OutTap[A, +B](out: A => B) extends Tap[A]
final case class PipeTap[A, B](in: B => A, out: A => B) extends Tap[A]

object macroAnnotations {
case class A(i: Int, s: String)

@generateGeneric
object A
}
}

class GenericTests {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package shapeless

import scala.annotation.{StaticAnnotation, compileTimeOnly}
import scala.reflect.macros.blackbox
import scala.language.experimental.macros

@compileTimeOnly("enable macro annotations")
class generateGeneric extends StaticAnnotation {
def macroTransform(annottees: Any*): Any = macro GenerateGenericMacroImpl.macroTransformImpl
}

object GenerateGenericMacroImpl {
def macroTransformImpl(c: blackbox.Context)(annottees: c.Tree*): c.Tree = {
import c.universe._

def modifyObject(obj: Tree): Tree = obj match {
case q"$mods object $tname extends { ..$earlydefns } with ..$parents { $self => ..$body }" =>
q"""$mods object $tname extends { ..$earlydefns } with ..$parents { $self =>
..$body
_root_.shapeless.Generic[${tname.toTypeName}](_root_.shapeless.Generic.materialize)
}"""
case _ => sys.error("impossible")
}

def modify(cls: Tree, obj: Tree): Tree = q"..${Seq(cls, modifyObject(obj))}"

annottees match {
case (cls: ClassDef) :: (obj: ModuleDef) :: Nil => modify(cls, obj)
case (cls: ClassDef) :: Nil => modify(cls, q"object ${cls.name.toTermName}")
// this works for the companion object of a sealed trait or top-level case class but not nested case class
case (obj: ModuleDef) :: Nil => modifyObject(obj)
case _ => c.abort(c.enclosingPosition, "@generateGeneric can annotate only traits, classes, and objects")
}
}
}

0 comments on commit df313df

Please sign in to comment.