-
Notifications
You must be signed in to change notification settings - Fork 22
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Utilities for extracting GenCodec field / type names
- Loading branch information
Showing
3 changed files
with
168 additions
and
0 deletions.
There are no files selected for viewing
58 changes: 58 additions & 0 deletions
58
core/src/main/scala/com/avsystem/commons/serialization/GencodecTypeName.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,58 @@ | ||
package com.avsystem.commons | ||
package serialization | ||
|
||
import com.avsystem.commons.annotation.explicitGenerics | ||
|
||
/** | ||
* Typeclass holding name of a type that will be used in [[GenCodec]] serialization | ||
* | ||
* @see [[com.avsystem.commons.serialization.GenCodecUtils.codecTypeName]] | ||
*/ | ||
final class GencodecTypeName[T](val name: String) | ||
object GencodecTypeName { | ||
def apply[T](implicit tpeName: GencodecTypeName[T]): GencodecTypeName[T] = tpeName | ||
|
||
implicit def materialize[T]: GencodecTypeName[T] = | ||
macro com.avsystem.commons.macros.serialization.GenCodecUtilMacros.codecTypeName[T] | ||
} | ||
|
||
object GenCodecUtils { | ||
/** | ||
* Allows to extract case class name that will be used in [[GenCodec]] serialization format when dealing with sealed | ||
* hierarchies. | ||
* | ||
* {{{ | ||
* @name("SomethingElse") | ||
* final case class Example(something: String) | ||
* object Example extends HasGenCodec[Example] | ||
* | ||
* GenCodecUtils.codecTypeName[Example] // "SomethingElse" | ||
* }}} | ||
* | ||
* @return name of case class, possibility adjusted by [[com.avsystem.commons.serialization.name]] annotation | ||
*/ | ||
@explicitGenerics | ||
def codecTypeName[T]: String = | ||
macro com.avsystem.commons.macros.serialization.GenCodecUtilMacros.codecTypeNameRaw[T] | ||
|
||
/** | ||
* Allows to extract case class field name that will be used in [[GenCodec]] serialization format | ||
* {{{ | ||
* final case class Example(something: String, @name("otherName") somethingElse: Int) | ||
* object Example extends HasGenCodec[Example] | ||
* | ||
* GenCodecUtils.codecFieldName[Example](_.somethingElse) // "otherName" | ||
* }}} | ||
* | ||
* @return name of case class field, possibility adjusted by [[com.avsystem.commons.serialization.name]] annotation | ||
*/ | ||
def codecFieldName[T](accessor: T => Any): String = | ||
macro com.avsystem.commons.macros.serialization.GenCodecUtilMacros.codecFieldName[T] | ||
|
||
/** | ||
* @return number of sealed hierarchy subclasses or `0` if specified type is not a hierarchy | ||
*/ | ||
@explicitGenerics | ||
def knownSubtypesCount[T]: Int = | ||
macro com.avsystem.commons.macros.serialization.GenCodecUtilMacros.knownSubtypesCount[T] | ||
} |
63 changes: 63 additions & 0 deletions
63
core/src/test/scala/com/avsystem/commons/serialization/GenCodecUtilsTest.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
package com.avsystem.commons | ||
package serialization | ||
|
||
import org.scalatest.funsuite.AnyFunSuite | ||
import org.scalatest.matchers.should.Matchers | ||
|
||
class GenCodecUtilsTest extends AnyFunSuite with Matchers { | ||
import GenCodecUtilsTest.* | ||
|
||
test("plain case class") { | ||
val name = GenCodecUtils.codecTypeName[Foo] | ||
name shouldBe "Foo" | ||
} | ||
|
||
test("case class with name annotation") { | ||
val name = GenCodecUtils.codecTypeName[Bar] | ||
name shouldBe "OtherBar" | ||
} | ||
|
||
test("case class with name annotation - using GencodecTypeName typeclass") { | ||
val name = GencodecTypeName[Bar].name | ||
name shouldBe "OtherBar" | ||
} | ||
|
||
test("plain field") { | ||
val name = GenCodecUtils.codecFieldName[Bar](_.str) | ||
name shouldBe "str" | ||
} | ||
|
||
test("field with name annotation") { | ||
val name = GenCodecUtils.codecFieldName[Foo](_.str) | ||
name shouldBe "otherStr" | ||
} | ||
|
||
test("accessor chain disallowed") { | ||
"GenCodecUtils.codecFieldName[Complex](_.str.str)" shouldNot compile | ||
} | ||
|
||
test("subtypes count hierarchy") { | ||
GenCodecUtils.knownSubtypesCount[ExampleHierarchy] shouldBe 3 | ||
} | ||
|
||
test("subtypes count leaf") { | ||
GenCodecUtils.knownSubtypesCount[CaseOther] shouldBe 0 | ||
} | ||
} | ||
|
||
object GenCodecUtilsTest { | ||
final case class Foo(@name("otherStr") str: String) | ||
object Foo extends HasGenCodec[Foo] | ||
|
||
@name("OtherBar") | ||
final case class Bar(str: String) | ||
object Bar extends HasGenCodec[Bar] | ||
|
||
final case class Complex(str: Foo) | ||
object Complex extends HasGenCodec[Complex] | ||
|
||
sealed trait ExampleHierarchy | ||
case class Case123() extends ExampleHierarchy | ||
case object CaseObj987 extends ExampleHierarchy | ||
case class CaseOther() extends ExampleHierarchy | ||
} |
47 changes: 47 additions & 0 deletions
47
macros/src/main/scala/com/avsystem/commons/macros/serialization/GenCodecUtilMacros.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
package com.avsystem.commons | ||
package macros.serialization | ||
|
||
import scala.annotation.tailrec | ||
import scala.reflect.macros.blackbox | ||
|
||
class GenCodecUtilMacros(ctx: blackbox.Context) extends CodecMacroCommons(ctx) { | ||
import c.universe._ | ||
|
||
final def Pkg: Tree = q"_root_.com.avsystem.commons.serialization" | ||
|
||
def codecTypeNameRaw[T: c.WeakTypeTag]: Tree = | ||
q"$extractName" | ||
|
||
def codecTypeName[T: c.WeakTypeTag]: Tree = | ||
q"new $Pkg.GencodecTypeName($extractName)" | ||
|
||
def codecFieldName[T: c.WeakTypeTag](accessor: Tree): Tree = { | ||
@tailrec | ||
def extract(tree: Tree): Name = tree match { | ||
case Ident(n) => n | ||
case Select(Select(_, _), _) => c.abort(c.enclosingPosition, s"Unsupported nested expression: $accessor") | ||
case Select(_, n) => n | ||
case Function(_, body) => extract(body) | ||
case Apply(func, _) => extract(func) | ||
case _ => c.abort(c.enclosingPosition, s"Unsupported expression: $accessor") | ||
} | ||
|
||
val name = extract(accessor) | ||
val tpe = weakTypeOf[T] | ||
val nameStr = | ||
applyUnapplyFor(tpe).flatMap(_.apply.asMethod.paramLists.flatten.iterator.find(_.name == name)) | ||
.orElse(tpe.members.iterator.filter(m => m.isMethod && m.isPublic).find(_.name == name)) | ||
.map(m => targetName(m)) | ||
.getOrElse(c.abort(c.enclosingPosition, s"$name is not a member of $tpe")) | ||
|
||
q"$nameStr" | ||
} | ||
|
||
def knownSubtypesCount[T: c.WeakTypeTag]: Tree = | ||
q"${knownSubtypes(weakTypeOf[T]).map(_.size).getOrElse(0)}" | ||
|
||
private def extractName[T: c.WeakTypeTag]: String = { | ||
val tType = weakTypeOf[T] | ||
targetName(tType.dealias.typeSymbol) | ||
} | ||
} |