Skip to content

Commit

Permalink
Migration SealedRowParserImpl
Browse files Browse the repository at this point in the history
  • Loading branch information
cchantep committed Aug 18, 2022
1 parent 81a4b32 commit 69a0b1e
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 5 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ private[anorm] object SealedRowParserImpl {
val caseName = TermName(c.freshName("discriminated"))
val key = q"$discriminate(${subclass.typeSymbol.fullName})"
val caseDecl = q"val $caseName = $key"
val subtype = {
val subtype = { // TODO: typeParams is not supported anyway
if (subclass.typeSymbol.asClass.typeParams.isEmpty) subclass
else subclass.erasure
}
Expand Down
5 changes: 3 additions & 2 deletions core/src/main/scala-3/anorm/Macro.scala
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,8 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta
case Some(n) =>
withColumn[T] { col =>
val cn = naming(Expr(n))
'{ anorm.SqlParser.get[T]($cn)($col) }

'{ SqlParser.get[T]($cn)($col) }
}

case _ =>
Expand Down Expand Up @@ -358,7 +359,7 @@ object Macro extends MacroOptions with macros.ValueColumn with macros.ValueToSta
Quotes,
Type[T]
): Expr[RowParser[T]] =
'{ ??? } // TODO: anorm.macros.SealedRowParserImpl[T](c)(naming, discriminate)
macros.SealedRowParserImpl[T](naming, discriminate)

private def parserImpl[T](genGet: (String, Int) => Expr[RowParser[T]])(using Quotes, Type[T]): Expr[RowParser[T]] = {
// TODO: anorm.macros.RowParserImpl[T](c)(genGet)
Expand Down
131 changes: 131 additions & 0 deletions core/src/main/scala-3/anorm/macros/SealedRowParserImpl.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package anorm.macros

import scala.quoted.{ Expr, Quotes, Type }

import anorm.Macro.{ debugEnabled, Discriminate, DiscriminatorNaming }
import anorm.{ Error, RowParser, SqlMappingError, SqlParser }

private[anorm] object SealedRowParserImpl {
def apply[A](
naming: Expr[DiscriminatorNaming],
discriminate: Expr[Discriminate]
)(using q: Quotes, tpe: Type[A]): Expr[RowParser[A]] = {
import q.reflect.*

val repr = TypeRepr.of[A](using tpe)

val subclasses = Inspect.knownSubclasses(q)(repr) match {
case Some(classes) =>
classes

case None =>
report.errorAndAbort(s"cannot find any subclass: ${repr.show}")
}

// ---

type CaseType[U <: A] = U

val subParsers = List.newBuilder[(TypeRepr, Expr[RowParser[_]])]

val missing: List[TypeRepr] = subclasses.flatMap { subcls =>
subcls.asType match {
case '[CaseType[sub]] =>
Expr.summon[RowParser[sub]] match {
case Some(subParser) => {
subParsers += subcls -> subParser

List.empty[TypeRepr]
}

case _ =>
List(subcls)
}

case _ =>
List(subcls)
}
}

if (missing.nonEmpty) {
def details = missing.map { subcls =>
s"- cannot find anorm.RowParser[${subcls.show}] in the implicit scope"
}
.mkString(",\r\n")

report.errorAndAbort(
s"fails to generate sealed parser: ${repr.show};\r\n$details")
}

// ---

val cases: List[(String, CaseDef)] = subParsers.result().map {
case (subcls, subParser) =>
val tpeSym = subcls.typeSymbol
val tpeName = {
if (tpeSym.flags is Flags.Module) tpeSym.fullName.stripSuffix(f"$$")
else tpeSym.fullName
}

val key = '{ $discriminate(${Expr(tpeName)}) }

val bind =
Symbol.newBind(
Symbol.spliceOwner,
tpeSym.name,
Flags.Case,
TypeRepr.of[String]
)

val ref = Ref(bind).asExprOf[String]

tpeSym.fullName -> CaseDef(
Bind(bind, Wildcard()),
guard = Some('{ $ref == $key }.asTerm),
rhs = subParser.asTerm)
}

def fallbackCase: CaseDef = {
val fallbackBind =
Symbol.newBind(
Symbol.spliceOwner,
"d",
Flags.Case,
TypeRepr.of[String]
)

val fallbackVal = Ref(fallbackBind).asExprOf[String]

CaseDef(
Bind(fallbackBind, Wildcard()),
guard = None,
rhs = '{
val msg = "unexpected row type '%s'; expected: %s".
format($fallbackVal, ${Expr(cases.map(_._1))}.mkString(", "))

RowParser.failed[A](Error(SqlMappingError(msg)))
}.asTerm
)
}

inline def body(inline discriminatorVal: Expr[String]): Expr[RowParser[A]] =
Match(
discriminatorVal.asTerm,
cases.map(_._2) :+ fallbackCase
).asExprOf[RowParser[A]]

val parser: Expr[RowParser[A]] = '{
val discriminatorCol = $naming(${Expr(repr.typeSymbol.fullName)})

SqlParser.str(discriminatorCol).flatMap { (discriminatorVal: String) =>
${ body('discriminatorVal) }
}
}

if (debugEnabled) {
report.info(s"row parser generated for $tpe: ${parser.show}")
}

parser
}
}
2 changes: 1 addition & 1 deletion core/src/main/scala-3/anorm/macros/ValueColumnImpl.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import anorm.Column
import anorm.Macro.debugEnabled

private[anorm] trait ValueColumn {
def valueColumnImpl[A <: AnyVal](using q: Quotes, tpe: Type[A]): Expr[Column[A]] = {
protected def valueColumnImpl[A <: AnyVal](using q: Quotes, tpe: Type[A]): Expr[Column[A]] = {
import q.reflect.*

val aTpr = TypeRepr.of[A](using tpe)
Expand Down
2 changes: 1 addition & 1 deletion core/src/main/scala-3/anorm/macros/ValueToStatement.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import anorm.Macro.debugEnabled
import anorm.ToStatement

private[anorm] trait ValueToStatement {
def valueToStatementImpl[A <: AnyVal](using q: Quotes, tpe: Type[A]): Expr[ToStatement[A]] = {
protected def valueToStatementImpl[A <: AnyVal](using q: Quotes, tpe: Type[A]): Expr[ToStatement[A]] = {

import q.reflect.*

Expand Down

0 comments on commit 69a0b1e

Please sign in to comment.