Search code examples
scalascala-macrosscala-3

How to match a TypeDef in a Scala 3 macro annotation correctly?


I created an example macro annotation to test if I can properly match a case class TypeDef to later modify it, but it does not match, though all seems fine and compiles, heres how it looks:

import scala.annotation.{MacroAnnotation, experimental}
import scala.quoted.*


object Macros:
  @experimental
  class table extends MacroAnnotation:
    def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
      import quotes.reflect.*
      tree match
        case td @ TypeDef(_, _) =>
          report.error(s"from macro annotation1 ${td.symbol}")
          List(tree)
        case _ =>
          report.error(s"from macro annotation2 $tree")
          List(tree)

Example case class where I applied it:

@experimental @table
case class Person(firstName: String, lastName: String)

It always runs the second message meaning I wasn't able to match the TypeDef.

What am I missing? Any further imports needed?

Also tried tree.isInstanceOf[TypeDef]} and this returns true as expected, making it even more complicated to understand why the match did not work.


Solution

  • If you check tree structure with Printer.TreeStructure.show(tree) you will see that Person is ClassDef. Then following code will match on first arm

    @experimental class table extends MacroAnnotation:
      override def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] =
        import quotes.reflect.*
    
        tree match
          case td @ ClassDef(className, _, _, _, _)=>
            report.error(s"from macro annotation1 ${className}")
            List(tree)
          case _ =>
            report.error(s"from macro annotation2 $tree")
            List(tree)
    
    

    A TypeDef will be found if you add a type definition inside yours Person class.

    Example

    @experimental @table
    case class Hello(name: String, lastName: String) {
      type Item
    }
    

    TreeStructure.show output

    ClassDef("Hello", DefDef("<init>", List(TermParamClause(List(ValDef("name", TypeIdent("String"), None), ValDef("lastName", TypeIdent("String"), None)))), Inferred(), None), List(Apply(Select(New(Inferred()), "<init>"), Nil), TypeSelect(Select(Ident("_root_"), "scala"), "Product"), TypeSelect(Select(Ident("_root_"), "scala"), "Serializable")), None, List(DefDef("hashCode", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_hashCode"), List(This(Some("Hello")))))), DefDef("equals", List(TermParamClause(List(ValDef("x$0", Inferred(), None)))), Inferred(), Some(Apply(Select(Apply(Select(This(Some("Hello")), "eq"), List(TypeApply(Select(Ident("x$0"), "$asInstanceOf$"), List(Inferred())))), "||"), List(Match(Ident("x$0"), List(CaseDef(Bind("x$0", Typed(Wildcard(), Inferred())), None, Apply(Select(Apply(Select(Apply(Select(Select(This(Some("Hello")), "name"), "=="), List(Select(Ident("x$0"), "name"))), "&&"), List(Apply(Select(Select(This(Some("Hello")), "lastName"), "=="), List(Select(Ident("x$0"), "lastName"))))), "&&"), List(Apply(Select(Ident("x$0"), "canEqual"), List(This(Some("Hello"))))))), CaseDef(Wildcard(), None, Literal(BooleanConstant(false))))))))), DefDef("toString", List(TermParamClause(Nil)), Inferred(), Some(Apply(Ident("_toString"), List(This(Some("Hello")))))), DefDef("canEqual", List(TermParamClause(List(ValDef("that", Inferred(), None)))), Inferred(), Some(TypeApply(Select(Ident("that"), "isInstanceOf"), List(Inferred())))), DefDef("productArity", Nil, Inferred(), Some(Literal(IntConstant(2)))), DefDef("productPrefix", Nil, Inferred(), Some(Literal(StringConstant("Hello")))), DefDef("productElement", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Literal(IntConstant(0)), None, Select(This(Some("Hello")), "_1")), CaseDef(Literal(IntConstant(1)), None, Select(This(Some("Hello")), "_2")), CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), "<init>"), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), DefDef("productElementName", List(TermParamClause(List(ValDef("n", Inferred(), None)))), Inferred(), Some(Match(Ident("n"), List(CaseDef(Literal(IntConstant(0)), None, Literal(StringConstant("name"))), CaseDef(Literal(IntConstant(1)), None, Literal(StringConstant("lastName"))), CaseDef(Wildcard(), None, Apply(Ident("throw"), List(Apply(Select(New(Inferred()), "<init>"), List(Apply(Select(Ident("n"), "toString"), Nil)))))))))), ValDef("name", Inferred(), None), ValDef("lastName", Inferred(), None), TypeDef("Item", TypeBoundsTree(Inferred(), Inferred())), DefDef("copy", List(TermParamClause(List(ValDef("name", Inferred(), None), ValDef("lastName", Inferred(), None)))), Inferred(), Some(Apply(Select(New(Inferred()), "<init>"), List(Ident("name"), Ident("lastName"))))), DefDef("copy$default$1", Nil, Inferred(), Some(Ident("name"))), DefDef("copy$default$2", Nil, Inferred(), Some(Ident("lastName"))), DefDef("_1", Nil, Inferred(), Some(Select(This(None), "name"))), DefDef("_2", Nil, Inferred(), Some(Select(This(None), "lastName")))))}