From f9fef8dbc51ad2fd972e75aaefd30281b59d94e9 Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Thu, 10 Aug 2023 16:17:22 +0300 Subject: [PATCH 1/2] Optimize scala3's codegen --- .../caliban/schema/ArgBuilderDerivation.scala | 102 +++++++++--------- .../caliban/schema/SchemaDerivation.scala | 32 +++--- 2 files changed, 73 insertions(+), 61 deletions(-) diff --git a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala index 1f3da441ee..3eb6998432 100644 --- a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala +++ b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala @@ -30,60 +30,66 @@ trait CommonArgBuilderDerivation { inline def derived[A]: ArgBuilder[A] = inline summonInline[Mirror.Of[A]] match { case m: Mirror.SumOf[A] => - lazy val subTypes = recurse[m.MirroredElemLabels, m.MirroredElemTypes]() - lazy val traitLabel = constValue[m.MirroredLabel] - new ArgBuilder[A] { - def build(input: InputValue): Either[ExecutionError, A] = - buildSum[A](subTypes, traitLabel)(input) - } + makeSumArgBuilder[A]( + recurse[m.MirroredElemLabels, m.MirroredElemTypes](), + constValue[m.MirroredLabel] + ) case m: Mirror.ProductOf[A] => - lazy val fields = recurse[m.MirroredElemLabels, m.MirroredElemTypes]() - lazy val annotations = Macros.paramAnnotations[A].to(Map) - new ArgBuilder[A] { - def build(input: InputValue): Either[ExecutionError, A] = - buildProduct(fields, annotations)(input).map(m.fromProduct) - } + makeProductArgBuilder( + recurse[m.MirroredElemLabels, m.MirroredElemTypes](), + Macros.paramAnnotations[A].to(Map) + )(m.fromProduct) } - private def buildSum[A]( - subTypes: => List[(String, List[Any], ArgBuilder[Any])], - traitLabel: => String - )(input: InputValue) = - (input match { - case EnumValue(value) => Some(value) - case StringValue(value) => Some(value) - case _ => None - }) match { - case Some(value) => - subTypes.find { (label, annotations, _) => - label == value || annotations.exists { case GQLName(name) => name == value } - } match { - case Some((_, _, builder)) => builder.asInstanceOf[ArgBuilder[A]].build(InputValue.ObjectValue(Map())) - case None => Left(ExecutionError(s"Invalid value $value for trait $traitLabel")) - } - case None => Left(ExecutionError(s"Can't build a trait from input $input")) - } + private def makeSumArgBuilder[A]( + _subTypes: => List[(String, List[Any], ArgBuilder[Any])], + _traitLabel: => String + ) = new ArgBuilder[A] { + private lazy val subTypes = _subTypes + private lazy val traitLabel = _traitLabel - private def buildProduct( - fields: => List[(String, List[Any], ArgBuilder[Any])], - annotations: => Map[String, List[Any]] - )(input: InputValue) = - fields.map { (label, _, builder) => - input match { - case InputValue.ObjectValue(fields) => - val finalLabel = - annotations.getOrElse(label, Nil).collectFirst { case GQLName(name) => name }.getOrElse(label) - val default = annotations.getOrElse(label, Nil).collectFirst { case GQLDefault(v) => v } - fields.get(finalLabel).fold(builder.buildMissing(default))(builder.build) - case value => builder.build(value) - } - }.foldRight[Either[ExecutionError, Tuple]](Right(EmptyTuple)) { case (item, acc) => - item match { - case error: Left[ExecutionError, Any] => error.asInstanceOf[Left[ExecutionError, Tuple]] - case Right(value) => acc.map(value *: _) + def build(input: InputValue): Either[ExecutionError, A] = + (input match { + case EnumValue(value) => Some(value) + case StringValue(value) => Some(value) + case _ => None + }) match { + case Some(value) => + subTypes.find { (label, annotations, _) => + label == value || annotations.exists { case GQLName(name) => name == value } + } match { + case Some((_, _, builder)) => builder.asInstanceOf[ArgBuilder[A]].build(InputValue.ObjectValue(Map())) + case None => Left(ExecutionError(s"Invalid value $value for trait $traitLabel")) + } + case None => Left(ExecutionError(s"Can't build a trait from input $input")) } - } + } + + private def makeProductArgBuilder[A]( + _fields: => List[(String, List[Any], ArgBuilder[Any])], + _annotations: => Map[String, List[Any]] + )(fromProduct: Product => A) = new ArgBuilder[A] { + private lazy val fields = _fields + private lazy val annotations = _annotations + + def build(input: InputValue): Either[ExecutionError, A] = + fields.map { (label, _, builder) => + input match { + case InputValue.ObjectValue(fields) => + val finalLabel = + annotations.getOrElse(label, Nil).collectFirst { case GQLName(name) => name }.getOrElse(label) + val default = annotations.getOrElse(label, Nil).collectFirst { case GQLDefault(v) => v } + fields.get(finalLabel).fold(builder.buildMissing(default))(builder.build) + case value => builder.build(value) + } + }.foldRight[Either[ExecutionError, Tuple]](Right(EmptyTuple)) { case (item, acc) => + item match { + case error: Left[ExecutionError, Any] => error.asInstanceOf[Left[ExecutionError, Tuple]] + case Right(value) => acc.map(value *: _) + } + }.map(fromProduct) + } } trait ArgBuilderDerivation extends CommonArgBuilderDerivation { diff --git a/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala b/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala index 2a560a64fc..5899490504 100644 --- a/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala +++ b/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala @@ -58,24 +58,28 @@ trait CommonSchemaDerivation { inline def derived[R, A]: Schema[R, A] = inline summonInline[Mirror.Of[A]] match { case m: Mirror.SumOf[A] => - lazy val members = recurse[R, A, m.MirroredElemLabels, m.MirroredElemTypes]()() - def info = Macros.typeInfo[A] - def annotations = Macros.annotations[A] - makeSumSchema[R, A](members, info, annotations)(m) + makeSumSchema[R, A]( + recurse[R, A, m.MirroredElemLabels, m.MirroredElemTypes]()(), + Macros.typeInfo[A], + Macros.annotations[A] + )(m.ordinal) case m: Mirror.ProductOf[A] => - lazy val fields = recurse[R, A, m.MirroredElemLabels, m.MirroredElemTypes]()() - def annotations = Macros.annotations[A] - def info = Macros.typeInfo[A] - def paramAnnotations = Macros.paramAnnotations[A].toMap - makeProductSchema[R, A](fields, info, annotations, paramAnnotations) + makeProductSchema[R, A]( + recurse[R, A, m.MirroredElemLabels, m.MirroredElemTypes]()(), + Macros.typeInfo[A], + Macros.annotations[A], + Macros.paramAnnotations[A].toMap + ) } private def makeSumSchema[R, A]( - members: => List[(String, List[Any], Schema[R, Any], Int)], + _members: => List[(String, List[Any], Schema[R, Any], Int)], info: TypeInfo, annotations: List[Any] - )(m: Mirror.SumOf[A]): Schema[R, A] = new Schema[R, A] { + )(ordinal: A => Int): Schema[R, A] = new Schema[R, A] { + + private lazy val members = _members private lazy val subTypes = members.map { case (label, subTypeAnnotations, schema, _) => (label, schema.toType_(), subTypeAnnotations) @@ -112,18 +116,20 @@ trait CommonSchemaDerivation { } def resolve(value: A): Step[R] = { - val (label, _, schema, _) = members(m.ordinal(value)) + val (label, _, schema, _) = members(ordinal(value)) if (isEnum) PureStep(EnumValue(label)) else schema.resolve(value) } } private def makeProductSchema[R, A]( - fields: => List[(String, List[Any], Schema[R, Any], Int)], + _fields: => List[(String, List[Any], Schema[R, Any], Int)], info: TypeInfo, annotations: List[Any], paramAnnotations: Map[String, List[Any]] ): Schema[R, A] = new Schema[R, A] { + private lazy val fields = _fields + private lazy val isValueType: Boolean = annotations.exists { case GQLValueType(_) => true From 631ed9a917f042a8a72fcffd255fb8fa42d4ae4e Mon Sep 17 00:00:00 2001 From: Kyri Petrou Date: Thu, 10 Aug 2023 20:19:15 +0300 Subject: [PATCH 2/2] Cleanup ArgBuilder logic for sum and product types --- .../caliban/schema/ArgBuilderDerivation.scala | 45 ++++++++++--------- .../caliban/schema/SchemaDerivation.scala | 1 - 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala index 3eb6998432..d1af1817ba 100644 --- a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala +++ b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala @@ -1,7 +1,7 @@ package caliban.schema import caliban.CalibanError.ExecutionError -import caliban.InputValue +import caliban.{ CalibanError, InputValue } import caliban.Value.* import caliban.schema.macros.Macros import caliban.schema.Annotations.GQLDefault @@ -48,21 +48,24 @@ trait CommonArgBuilderDerivation { ) = new ArgBuilder[A] { private lazy val subTypes = _subTypes private lazy val traitLabel = _traitLabel + private val emptyInput = InputValue.ObjectValue(Map()) def build(input: InputValue): Either[ExecutionError, A] = - (input match { - case EnumValue(value) => Some(value) - case StringValue(value) => Some(value) - case _ => None - }) match { - case Some(value) => - subTypes.find { (label, annotations, _) => - label == value || annotations.exists { case GQLName(name) => name == value } - } match { - case Some((_, _, builder)) => builder.asInstanceOf[ArgBuilder[A]].build(InputValue.ObjectValue(Map())) - case None => Left(ExecutionError(s"Invalid value $value for trait $traitLabel")) - } - case None => Left(ExecutionError(s"Can't build a trait from input $input")) + input.match { + case EnumValue(value) => Right(value) + case StringValue(value) => Right(value) + case _ => Left(ExecutionError(s"Can't build a trait from input $input")) + }.flatMap { value => + subTypes.collectFirst { + case ( + label, + annotations, + builder: ArgBuilder[A @unchecked] + ) if label == value || annotations.exists { case GQLName(name) => name == value } => + builder + } + .toRight(ExecutionError(s"Invalid value $value for trait $traitLabel")) + .flatMap(_.build(emptyInput)) } } @@ -74,19 +77,19 @@ trait CommonArgBuilderDerivation { private lazy val annotations = _annotations def build(input: InputValue): Either[ExecutionError, A] = - fields.map { (label, _, builder) => + fields.view.map { (label, _, builder) => input match { case InputValue.ObjectValue(fields) => - val finalLabel = - annotations.getOrElse(label, Nil).collectFirst { case GQLName(name) => name }.getOrElse(label) - val default = annotations.getOrElse(label, Nil).collectFirst { case GQLDefault(v) => v } + val labelList = annotations.get(label) + def default = labelList.flatMap(_.collectFirst { case GQLDefault(v) => v }) + val finalLabel = labelList.flatMap(_.collectFirst { case GQLName(name) => name }).getOrElse(label) fields.get(finalLabel).fold(builder.buildMissing(default))(builder.build) case value => builder.build(value) } - }.foldRight[Either[ExecutionError, Tuple]](Right(EmptyTuple)) { case (item, acc) => + }.foldLeft[Either[ExecutionError, Tuple]](Right(EmptyTuple)) { case (acc, item) => item match { - case error: Left[ExecutionError, Any] => error.asInstanceOf[Left[ExecutionError, Tuple]] - case Right(value) => acc.map(value *: _) + case Right(value) => acc.map(_ :* value) + case Left(e) => Left(e) } }.map(fromProduct) } diff --git a/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala b/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala index 5899490504..f7559b9aa9 100644 --- a/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala +++ b/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala @@ -11,7 +11,6 @@ import caliban.schema.macros.{ Macros, TypeInfo } import scala.compiletime.* import scala.deriving.Mirror import scala.util.NotGiven -import scala.quoted.* object PrintDerived { import scala.quoted.*