Skip to content

quotes.reflect.memberType can return ClassInfo #15159

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
bishabosha opened this issue May 10, 2022 Discussed in #15157 · 12 comments
Open

quotes.reflect.memberType can return ClassInfo #15159

bishabosha opened this issue May 10, 2022 Discussed in #15157 · 12 comments
Assignees
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug

Comments

@bishabosha
Copy link
Member

bishabosha commented May 10, 2022

Discussed in #15157

in the example below, memberType returns a ClassInfo, which is not exposed in the quotes api, and seems unexpected when pattern matching on a type. It seems that memberType should escape this?

Originally posted by adamw May 10, 2022
I'm trying to summon a typeclass instance for each child class of a sealed trait. So far I've got the following code (here simplified):

import scala.quoted.*
object TestMacro:
  inline def test[T]: Unit = ${ testImpl[T] }
  def testImpl[T: Type](using Quotes): Expr[Unit] =
    import quotes.reflect.*
    val tpe = TypeRepr.of[T]
    tpe.typeSymbol.children.map { childSymbol =>
      val childTpe = tpe.memberType(childSymbol)
      println("Child TypeRepr: " + childTpe)
      childTpe.asType match
        case '[c] =>
          println("Got Type!")
    }
    '{ () }

The childTpe: TypeRepr is correct, however I get an exception when converting it to a Type and trying to get a handle to the type parameter c (so that later I can do Expr.summon[MyTypeClass[c]]). Using the following invocation:

sealed trait A
case class X(i: Int) extends A

object Test extends App {
  TestMacro.test[A]
}

The output is:

Child type: ClassInfo(ThisType(TypeRef(NoPrefix,module class newschema)), class X, List(TypeRef(ThisType(TypeRef(NoPrefix,module class lang)),class Object), TypeRef(ThisType(TypeRef(NoPrefix,module class newschema)),trait A), TypeRef(TermRef(TermRef(NoPrefix,object _root_),object scala),trait Product), TypeRef(ThisType(TypeRef(NoPrefix,module class io)),trait Serializable)))
[info] assertion failure for class X in package sttp.tapir.newschema <:< c, frozen = false
[error] -- Error: Test.scala:33:16
[error] 33 |  TestMacro.test[A]
[error]    |  ^^^^^^^^^^^^^^^^^
[error]    |Exception occurred while executing macro expansion.
[error]    |java.lang.AssertionError: assertion failed: ClassInfo(ThisType(TypeRef(NoPrefix,module class newschema)), class X, List(TypeRef(ThisType(TypeRef(NoPrefix,module class lang)),class Object), TypeRef(ThisType(TypeRef(NoPrefix,module class newschema)),trait A), TypeRef(TermRef(TermRef(NoPrefix,object _root_),object scala),trait Product), TypeRef(ThisType(TypeRef(NoPrefix,module class io)),trait Serializable)))
[error]    |	at scala.runtime.Scala3RunTime$.assertFailed(Scala3RunTime.scala:8)
[error]    |	at dotty.tools.dotc.core.Types$TypeBounds.<init>(Types.scala:5038)
[error]    |	at dotty.tools.dotc.core.Types$RealTypeBounds.<init>(Types.scala:5114)
[error]    |	at dotty.tools.dotc.core.Types$TypeBounds$.apply(Types.scala:5158)
[error]    |	at dotty.tools.dotc.core.Types$TypeBounds.derivedTypeBounds(Types.scala:5046)
[error]    |	at dotty.tools.dotc.core.ConstraintHandling.addOneBound(ConstraintHandling.scala:262)
[error]    |	at dotty.tools.dotc.core.ConstraintHandling.addOneBound$(ConstraintHandling.scala:29)
[error]    |	at dotty.tools.dotc.core.ProperGadtConstraint.addOneBound(GadtConstraint.scala:61)
[error]    |	at dotty.tools.dotc.core.ConstraintHandling.addBoundTransitively(ConstraintHandling.scala:316)
[error]    |	at dotty.tools.dotc.core.ConstraintHandling.addBoundTransitively$(ConstraintHandling.scala:29)
[error]    |	at dotty.tools.dotc.core.ProperGadtConstraint.addBoundTransitively(GadtConstraint.scala:61)
[error]    |	at dotty.tools.dotc.core.ProperGadtConstraint.addBound(GadtConstraint.scala:168)
[error]    |	at dotty.tools.dotc.core.TypeComparer.gadtAddLowerBound(TypeComparer.scala:116)
[error]    |	at dotty.tools.dotc.core.TypeComparer.narrowGADTBounds(TypeComparer.scala:1893)
[error]    |	at dotty.tools.dotc.core.TypeComparer.compareGADT$1(TypeComparer.scala:509)
[error]    |	at dotty.tools.dotc.core.TypeComparer.thirdTryNamed$1(TypeComparer.scala:512)
[error]    |	at dotty.tools.dotc.core.TypeComparer.thirdTry$1(TypeComparer.scala:561)
[error]    |	at dotty.tools.dotc.core.TypeComparer.secondTry$1(TypeComparer.scala:492)
[error]    |	at dotty.tools.dotc.core.TypeComparer.compareNamed$1(TypeComparer.scala:301)
[error]    |	at dotty.tools.dotc.core.TypeComparer.firstTry$1(TypeComparer.scala:307)
[error]    |	at dotty.tools.dotc.core.TypeComparer.recur(TypeComparer.scala:1309)
[error]    |	at dotty.tools.dotc.core.TypeComparer.isSubType(TypeComparer.scala:189)
[error]    |	at dotty.tools.dotc.core.TypeComparer.isSubType(TypeComparer.scala:199)
[error]    |	at dotty.tools.dotc.core.TypeComparer.topLevelSubType(TypeComparer.scala:126)
[error]    |	at dotty.tools.dotc.core.TypeComparer$.topLevelSubType(TypeComparer.scala:2709)
[error]    |	at dotty.tools.dotc.core.Types$Type.$less$colon$less(Types.scala:1040)
[error]    |	at scala.quoted.runtime.impl.QuoteMatcher$.$eq$qmark$eq(QuoteMatcher.scala:336)
[error]    |	at scala.quoted.runtime.impl.QuoteMatcher$.treeMatch(QuoteMatcher.scala:129)
[error]    |	at scala.quoted.runtime.impl.QuotesImpl.scala$quoted$runtime$impl$QuotesImpl$$treeMatch(QuotesImpl.scala:3051)
[error]    |	at scala.quoted.runtime.impl.QuotesImpl$TypeMatch$.unapply(QuotesImpl.scala:3021)

Is there a better (and working :) ) way to achieve the above goal?

@bishabosha bishabosha added the area:metaprogramming:reflection Issues related to the quotes reflection API label May 10, 2022
@nicolasstucki
Copy link
Contributor

The macro is trying to select the member X of type A, which is non-sensical. We should add an assertion to check that we only select members that exist on that type.

@bishabosha
Copy link
Member Author

@nicolasstucki I tried using the childSymbol.owner.typeRef before selecting memberType and I still get the ClassInfo:

val childTpe = childSymbol.owner.typeRef.memberType(childSymbol)

@adamw
Copy link
Contributor

adamw commented May 14, 2022

@nicolasstucki does this mean that .memberType shouldn't be used when trying to get the TypeRepr of a symbol obtained via Symbol.children? If it shouldn't, what's the correct way to do this?

My use-case is trying to get the types of implementations of a sealed trait in order to summon typeclass instances for them.

@sirthias
Copy link
Contributor

I'd like to second @adamw's question above.
Whats the correct way to turn the List[Symbol] produced by Symbol.children for a sum type back into TypeRepr instances for further processing?

@sirthias
Copy link
Contributor

This is how @plokhotnyuk is doing it in jsoniter-scala:
https://github.com/plokhotnyuk/jsoniter-scala/blob/a9653be18c5ee77c95c763c896316c21d63323ef/jsoniter-scala-macros/shared/src/main/scala-3/com/github/plokhotnyuk/jsoniter_scala/macros/JsonCodecMaker.scala#L600-L662
which looks insanely complicated.

I refuse to believe that to be the "intended" way.
(Still, hats off to Andriy for finding a way that appears to work with the current compiler!)

@sirthias
Copy link
Contributor

Damn. Just now saw #15157, which already contains a lot of the relevant discussion. Apologies for the duplicated content here... 🫤

@adamw
Copy link
Contributor

adamw commented Jun 17, 2022

@sirthias Thanks! :) Wow that's indeed complex. How did @plokhotnyuk arrive at that code? ;) Anyway, I think this means that getting TypeReprs of children classes isn't something that's supported by the current macros.

I'll try to use the above (with attribution, of course) and we'll see how this works for my use case

@plokhotnyuk
Copy link

@sirthias @adamw
All credits to @rssh for his amazing work of porting macros of jsoniter-scala to Scala 3 in a source compatible way!

@sirthias
Copy link
Contributor

sirthias commented Jun 20, 2022

@adamw Yes, the linked snippet from jsoniter-scala works fine for me in my current rewrite of the borer macros to Scala 3. Like you I'm simply using a verbatim copy of the snippet with proper attribution.
But the need for such a TypeRepr#children:List[TypeRepr] functionality appears to be evident from all of us gathering here.
Internally it must be available somewhere since the compiler nicely generates the Mirror.Sum expressions for a type if asked to do so. I tried to get somewhere by summoning such a mirror expression for my super type and then matching my way through what the compiler generated but had to give up due to lack of time.
AFAICS this should also work and maybe be even simpler than the jsoniter snippet but a working incantation for this approach is still "out there" somewhere. But maybe we would still be left with only Symbols with that approach?

@bishabosha
Copy link
Member Author

bishabosha commented Jun 20, 2022

@adamw Yes, the linked snippet from jsoniter-scala works fine for me in my current rewrite of the borer macros to Scala 3. Like you I'm simply using a verbatim copy of the snippet with proper attribution.
But the need for such a TypeRepr#children:List[TypeRepr] functionality appears to be evident from all of us gathering here.
Internally it must be available somewhere since the compiler nicely generates the Mirror.Sum expressions for a type if asked to do so. I tried to get somewhere by summoning such a mirror expression for my super type and then matching my way through what the compiler generated but had to give up due to lack of time.

I am still working on trying to improve the situation for inner classes (it is non-trivial to construct types with the correct prefix), but for static classes it is comparatively simple - that would be very interesting to extract the logic from Mirror synthesis and expose it as an API

You also mentioned that you were not successful extracting the MirroredElemTypes from a Mirror with a macro, did you try this guide? - https://docs.scala-lang.org/scala3/reference/contextual/derivation-macro.html

@nicolasstucki
Copy link
Contributor

Seems that that page is not up to date with the fixes in https://github.com/lampepfl/dotty/blob/main/docs/_docs/reference/contextual/derivation-macro.md

@sirthias
Copy link
Contributor

Thank you, @nicolasstucki, for the pointer to that page!
With the help of that example I was able to replace the complicated @jsoniter snippet with a much cleaner and simpler implementation, that does exactly what I need:

def adtChildren(tpe: TypeRepr): List[TypeRepr] =
  tpe.asType match
    case '[t] =>
      Expr.summon[Mirror.Of[t]].toList.flatMap {
        case '{ $m: Mirror.SumOf[t] { type MirroredElemTypes = subs } } => typeReprsOf[subs]
        case x                                                          => Nil
      }

def typeReprsOf[Ts: Type]: List[TypeRepr] =
  Type.of[Ts] match
    case '[EmptyTuple] => Nil
    case '[t *: ts]    => TypeRepr.of[t] :: typeReprsOf[ts]

No need for a call to tpe.typeSymbol.children anymore. 👍

nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Nov 16, 2022
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Mar 10, 2023
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Jul 6, 2023
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Feb 7, 2024
nicolasstucki added a commit to dotty-staging/dotty that referenced this issue Apr 5, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:reflection Issues related to the quotes reflection API itype:bug
Projects
None yet
5 participants