diff --git a/.circleci/config.yml b/.circleci/config.yml index 41303d56c..fc5f7a848 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,6 +8,7 @@ jobs: - restore_cache: key: sbtcache - run: sbt ++2.13.6! check + - run: sbt ++3.0.2! check - save_cache: key: sbtcache paths: diff --git a/.scalafmt.conf b/.scalafmt.conf index 01b62bc04..b06f43c0c 100644 --- a/.scalafmt.conf +++ b/.scalafmt.conf @@ -1,4 +1,4 @@ -version = "3.0.6" +version = "3.0.7" runner.dialect = scala213 maxColumn = 120 diff --git a/core/src/main/scala-3/caliban/Macros.scala b/core/src/main/scala-3/caliban/Macros.scala index 621ecf0ac..c30cb760e 100644 --- a/core/src/main/scala-3/caliban/Macros.scala +++ b/core/src/main/scala-3/caliban/Macros.scala @@ -1,4 +1,4 @@ -package caliban +package caliban import scala.quoted._ diff --git a/core/src/main/scala-3/caliban/parsing/Parser.scala b/core/src/main/scala-3/caliban/parsing/Parser.scala index 77787a021..7c8eeb810 100644 --- a/core/src/main/scala-3/caliban/parsing/Parser.scala +++ b/core/src/main/scala-3/caliban/parsing/Parser.scala @@ -94,7 +94,7 @@ object Parser { } def unescape(str: String): Either[Int, String] = { - val sb = new java.lang.StringBuilder + val sb = new java.lang.StringBuilder def decodeNum(idx: Int, size: Int, base: Int): Int = { val end = idx + size if (end <= str.length) { @@ -240,7 +240,7 @@ object Parser { private val listType: P[ListType] = (wrapSquareBrackets(type_) ~ P.char('!').?).map { case (typ, nonNull) => ListType(typ, nonNull = nonNull.nonEmpty) } - private lazy val type_ : P[Type] = P.defer(P.oneOf(namedType :: listType :: Nil)) + private lazy val type_ : P[Type] = P.defer(P.oneOf(namedType :: listType :: Nil)) private val argumentDefinition: P[InputValueDefinition] = (((stringValue <* whitespaceWithComment1).?.with1 ~ name <* wrapWhitespaces(P.char(':'))) ~ @@ -286,8 +286,9 @@ object Parser { private val fragmentName: P[String] = name.filter(_ != "on") private val fragmentSpread: P[FragmentSpread] = - ((P.string("...").soft *> fragmentName <* whitespaceWithComment).backtrack ~ directives.?).map { case (name, dirs) => - FragmentSpread(name, dirs.getOrElse(Nil)) + ((P.string("...").soft *> fragmentName <* whitespaceWithComment).backtrack ~ directives.?).map { + case (name, dirs) => + FragmentSpread(name, dirs.getOrElse(Nil)) } private val typeCondition: P[NamedType] = P.string("on") *> whitespaceWithComment1 *> namedType diff --git a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala index 164a877b4..114224d1a 100644 --- a/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala +++ b/core/src/main/scala-3/caliban/schema/ArgBuilderDerivation.scala @@ -14,58 +14,56 @@ trait ArgBuilderDerivation { inline def recurse[Label, A <: Tuple]: List[(String, List[Any], ArgBuilder[Any])] = inline erasedValue[(Label, A)] match { case (_: (name *: names), _: (t *: ts)) => - val label = constValue[name].toString + val label = constValue[name].toString val annotations = Macros.annotations[t] - val builder = summonInline[ArgBuilder[t]].asInstanceOf[ArgBuilder[Any]] + val builder = summonInline[ArgBuilder[t]].asInstanceOf[ArgBuilder[Any]] (label, annotations, builder) :: recurse[names, ts] - case (_: EmptyTuple, _) => Nil + case (_: EmptyTuple, _) => Nil } 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 subTypes = recurse[m.MirroredElemLabels, m.MirroredElemTypes] lazy val traitLabel = constValue[m.MirroredLabel] new ArgBuilder[A] { - def build(input: InputValue): Either[ExecutionError, A] = { + 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, _) => - annotations.collectFirst { case GQLName(name) => name }.contains(value) || label == value - } match { + subTypes.find { (label, annotations, _) => + annotations.collectFirst { case GQLName(name) => name }.contains(value) || label == 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"Invalid value $value for trait $traitLabel")) } case None => Left(ExecutionError(s"Can't build a trait from input $input")) } - } } case m: Mirror.ProductOf[A] => - lazy val fields = recurse[m.MirroredElemLabels, m.MirroredElemTypes] + 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] = { + 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 } + 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) + 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 *: _) + case Right(value) => acc.map(value *: _) } }.map(m.fromProduct) - } } } diff --git a/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala b/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala index 1758f5a51..ccba75936 100644 --- a/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala +++ b/core/src/main/scala-3/caliban/schema/SchemaDerivation.scala @@ -6,7 +6,7 @@ import caliban.parsing.adt.Directive import caliban.schema.Annotations._ import caliban.schema.Step.ObjectStep import caliban.schema.Types._ -import caliban.schema.macros.{Macros, TypeInfo} +import caliban.schema.macros.{ Macros, TypeInfo } import scala.deriving.Mirror import scala.compiletime._ @@ -24,27 +24,27 @@ trait SchemaDerivation[R] { inline def recurse[Label, A <: Tuple](index: Int = 0): List[(String, List[Any], Schema[R, Any], Int)] = inline erasedValue[(Label, A)] match { case (_: (name *: names), _: (t *: ts)) => - val label = constValue[name].toString + val label = constValue[name].toString val annotations = Macros.annotations[t] - val builder = summonInline[Schema[R, t]].asInstanceOf[Schema[R, Any]] + val builder = summonInline[Schema[R, t]].asInstanceOf[Schema[R, Any]] (label, annotations, builder, index) :: recurse[names, ts](index + 1) - case (_: EmptyTuple, _) => Nil + case (_: EmptyTuple, _) => Nil } inline def derived[A]: Schema[R, A] = inline summonInline[Mirror.Of[A]] match { - case m: Mirror.SumOf[A] => - lazy val members = recurse[m.MirroredElemLabels, m.MirroredElemTypes]() - lazy val info = Macros.typeInfo[A] + case m: Mirror.SumOf[A] => + lazy val members = recurse[m.MirroredElemLabels, m.MirroredElemTypes]() + lazy val info = Macros.typeInfo[A] lazy val annotations = Macros.annotations[A] - lazy val subTypes = - members - .map { case (label, subTypeAnnotations, schema, _) => (label, schema.toType_(), subTypeAnnotations) } - .sortBy { case (label, _, _) => label } - lazy val isEnum = subTypes.forall { + lazy val subTypes = + members.map { case (label, subTypeAnnotations, schema, _) => + (label, schema.toType_(), subTypeAnnotations) + }.sortBy { case (label, _, _) => label } + lazy val isEnum = subTypes.forall { case (_, t, _) - if t.fields(__DeprecatedArgs(Some(true))).forall(_.isEmpty) - && t.inputFields.forall(_.isEmpty) => + if t.fields(__DeprecatedArgs(Some(true))).forall(_.isEmpty) + && t.inputFields.forall(_.isEmpty) => true case _ => false } @@ -52,13 +52,13 @@ trait SchemaDerivation[R] { case GQLInterface() => true case _ => false } - lazy val isUnion = annotations.exists { + lazy val isUnion = annotations.exists { case GQLUnion() => true case _ => false } new Schema[R, A] { - def toType(isInput: Boolean, isSubscription: Boolean): __Type = { + def toType(isInput: Boolean, isSubscription: Boolean): __Type = if (isEnum && subTypes.nonEmpty && !isInterface && !isUnion) { makeEnum( Some(getName(annotations, info)), @@ -74,19 +74,20 @@ trait SchemaDerivation[R] { Some(info.full) ) } else if (!isInterface) - makeUnion( - Some(getName(annotations, info)), - getDescription(annotations), - subTypes.map { case (_, t, _) => fixEmptyUnionObject(t) }, - Some(info.full) - ) - else { - val impl = subTypes.map(_._2.copy(interfaces = () => Some(List(toType(isInput, isSubscription))))) - val commonFields = () => impl + makeUnion( + Some(getName(annotations, info)), + getDescription(annotations), + subTypes.map { case (_, t, _) => fixEmptyUnionObject(t) }, + Some(info.full) + ) + else { + val impl = subTypes.map(_._2.copy(interfaces = () => Some(List(toType(isInput, isSubscription))))) + val commonFields = () => + impl .flatMap(_.fields(__DeprecatedArgs(Some(true)))) .flatten .groupBy(_.name) - .filter({ case (name, list) => list.lengthCompare(impl.size) == 0 }) + .filter { case (name, list) => list.lengthCompare(impl.size) == 0 } .collect { case (name, list) => Types .unify(list.map(_.`type`())) @@ -95,9 +96,14 @@ trait SchemaDerivation[R] { .flatten .toList - makeInterface(Some(getName(annotations, info)), getDescription(annotations), commonFields, impl, Some(info.full)) - } - } + makeInterface( + Some(getName(annotations, info)), + getDescription(annotations), + commonFields, + impl, + Some(info.full) + ) + } def resolve(value: A): Step[R] = { val (label, _, schema, _) = members(m.ordinal(value)) @@ -105,9 +111,9 @@ trait SchemaDerivation[R] { } } case m: Mirror.ProductOf[A] => - lazy val annotations = Macros.annotations[A] - lazy val fields = recurse[m.MirroredElemLabels, m.MirroredElemTypes]() - lazy val info = Macros.typeInfo[A] + lazy val annotations = Macros.annotations[A] + lazy val fields = recurse[m.MirroredElemLabels, m.MirroredElemTypes]() + lazy val info = Macros.typeInfo[A] lazy val paramAnnotations = Macros.paramAnnotations[A].toMap new Schema[R, A] { def toType(isInput: Boolean, isSubscription: Boolean): __Type = @@ -117,40 +123,38 @@ trait SchemaDerivation[R] { Some(annotations.collectFirst { case GQLInputName(suffix) => suffix } .getOrElse(customizeInputTypeName(getName(annotations, info)))), getDescription(annotations), - fields - .map { case (label, _, schema, _) => - val fieldAnnotations = paramAnnotations.getOrElse(label, Nil) - __InputValue( - getName(fieldAnnotations, label), - getDescription(fieldAnnotations), - () => - if (schema.optional) schema.toType_(isInput, isSubscription) - else makeNonNull(schema.toType_(isInput, isSubscription)), - getDefaultValue(fieldAnnotations), - Some(fieldAnnotations.collect { case GQLDirective(dir) => dir }).filter(_.nonEmpty) - ) - }, + fields.map { case (label, _, schema, _) => + val fieldAnnotations = paramAnnotations.getOrElse(label, Nil) + __InputValue( + getName(fieldAnnotations, label), + getDescription(fieldAnnotations), + () => + if (schema.optional) schema.toType_(isInput, isSubscription) + else makeNonNull(schema.toType_(isInput, isSubscription)), + getDefaultValue(fieldAnnotations), + Some(fieldAnnotations.collect { case GQLDirective(dir) => dir }).filter(_.nonEmpty) + ) + }, Some(info.full) ) else makeObject( Some(getName(annotations, info)), getDescription(annotations), - fields - .map { case (label, _, schema, _) => - val fieldAnnotations = paramAnnotations.getOrElse(label, Nil) - __Field( - getName(fieldAnnotations, label), - getDescription(fieldAnnotations), - schema.arguments, - () => - if (schema.optional) schema.toType_(isInput, isSubscription) - else makeNonNull(schema.toType_(isInput, isSubscription)), - fieldAnnotations.collectFirst { case GQLDeprecated(_) => () }.isDefined, - fieldAnnotations.collectFirst { case GQLDeprecated(reason) => reason }, - Option(fieldAnnotations.collect { case GQLDirective(dir) => dir }).filter(_.nonEmpty) - ) - }, + fields.map { case (label, _, schema, _) => + val fieldAnnotations = paramAnnotations.getOrElse(label, Nil) + __Field( + getName(fieldAnnotations, label), + getDescription(fieldAnnotations), + schema.arguments, + () => + if (schema.optional) schema.toType_(isInput, isSubscription) + else makeNonNull(schema.toType_(isInput, isSubscription)), + fieldAnnotations.collectFirst { case GQLDeprecated(_) => () }.isDefined, + fieldAnnotations.collectFirst { case GQLDeprecated(reason) => reason }, + Option(fieldAnnotations.collect { case GQLDirective(dir) => dir }).filter(_.nonEmpty) + ) + }, getDirectives(annotations), Some(info.full) ) @@ -164,12 +168,14 @@ trait SchemaDerivation[R] { val fieldsBuilder = Map.newBuilder[String, Step[R]] fields.foreach { case (label, _, schema, index) => val fieldAnnotations = paramAnnotations.getOrElse(label, Nil) - fieldsBuilder += getName(fieldAnnotations, label) -> schema.resolve(value.asInstanceOf[Product].productElement(index)) + fieldsBuilder += getName(fieldAnnotations, label) -> schema.resolve( + value.asInstanceOf[Product].productElement(index) + ) } ObjectStep(getName(annotations, info), fieldsBuilder.result()) } } - } + } // see https://github.com/graphql/graphql-spec/issues/568 private def fixEmptyUnionObject(t: __Type): __Type = @@ -204,7 +210,7 @@ trait SchemaDerivation[R] { private def isValueType(annotations: Seq[Any]): Boolean = annotations.exists { case GQLValueType() => true - case _ => false + case _ => false } private def getName(annotations: Seq[Any], label: String): String = diff --git a/core/src/main/scala-3/caliban/schema/SubscriptionSchemaDerivation.scala b/core/src/main/scala-3/caliban/schema/SubscriptionSchemaDerivation.scala index e6e069943..37d188abe 100644 --- a/core/src/main/scala-3/caliban/schema/SubscriptionSchemaDerivation.scala +++ b/core/src/main/scala-3/caliban/schema/SubscriptionSchemaDerivation.scala @@ -7,7 +7,7 @@ trait SubscriptionSchemaDerivation { inline def checkParams[T <: Tuple]: Unit = inline erasedValue[T] match { case _: EmptyTuple => () - case _: (t *: ts) => + case _: (t *: ts) => summonInline[SubscriptionSchema[t]] checkParams[ts] } diff --git a/core/src/main/scala-3/caliban/schema/macros/Macros.scala b/core/src/main/scala-3/caliban/schema/macros/Macros.scala index a3f00b4cb..bca07246a 100644 --- a/core/src/main/scala-3/caliban/schema/macros/Macros.scala +++ b/core/src/main/scala-3/caliban/schema/macros/Macros.scala @@ -6,9 +6,9 @@ private[caliban] object Macros { // this code was inspired from WIP in magnolia // https://github.com/propensive/magnolia/blob/b937cf2c7dabebb8236e7e948f37a354777fa9b7/src/core/macro.scala - inline def annotations[T]: List[Any] = ${annotationsImpl[T]} - inline def paramAnnotations[T]: List[(String, List[Any])] = ${paramAnnotationsImpl[T]} - inline def typeInfo[T]: TypeInfo = ${typeInfoImpl[T]} + inline def annotations[T]: List[Any] = ${ annotationsImpl[T] } + inline def paramAnnotations[T]: List[(String, List[Any])] = ${ paramAnnotationsImpl[T] } + inline def typeInfo[T]: TypeInfo = ${ typeInfoImpl[T] } def annotationsImpl[T: Type](using qctx: Quotes): Expr[List[Any]] = { import qctx.reflect.* @@ -27,9 +27,9 @@ private[caliban] object Macros { tpe.typeSymbol.primaryConstructor.paramSymss.flatten.map { field => Expr(field.name) -> field.annotations.filter { a => a.tpe.typeSymbol.maybeOwner.isNoSymbol || - a.tpe.typeSymbol.owner.fullName != "scala.annotation.internal" + a.tpe.typeSymbol.owner.fullName != "scala.annotation.internal" }.map(_.asExpr.asInstanceOf[Expr[Any]]) - }.filter(_._2.nonEmpty).map { (name, anns) => Expr.ofTuple(name, Expr.ofList(anns)) } + }.filter(_._2.nonEmpty).map((name, anns) => Expr.ofTuple(name, Expr.ofList(anns))) } } @@ -37,7 +37,7 @@ private[caliban] object Macros { import qctx.reflect._ def normalizedName(s: Symbol): String = if s.flags.is(Flags.Module) then s.name.stripSuffix("$") else s.name - def name(tpe: TypeRepr) : Expr[String] = Expr(normalizedName(tpe.typeSymbol)) + def name(tpe: TypeRepr): Expr[String] = Expr(normalizedName(tpe.typeSymbol)) def ownerNameChain(sym: Symbol): List[String] = if sym.isNoSymbol then List.empty @@ -50,9 +50,9 @@ private[caliban] object Macros { def typeInfo(tpe: TypeRepr): Expr[TypeInfo] = tpe match case AppliedType(tpe, args) => - '{TypeInfo(${owner(tpe)}, ${name(tpe)}, ${Expr.ofList(args.map(typeInfo))})} - case _ => - '{TypeInfo(${owner(tpe)}, ${name(tpe)}, Nil)} + '{ TypeInfo(${ owner(tpe) }, ${ name(tpe) }, ${ Expr.ofList(args.map(typeInfo)) }) } + case _ => + '{ TypeInfo(${ owner(tpe) }, ${ name(tpe) }, Nil) } typeInfo(TypeRepr.of[T]) }