All Downloads are FREE. Search and download functionalities are using the official Maven repository.

sttp.tapir.internal.CaseClass.scala Maven / Gradle / Ivy

There is a newer version: 1.11.11
Show newest version
package sttp.tapir.internal

import scala.quoted.*

private[tapir] class CaseClass[Q <: Quotes, T: Type](using val q: Q) {
  import q.reflect.*

  val tpe = TypeRepr.of[T]
  val symbol = tpe.typeSymbol

  if !symbol.flags.is(Flags.Case) then
    report.errorAndAbort(s"CaseClass can be instantiated only for case classes, but got: ${summon[Type[T]]}")

  def name = symbol.name

  def fields: List[CaseClassField[Q, T]] =
    symbol.caseFields.zip(symbol.primaryConstructor.paramSymss.head).map { (caseField, constructorField) =>
      new CaseClassField[Q, T](using q, summon[Type[T]])(caseField, constructorField, tpe.memberType(caseField))
    }

  def instanceFromValues(values: Expr[Seq[Any]]): Expr[T] = '{
    val valuesVector = $values.toVector
    ${
      Apply(
        Select.unique(New(Inferred(tpe)), ""),
        symbol.caseFields.zipWithIndex.map { (field: Symbol, i: Int) =>
          TypeApply(
            Select.unique('{ valuesVector.apply(${ Expr(i) }) }.asTerm, "asInstanceOf"),
            List(Inferred(tpe.memberType(field)))
          )
        }
      ).asExprOf[T]
    }
  }

  def instanceFromValues(values: Seq[Expr[Any]]): Expr[T] = {
    val valuesVector = values.toVector
    Apply(
      Select.unique(New(Inferred(tpe)), ""),
      symbol.caseFields.zipWithIndex.map { (field: Symbol, i: Int) =>
        TypeApply(
          Select.unique(valuesVector(i).asTerm, "asInstanceOf"),
          List(Inferred(tpe.memberType(field)))
        )
      }
    ).asExprOf[T]
  }

  /** Extracts an optional argument from an annotation with a single string-valued argument with a default value. */
  def extractOptStringArgFromAnnotation(annSymbol: Symbol): Option[Option[String]] = {
    symbol.getAnnotation(annSymbol).map {
      case Apply(_, List(Select(_, "$lessinit$greater$default$1")))             => None
      case Apply(_, List(Literal(c: Constant))) if c.value.isInstanceOf[String] => Some(c.value.asInstanceOf[String])
      case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from class: ${symbol.name}")
    }
  }
}

// The `symbol` is needed to get the field type and generate instance selects. The `constructorField` symbol is needed
// to read annotations.
private[tapir] class CaseClassField[Q <: Quotes, T](using val q: Q, t: Type[T])(
    val symbol: q.reflect.Symbol,
    constructorField: q.reflect.Symbol,
    val tpe: q.reflect.TypeRepr
) {
  import q.reflect.*

  def name = symbol.name

  /** Extracts an argument from an annotation with a single string-valued argument. */
  def extractStringArgFromAnnotation(annSymbol: Symbol): Option[String] = constructorField.getAnnotation(annSymbol).map {
    case Apply(_, List(Literal(c: Constant))) if c.value.isInstanceOf[String] => c.value.asInstanceOf[String]
    case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
  }

  /** Extracts an optional argument from an annotation with a single string-valued argument with a default value. */
  def extractOptStringArgFromAnnotation(annSymbol: Symbol): Option[Option[String]] = {
    constructorField.getAnnotation(annSymbol).map {
      case Apply(_, List(Select(_, "$lessinit$greater$default$1")))             => None
      case Apply(_, List(Literal(c: Constant))) if c.value.isInstanceOf[String] => Some(c.value.asInstanceOf[String])
      case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
    }
  }

  def extractTreeFromAnnotation(annSymbol: Symbol): Option[Tree] = constructorField.getAnnotation(annSymbol).map {
    case Apply(_, List(t)) => t
    case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
  }

  def extractFirstTreeArgFromAnnotation(annSymbol: Symbol): Option[Tree] = constructorField.getAnnotation(annSymbol).map {
    case Apply(_, List(t, _*)) => t
    case _ => report.errorAndAbort(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
  }

  def annotated(annSymbol: Symbol): Boolean = annotation(annSymbol).isDefined
  def annotation(annSymbol: Symbol): Option[Term] = constructorField.getAnnotation(annSymbol)
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy