Skip to content
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

Wrong type representation when using typeMember to select a type inherited from a base class #19825

Open
OndrejSpanel opened this issue Feb 29, 2024 · 1 comment
Assignees
Labels
area:metaprogramming:quotes Issues related to quotes and splices itype:bug

Comments

@OndrejSpanel
Copy link
Member

When using typeMember function of TypeRepr.of[OuterType].typeSymbol, the resulting symbol is represented as Base.this.InnerType, not OuterType.InnerType. This is a problem when creating a macro which is expected to return an expression of OuterType.InnerType type.

This was found while working on Surface representation of types derived from Scala 2 Enumeration, see wvlet/airframe#3429.

Compiler version

3.3.2, 3.4.0, 3.4.1-RC1

Minimized code

trait Base {
  class InnerType
}

object OuterType extends Base {
  def create: InnerType = new InnerType
}

@main
def main(): Unit = {
  val typeDesc = TypeUtil.memberTypeName[OuterType.type, OuterType.InnerType]
  println(s"Type $typeDesc")
}
import scala.quoted.*

object TypeUtil {
  inline def memberTypeName[A, B] = ${ memberTypeNameImpl[A, B]}

  private def memberTypeNameImpl[A, B](using typeA: Type[A], typeB: Type[B], quotes: Quotes): Expr[String] = {
    import quotes.reflect.*
    val repr = TypeRepr.of(using typeA)
    val memberName = TypeRepr.of(using typeB).typeSymbol.name
    val result = repr.show + "." + memberName +": " + repr.typeSymbol.typeMember(memberName).typeRef.show
    Expr(result)
  }
}

Output

Type OuterType.InnerType: Base.this.InnerType

Expectation

The output should be OuterType.InnerType: OuterType.InnerType.

@jchyb
Copy link
Contributor

jchyb commented Nov 28, 2024

Apologies for the very late reply, but this is actually correct behavior. in the quotes reflect API, Symbols do not store type prefix information (whereas TypeRepr do have those). They are moreso a representation of a part of the code that defines them and their attributes, so for TypeRepr.of[OuterType].typeMember("InnerType") we get a symbol pointing to Base.InnerType:

trait Base {
  class InnerType // <- with a Symbol we reference exactly that, no more, no less
}

We lose that type information about the type prefix there, so when calling typeRef on that symbol, we are not able to regain it.
This is why the scaladoc for typeRef explains:

Type reference to the symbol usable in the scope of its owner
To get a reference to a symbol from a specific prefix `tp`, use `tp.select(symbol)` instead.
*  @see TypeReprMethods.select

("usable in the scope of an owner" means that if we were calling that macro from inside of OuterType, the compiler would know what Base.this.InnerType references).

Regardless, the rewritten macro with TypeRepr.select would look like this:

object TypeUtil {
  inline def memberTypeName[A, B] = ${ memberTypeNameImpl[A, B]}

  private def memberTypeNameImpl[A, B](using typeA: Type[A], typeB: Type[B], quotes: Quotes): Expr[String] = {
    import quotes.reflect.*
    val repr = TypeRepr.of(using typeA)
    val memberName = TypeRepr.of(using typeB).typeSymbol.name
    val result = repr.show + "." + memberName +": " + repr.select(repr.typeSymbol.typeMember(memberName)).show
    Expr(result)
  }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area:metaprogramming:quotes Issues related to quotes and splices itype:bug
Projects
None yet
Development

No branches or pull requests

3 participants