sangria-graphql / sangria

Scala GraphQL implementation
https://sangria-graphql.github.io
Apache License 2.0
1.97k stars 221 forks source link

Scala 3 Derive Macro Compiler Error #905

Open PsyfireX opened 1 year ago

PsyfireX commented 1 year ago

Dependencies:


scalaVersion := "3.1.3"

libraryDependencies ++= Seq(
      "org.sangria-graphql"  %% "sangria"             % "3.3.0-RC1",
      "org.sangria-graphql"  %% "sangria-circe"       % "1.3.2",
      "io.circe"             %% "circe-parser"        % "0.14.2",
      "io.circe"             %% "circe-generic"       % "0.14.2",
    )

Code:

import sangria.schema.ObjectType
import scala.concurrent.Future
import sangria.schema.InputObjectType

case class Ctx(ctx: String)

object PostSchema {
  import sangria.marshalling.circe._
  import io.circe.generic.auto._
  import sangria.macros.derive._

  case class UpdatePostGql() {
    @GraphQLField
    def text(text: TextInputGql): Future[String] = ???
  }

  object UpdatePostGql{
    implicit val _ot: ObjectType[Ctx, UpdatePostGql] =
      deriveObjectType[Ctx, UpdatePostGql](
        ObjectTypeName("UpdatePost")
      )
  }

  case class TextInputGql(text: String)

  object TextInputGql{
    implicit val _it: InputObjectType[TextInputGql] =
      deriveInputObjectType[TextInputGql](
        InputObjectTypeName("TextInput")
      )
  }
}

Error:

[error] -- Error: {path}\src\main\scala\PostSchema.scala:35:42
[error] 35 |      deriveObjectType[Ctx, UpdatePostGql](
[error]    |      ^
[error]    |Exception occurred while executing macro expansion.
[error]    |scala.MatchError: OrType(AndType(TypeRef(ThisType(TypeRef(ThisType(TypeRef(NoPrefix,module class schema)),module class PostSchema$)),class TextInputGql),AppliedType(TypeRef(TermRef(TermRef(ThisType(TypeRef(NoPrefix,module class sangria)),object util),tag),type Tagged),List(TypeRef(TermRef(TermRef(ThisType(TypeRef(NoPrefix,module class sangria)),object marshalling),FromInput),InputObjectResult)))),TypeRef(TermRef(ThisType(TypeRef(NoPrefix,module class <root>)),object scala),Null)) (of class dotty.tools.dotc.core.Types$CachedOrType)
[error]    |    at dotty.tools.dotc.transform.TypeUtils$.companionRef(TypeUtils.scala:91)
[error]    |    at dotty.tools.dotc.typer.Synthesizer.companionPath(Synthesizer.scala:252)
[error]    |    at dotty.tools.dotc.typer.Synthesizer.productMirror(Synthesizer.scala:311)
[error]    |    at dotty.tools.dotc.typer.Synthesizer.$init$$$anonfun$6$$anonfun$1$$anonfun$1$$anonfun$1(Synthesizer.scala:396)
[error]    |    at dotty.tools.dotc.typer.Synthesizer.makeMirror(Synthesizer.scala:388)
[error]    |    at dotty.tools.dotc.typer.Synthesizer.$init$$$anonfun$6$$anonfun$1(Synthesizer.scala:396)
[error]    |    at dotty.tools.dotc.typer.Synthesizer.$init$$$anonfun$8$$anonfun$1(Synthesizer.scala:413)
[error]    |    at dotty.tools.dotc.typer.Synthesizer.recur$1(Synthesizer.scala:556)
[error]    |    at dotty.tools.dotc.typer.Synthesizer.tryAll(Synthesizer.scala:561)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicitArg(Implicits.scala:857)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicitArg$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.inferImplicitArg(Typer.scala:117)
[error]    |    at dotty.tools.dotc.typer.Typer.implicitArgs$1(Typer.scala:3451)
[error]    |    at dotty.tools.dotc.typer.Typer.addImplicitArgs$1(Typer.scala:3487)
[error]    |    at dotty.tools.dotc.typer.Typer.adaptNoArgsImplicitMethod$1(Typer.scala:3563)
[error]    |    at dotty.tools.dotc.typer.Typer.adaptNoArgs$1(Typer.scala:3761)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt1(Typer.scala:3995)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt(Typer.scala:3329)
[error]    |    at dotty.tools.dotc.typer.Typer.readapt$1(Typer.scala:3340)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt1(Typer.scala:3982)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt(Typer.scala:3329)
[error]    |    at dotty.tools.dotc.typer.Implicits.typedImplicit(Implicits.scala:1053)
[error]    |    at dotty.tools.dotc.typer.Implicits.typedImplicit$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.typedImplicit(Typer.scala:117)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.tryImplicit(Implicits.scala:1173)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.rank$1(Implicits.scala:1272)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.searchImplicit(Implicits.scala:1442)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.searchImplicit(Implicits.scala:1470)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.bestImplicit(Implicits.scala:1503)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicit(Implicits.scala:997)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicit$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.inferImplicit(Typer.scala:117)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicitArg(Implicits.scala:851)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicitArg$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.inferImplicitArg(Typer.scala:117)
[error]    |    at dotty.tools.dotc.typer.Typer.implicitArgs$1(Typer.scala:3451)
[error]    |    at dotty.tools.dotc.typer.Typer.addImplicitArgs$1(Typer.scala:3487)
[error]    |    at dotty.tools.dotc.typer.Typer.adaptNoArgsImplicitMethod$1(Typer.scala:3563)
[error]    |    at dotty.tools.dotc.typer.Typer.adaptNoArgs$1(Typer.scala:3761)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt1(Typer.scala:3995)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt(Typer.scala:3329)
[error]    |    at dotty.tools.dotc.typer.Typer.readapt$1(Typer.scala:3340)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt1(Typer.scala:3982)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt(Typer.scala:3329)
[error]    |    at dotty.tools.dotc.typer.Implicits.typedImplicit(Implicits.scala:1053)
[error]    |    at dotty.tools.dotc.typer.Implicits.typedImplicit$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.typedImplicit(Typer.scala:117)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.tryImplicit(Implicits.scala:1173)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.rank$1(Implicits.scala:1272)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.searchImplicit(Implicits.scala:1442)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.searchImplicit(Implicits.scala:1470)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.searchImplicit(Implicits.scala:1478)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.bestImplicit(Implicits.scala:1503)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicit(Implicits.scala:997)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicit$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.inferImplicit(Typer.scala:117)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicitArg(Implicits.scala:851)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicitArg$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.inferImplicitArg(Typer.scala:117)
[error]    |    at dotty.tools.dotc.typer.Typer.implicitArgs$1(Typer.scala:3451)
[error]    |    at dotty.tools.dotc.typer.Typer.addImplicitArgs$1(Typer.scala:3487)
[error]    |    at dotty.tools.dotc.typer.Typer.adaptNoArgsImplicitMethod$1(Typer.scala:3563)
[error]    |    at dotty.tools.dotc.typer.Typer.adaptNoArgs$1(Typer.scala:3761)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt1(Typer.scala:3995)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt(Typer.scala:3329)
[error]    |    at dotty.tools.dotc.typer.Typer.readapt$1(Typer.scala:3340)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt1(Typer.scala:3982)
[error]    |    at dotty.tools.dotc.typer.Typer.adapt(Typer.scala:3329)
[error]    |    at dotty.tools.dotc.typer.Implicits.typedImplicit(Implicits.scala:1053)
[error]    |    at dotty.tools.dotc.typer.Implicits.typedImplicit$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.typedImplicit(Typer.scala:117)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.tryImplicit(Implicits.scala:1173)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.rank$1(Implicits.scala:1272)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.searchImplicit(Implicits.scala:1442)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.searchImplicit(Implicits.scala:1470)
[error]    |    at dotty.tools.dotc.typer.Implicits$ImplicitSearch.bestImplicit(Implicits.scala:1503)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicit(Implicits.scala:997)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicit$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.inferImplicit(Typer.scala:117)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicitArg(Implicits.scala:851)
[error]    |    at dotty.tools.dotc.typer.Implicits.inferImplicitArg$(Implicits.scala:785)
[error]    |    at dotty.tools.dotc.typer.Typer.inferImplicitArg(Typer.scala:117)
[error]    |    at scala.quoted.runtime.impl.QuotesImpl$reflect$Implicits$.search(QuotesImpl.scala:2396)
[error]    |    at scala.quoted.runtime.impl.QuotesImpl$reflect$Implicits$.search(QuotesImpl.scala:2395)
[error]    |    at scala.quoted.Expr$.summon(Expr.scala:232)
[error]    |    at sangria.macros.derive.DeriveObjectTypeMacro.createArg(DeriveObjectTypeMacro.scala:429)
[error]    |    at sangria.macros.derive.DeriveObjectTypeMacro.$anonfun$237$$anonfun$1(DeriveObjectTypeMacro.scala:299)
[error]    |    at scala.collection.immutable.List.map(List.scala:246)
[error]    |    at sangria.macros.derive.DeriveObjectTypeMacro.$anonfun$237(DeriveObjectTypeMacro.scala:299)
[error]    |    at scala.collection.immutable.List.map(List.scala:246)
[error]    |    at sangria.macros.derive.DeriveObjectTypeMacro.fieldWithArguments(DeriveObjectTypeMacro.scala:299)
[error]    |    at sangria.macros.derive.DeriveObjectTypeMacro.$anonfun$228(DeriveObjectTypeMacro.scala:171)
[error]    |    at scala.collection.immutable.List.map(List.scala:246)
[error]    |    at sangria.macros.derive.DeriveObjectTypeMacro.collectFields(DeriveObjectTypeMacro.scala:254)
[error]    |    at sangria.macros.derive.DeriveObjectTypeMacro.deriveObjectType(DeriveObjectTypeMacro.scala:72)
[error]    |    at sangria.macros.derive.DeriveObjectTypeMacro$.deriveNormalObjectType(DeriveObjectTypeMacro.scala:45)
[error]    |
[error] 36 |        ObjectTypeName("UpdatePost")
[error] 37 |      )
[error]    |----------------------------------------------------------------------------
[error]    |Inline stack trace
[error]    |- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
[error]    |This location contains code that was inlined from package.scala:14
[error]     ----------------------------------------------------------------------------
[error] one error found
PsyfireX commented 1 year ago

Additional notes:

The code did compile previously under Scala 2.13

Debugging macros is beyond my current expertise. However, narrowing in closer to the issue, it appears to happen when mixing ObjectType and InputObjectType, where the input is a method-parameter.

When I use an ObjectType inside and ObjectType, or an InputObjectType inside and InputObjectType it seems to compile just fine:

ObjectType inside ObjectType (no errors)

package ai.tagr.graphql.tagr.schema

import sangria.schema.ObjectType
import scala.concurrent.Future
import sangria.schema.InputObjectType

case class Ctx(ctx: String)

object PostSchema {
  import sangria.marshalling.circe._
  import io.circe.generic.auto._
  import sangria.macros.derive._

  case class UpdatePostGql() {
    @GraphQLField
    def text(text: String): Future[TextGql] = ???   // relevant line
  }

  object UpdatePostGql{
    implicit val _ot: ObjectType[Ctx, UpdatePostGql] =
      deriveObjectType[Ctx, UpdatePostGql](
        ObjectTypeName("UpdatePost")
      )
  }

  case class TextGql(text: String)

  object TextGql{
    implicit val _it: ObjectType[Ctx, TextGql] =
      deriveObjectType[Ctx, TextGql](
        ObjectTypeName("Text")
      )
  }
}

InputObjectType inside InputObjectType (no errors)

import sangria.schema.ObjectType
import scala.concurrent.Future
import sangria.schema.InputObjectType

case class Ctx(ctx: String)

object PostSchema {
  import sangria.marshalling.circe._
  import io.circe.generic.auto._
  import sangria.macros.derive._

  case class UpdatePostGql(id: String) {
    @GraphQLField
    def text(text: TextInputGql): Future[String] = ???    // relevant line
  }

  object UpdatePostGql{    // swapped from ObjectType to InputObjectType
    implicit val _it: InputObjectType[UpdatePostGql] =
      deriveInputObjectType[UpdatePostGql](
        InputObjectTypeName("UpdatePost")
      )
  }

  case class TextInputGql(text: String)

  object TextInputGql{
    implicit val _it: InputObjectType[TextInputGql] =
      deriveInputObjectType[TextInputGql](
        InputObjectTypeName("TextInput")
      )
  }
}
PsyfireX commented 1 year ago

update:

This code has the same issue, and uses an id with a ScalarAlias[T, Long] for input, and has the same issue.

import scala.concurrent.Future
import scala.util.Try

case class Ctx(ctx: String)

case class SomeId(id: Long)

object SomeSchema {
  import sangria.marshalling.circe._
  import io.circe.generic.auto._
  import sangria.macros.derive._
  import sangria.schema._

  type LongAlias[T] = ScalarAlias[T, Long]

  def longAlias[ID](from: ID=>Long, to: Long=>ID): LongAlias[ID] =
    ScalarAlias(LongType, from, v ⇒ Right(to(v)))

  implicit val someIdType: LongAlias[SomeId] = longAlias[SomeId](_.id, SomeId.apply)

  case class SomeOT(id: String) {
    @GraphQLField
    def getSome1(id: SomeId): Future[String] = ???
  }

  object SomeOT{
    implicit val _ot: ObjectType[Ctx, SomeOT] =
      deriveObjectType[Ctx, SomeOT](
        ObjectTypeName("SomeOT")
      )
  }
}

However, I noticed that commenting out:

// import io.circe.generic.auto._

Appears to make the compiler error go away in this very tiny and limited code sample.

Now, I can't do that in my production code. Commenting that line out in my prod code causes a bunch of other errors, and it's apparently being used to support FromInput.

yanns commented 1 year ago

I'll have a look at this. Pinging @jchyb who may have an opinion on what is going on.

yanns commented 1 year ago

I can confirm that the first example compiles if I remove import io.circe.generic.auto._ and if I define the FromInput[TextInputGql]:

import sangria.marshalling.FromInput
import sangria.schema.ObjectType

import scala.concurrent.Future
import sangria.schema.InputObjectType

case class Ctx(ctx: String)

object PostSchema {
  import sangria.macros.derive._

  case class UpdatePostGql() {
    @GraphQLField
    def text(text: TextInputGql): Future[String] = ???
  }

  object UpdatePostGql {
    implicit val _ot: ObjectType[Ctx, UpdatePostGql] =
      deriveObjectType[Ctx, UpdatePostGql](
        ObjectTypeName("UpdatePost")
      )
  }

  case class TextInputGql(text: String)

  object TextInputGql {
    implicit val fi: FromInput[TextInputGql] = ???
    implicit val _it: InputObjectType[TextInputGql] =
      deriveInputObjectType[TextInputGql](
        InputObjectTypeName("TextInput")
      )
  }
}

So it seems that issue is coming from a different resolution of implicits.

yanns commented 1 year ago

I'm trying to check with semi-automatic derivation to better control which implicits are created:

import io.circe.Decoder
import sangria.schema.{InputObjectType, ObjectType}

import scala.concurrent.Future

case class Ctx(ctx: String)

object PostSchema {
  import sangria.marshalling.circe._
  import io.circe.generic.semiauto._
  import sangria.macros.derive._

  case class UpdatePostGql() {
    @GraphQLField
    def text(text: TextInputGql): Future[String] = ???
  }

  object UpdatePostGql {
    implicit val _ot: ObjectType[Ctx, UpdatePostGql] =
      deriveObjectType[Ctx, UpdatePostGql](
        ObjectTypeName("UpdatePost")
      )
  }

  case class TextInputGql(
      @GraphQLInputType(sangria.schema.StringType)
      text: String)

  object TextInputGql {
    implicit val decoder: Decoder[TextInputGql] = deriveDecoder[TextInputGql]
    implicit val _it: InputObjectType[TextInputGql] =
      deriveInputObjectType[TextInputGql](
        InputObjectTypeName("TextInput")
      )
  }
}

It leads to the compilation error:

[error] 20 |      deriveObjectType[Ctx, UpdatePostGql](
[error]    |      ^
[error]    |GraphQlOutputType not found: scala.Function1[java.lang.String, sangria.schema.Action[Ctx, java.lang.String]]
[error] 21 |        ObjectTypeName("UpdatePost")
[error] 22 |      )
[error] one error found

I've pushed the changes to https://github.com/sangria-graphql/sangria/tree/issue-905 if someone wants to re-use that.

PsyfireX commented 1 year ago

Thank you for starting the investigation! @yanns

I might not be very useful in fixing the bug itself with code-changes (due to no macro experience), however I also encountered what appears to be the exact same issue in both my top-level Query and Mutation types, using deriveContextObject, producing an almost identical MatchError, and similarly pointing to another InputObjectType

      val objectType: ObjectType[Ctx, Unit] =
        deriveContextObjectType[Ctx, AllQueries, Unit](_ctx => new AllQueries {
          override val ctx: Ctx = _ctx
        })

Since you are already started looking at the issue, I'll see if I can also extract this code into a reproducible, isolated, code-sample.

PsyfireX commented 1 year ago

Here is the second code sample, revolving around deriveContextObject

package ai.tagr.graphql.tagr.schema

import scala.concurrent.Future
import scala.util.Try

case class Ctx(ctx: String)

case class SomeId(id: Long)

object SomeSchema {
  import sangria.marshalling.circe._
  // commenting out the import below eliminates one error around `getSome1`.  However, it causes  `getSome2` breaks, because it needs a `FromInput[SomeInput,InputObjectResult]`
  // import io.circe.generic.auto._ 
  import sangria.macros.derive._
  import sangria.schema._

  type LongAlias[T] = ScalarAlias[T, Long]

  def longAlias[ID](from: ID=>Long, to: Long=>ID): LongAlias[ID] =
    ScalarAlias(LongType, from, v ⇒ Right(to(v)))

  implicit val someIdType: LongAlias[SomeId] = longAlias[SomeId](_.id, SomeId.apply)

  case class SomeInput(text: String)
  object SomeInput{
    implicit val _it: InputObjectType[SomeInput] = deriveInputObjectType[SomeInput](
      InputObjectTypeName("SomeInput")
    )
  }

  trait QueryRoot {
    def ctx: Ctx

    @GraphQLField
    def getSome1(id: SomeId): Future[String] = ???

    @GraphQLField
    def getSome2(in: SomeInput): Future[String] = ???
  }

  object QueryRoot {
    implicit val _ot: ObjectType[Ctx, Unit] = 
      deriveContextObjectType[Ctx,QueryRoot,Unit](
        _ctx => new QueryRoot{  override def ctx: Ctx = _ctx}
      )
  }
}
jchyb commented 1 year ago

I am taking a look at this right now, and I feel I am close to figuring this out. Slightly delaying implicit resolution in macro seems to make errors consistent between code with import io.circe.generic.auto._ and without it in the first snippet (no idea why yet). It still errors, but at least without the ugly dotc stack trace, just errors with failed implicit resolution. Those suggest that I might need to check what is going on in the sangria.marshalling.circe module. I'll try to prepare a PR with the fixes after figuring out the rest.

PsyfireX commented 1 year ago

@jchyb Thank you! I believe this is the very last piece in order for me to finish converting my entire project over to Scala 3.