anorm.macros.RowParserImpl.scala Maven / Gradle / Ivy
The newest version!
/*
* Copyright (C) from 2022 The Play Framework Contributors , 2011-2021 Lightbend Inc.
*/
package anorm.macros
import scala.quoted.{ Expr, Quotes, Type }
import anorm.{ ~, Column, Row, RowParser, SqlResult }
import anorm.Macro.{ debugEnabled, RowParserGenerator }
private[anorm] object RowParserImpl {
def apply[A](
q: Quotes,
forwardExpr: Expr[RowParser[A]]
)(genGet: RowParserGenerator)(using tpe: Type[A], parserTpe: Type[RowParser]): Expr[Row => SqlResult[A]] = {
given quotes: Quotes = q
import q.reflect.*
val (repr, erased, aTArgs) = TypeRepr.of[A](using tpe) match {
case tpr @ AppliedType(e, args) =>
Tuple3(
tpr,
e,
args.collect {
case repr: TypeRepr =>
repr
}
)
case tpr =>
Tuple3(tpr, tpr, List.empty[TypeRepr])
}
@inline def abort(msg: String) = report.errorAndAbort(msg)
val tpeSym = repr.typeSymbol
if (!tpeSym.isClassDef || !tpeSym.flags.is(Flags.Case)) {
abort(s"case class expected: ${repr.show}")
}
// ---
val ctor = tpeSym.primaryConstructor
val (boundTypes, properties) = ctor.paramSymss match {
case targs :: paramss if targs.forall(_.isType) && paramss.headOption.exists(_.nonEmpty) => {
val boundTps = targs.zip(aTArgs).toMap
boundTps -> paramss
}
case params :: Nil if !params.exists(_.isType) =>
Map.empty[Symbol, TypeRepr] -> List(params)
case _ =>
report.errorAndAbort(s"${repr.show} constructor has no parameter")
}
if (properties.isEmpty) {
abort(s"parsed data cannot be passed as parameter: $ctor")
}
val debug = {
if (debugEnabled) report.info(_: String)
else (_: String) => {}
}
val resolv = ImplicitResolver[A](q).resolver(forwardExpr, Map.empty, debug)(parserTpe)
// ---
/*
* @tparam T the type of parsed data (single column or tuple-like `~`)
* @param parsing the parsing expression (e.g. `get("a") ~ get("b")`)
* @param parsedTpr the representation of type `T`
* @param matchPattern the match pattern to extract values inside `.map`
* @param columnSymss the column symbols bounds in the `matchPattern` (list of list as properties can be passed as multiple parameter list to the constructor)
*/
case class GenerationState[T](
parsing: Expr[RowParser[T]],
parsedTpr: TypeRepr,
matchPattern: Tree,
columnSymss: List[List[Symbol]]
)
val pkg = Symbol.requiredPackage("anorm")
val TildeSelect = (for {
ts <- pkg.declaredType("~").headOption.map(_.companionModule)
un <- ts.declaredMethod("unapply").headOption
} yield Ref(pkg).select(ts).select(un)) match {
case Some(select) =>
select
case _ =>
abort("Fails to resolve ~ symbol")
}
@annotation.tailrec
def prepare[T](
propss: List[List[Symbol]],
pi: Int,
combined: Option[GenerationState[T]],
hasSelfRef: Boolean,
hasGenericProperty: Boolean
)(using Type[T]): Option[Expr[Row => SqlResult[A]]] =
propss.headOption match {
case Some(sym :: localTail) => {
val tn = sym.name
val tt: TypeRepr = sym.tree match {
case vd: ValDef => {
val vtpe = vd.tpt.tpe
boundTypes.getOrElse(vtpe.typeSymbol, vtpe)
}
case _ =>
abort(s"Value definition expected for ${repr.show} constructor parameter: $sym")
}
val isGenericProp = tt match {
case AppliedType(_, as) =>
as.nonEmpty
case _ =>
false
}
val colSym: Symbol =
Symbol.newBind(Symbol.spliceOwner, tn, Flags.Case, tt)
// Pattern to match a single column in `.map` pattern matching
val singlePat = Bind(colSym, Typed(Wildcard(), Inferred(tt)))
tt.asType match {
case '[t] =>
def initialState(expr: Expr[RowParser[t]]) =
GenerationState[t](
parsing = expr,
parsedTpr = tt,
matchPattern = singlePat,
columnSymss = List(colSym) :: Nil
)
def combineState(
parent: GenerationState[T],
expr: Expr[RowParser[T ~ t]]
): GenerationState[T ~ t] = {
val pat = Unapply(
fun = TypeApply(TildeSelect, List(Inferred(parent.parsedTpr), Inferred(tt))),
implicits = Nil,
patterns = List(parent.matchPattern, singlePat)
)
GenerationState[T ~ t](
parsing = expr,
parsedTpr = TypeRepr.of[T ~ t],
matchPattern = pat,
columnSymss = parent.columnSymss match {
case headList :: tails =>
(colSym :: headList) :: tails
case _ => {
// Should have been handled by initialState, smth wrong
abort(s"Fails to handle initial state of RowParser generation: ${repr.show}")
}
}
)
}
Expr.summon[Column[t]] match {
case None =>
// ... try to resolve `RowParser[tt]`
resolv(tt) match {
case None =>
abort(s"cannot find Column nor RowParser for ${tn}:${tt.show} in ${ctor.fullName}")
case Some((pr, s)) => {
val hasSelf = if s then s else hasSelfRef
// Use an existing `RowParser[t]` as part
val tpr: Expr[RowParser[t]] = pr.asExprOf[RowParser[t]]
combined match {
case Some(parent @ GenerationState(prev, _, _, _)) =>
prepare[T ~ t](
propss = localTail :: propss.tail,
pi = pi + 1,
combined = Some {
combineState(parent, '{ $prev ~ $tpr })
},
hasSelfRef = hasSelf,
hasGenericProperty = isGenericProp || hasGenericProperty
)
case _ =>
prepare[t](
propss = localTail :: propss.tail,
pi = pi + 1,
combined = Some(initialState(tpr)),
hasSelfRef = hasSelf,
hasGenericProperty = isGenericProp || hasGenericProperty
)
}
}
}
case Some(col) => {
// Generate a `get` for the `Column[T]`
val get: Expr[RowParser[t]] = genGet[t](col, tn, pi)
combined match {
case Some(parent @ GenerationState(prev, _, _, _)) =>
prepare[T ~ t](
propss = localTail :: propss.tail,
pi = pi + 1,
combined = Some {
combineState(parent, '{ $prev ~ $get })
},
hasSelfRef = hasSelfRef,
hasGenericProperty = isGenericProp || hasGenericProperty
)
case None =>
prepare[t](
propss = localTail :: propss.tail,
pi = pi + 1,
combined = Some(initialState(get)),
hasSelfRef = hasSelfRef,
hasGenericProperty = isGenericProp || hasGenericProperty
)
}
}
}
}
}
case Some(Nil) if propss.tail.nonEmpty => {
// End of one parameter list for the properties, but there is more
prepare[T](
propss = propss.tail, // other parameter lists
pi = pi,
combined = combined match {
case Some(state) =>
Some(state.copy(columnSymss = Nil :: state.columnSymss))
case None =>
abort("Missing generation state: ${repr.show}")
},
hasSelfRef = hasSelfRef,
hasGenericProperty = hasGenericProperty
)
}
case Some(Nil) | None =>
combined match {
case None =>
None
case Some(GenerationState(parsing, _, matchPattern, revColss)) => {
val targs = boundTypes.values.toList
val colArgss = revColss.reverse.map {
_.reverse.map(Ref(_: Symbol))
}
val newTerm = New(Inferred(erased)).select(ctor)
val ctorCall: Expr[A] = {
if (targs.nonEmpty) {
newTerm.appliedToTypes(targs).appliedToArgss(colArgss)
} else {
newTerm.appliedToArgss(colArgss)
}
}.asExprOf[A]
val ctorCase = CaseDef(
pattern = matchPattern,
guard = None,
rhs = '{ anorm.Success[A](${ ctorCall }) }.asTerm
)
inline def cases[U: Type](inline parsed: Expr[U]) = {
// Workaround as in case of generic property
// (whose type has type arguments), false warning is raised
// about exhaustivity.
List(
ctorCase,
CaseDef(
Wildcard(),
guard = None,
rhs = '{
anorm.Error(
anorm.SqlMappingError(
"Unexpected parsed value: " + ${ parsed }
)
)
}.asTerm
)
)
}
inline def flatMapParsed[U: Type](inline parsed: Expr[U]): Expr[SqlResult[A]] =
Match(parsed.asTerm, cases(parsed)).asExprOf[SqlResult[A]]
Some('{
lazy val parsingRow = ${ parsing }
{ (row: Row) =>
parsingRow(row).flatMap { parsed =>
${ flatMapParsed('parsed) }
}
}
})
}
}
}
val generated: Expr[Row => SqlResult[A]] =
prepare[Nothing](properties, 0, None, false, false) match {
case Some(fn) =>
fn
case _ =>
abort(s"Fails to prepare the parser function: ${repr.show}")
}
debug(s"row parser generated for ${repr.show}: ${generated.show}")
generated
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy