Movatterモバイル変換


[0]ホーム

URL:


Scala 3
3.7.4
LearnInstallPlaygroundFind A LibraryCommunityBlog
Scala 3
LearnInstallPlaygroundFind A LibraryCommunityBlog
DocsAPI
Generated with
Copyright (c) 2002-2025, LAMP/EPFL
Copyright (c) 2002-2025, LAMP/EPFL
Scala 3/scala/scala.annotation/MacroAnnotation

MacroAnnotation

scala.annotation.MacroAnnotation

Base trait for macro annotation implementation. Macro annotations can transform definitions and add new definitions.

See:MacroAnnotation.transform

Attributes

Experimental
true
Source
MacroAnnotation.scala
Graph
Supertypes
classObject
traitMatchable
classAny

Members list

Value members

Abstract methods

deftransform(usingQuotes)(definition: x$1.reflect.Definition,companion:Option[x$1.reflect.Definition]):List[x$1.reflect.Definition]

Transform thetree definition and add new definitions

Transform thetree definition and add new definitions

This method takes as argument the annotated definition. It returns a non-empty list containing the modified version of the annotated definition. The new tree for the definition must use the original symbol. New definitions can be added to the list before or after the transformed definitions, this order will be retained. New definitions will not be visible from outside the macro expansion.

Restrictions

  • All definitions in the result must have the same owner. The owner can be recovered fromSymbol.spliceOwner.
    • Special case: an annotated top-leveldef,val,var,lazy val can return aclass/object definition that is owned by the package or package object.
  • Can not return atype.
  • Annotated top-levelclass/object can not return top-leveldef,val,var,lazy val.
  • Can not see new definition in user written code.

Good practices

  • Make your new definitions private if you can.
  • New definitions added as class members should use a fresh name (Symbol.freshName) to avoid collisions.
  • New top-level definitions should use a fresh name (Symbol.freshName) that includes the name of the annotated member as a prefix to avoid collisions of definitions added in other files.

IMPORTANT: When developing and testing a macro annotation, you must enable-Xcheck-macros and-Ycheck:all.

Example 1

This example shows how to modify adef and add aval next to it using a macro annotation.

import scala.quoted.*import scala.collection.concurrentclass memoize extends MacroAnnotation:  def transform(using Quotes)(    definition: quotes.reflect.Definition,    companion: Option[quotes.reflect.Definition]  ): List[quotes.reflect.Definition] =    import quotes.reflect.*    definition match      case DefDef(name, TermParamClause(param  :: Nil) :: Nil, tpt, Some(rhsTree)) =>        (param.tpt.tpe.asType, tpt.tpe.asType) match          case ('[t], '[u]) =>            val cacheName = Symbol.freshName(name + "Cache")            val cacheSymbol = Symbol.newVal(Symbol.spliceOwner, cacheName, TypeRepr.of[concurrent.Map[t, u]], Flags.Private, Symbol.noSymbol)            val cacheRhs =              given Quotes = cacheSymbol.asQuotes              '{ concurrent.TrieMap.empty[t, u] }.asTerm            val cacheVal = ValDef(cacheSymbol, Some(cacheRhs))            val newRhs =              given Quotes = definition.symbol.asQuotes              val cacheRefExpr = Ref(cacheSymbol).asExprOf[concurrent.Map[t, u]]              val paramRefExpr = Ref(param.symbol).asExprOf[t]              val rhsExpr = rhsTree.asExprOf[u]              '{ $cacheRefExpr.getOrElseUpdate($paramRefExpr, $rhsExpr) }.asTerm            val newTree = DefDef.copy(definition)(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(newRhs))            List(cacheVal, newTree)      case _ =>        report.error("Annotation only supported on `def` with a single argument are supported")        List(definition)  end transform

with this macro annotation a user can write

class memoize extends scala.annotation.StaticAnnotation@memoizedef fib(n: Int): Int =  println(s"compute fib of $n")  if n <= 1 then n else fib(n - 1) + fib(n - 2)

and the macro will modify the definition to create

val fibCache$macro$1 =  scala.collection.concurrent.TrieMap.empty[Int, Int]def fib(n: Int): Int =  fibCache$macro$1.getOrElseUpdate(    n,    {      println(s"compute fib of $n")      if n <= 1 then n else fib(n - 1) + fib(n - 2)    }  )

Example 2

This example shows how to modify aclass using a macro annotation. It shows how to override inherited members and add new ones.

import scala.annotation.{experimental, MacroAnnotation}import scala.quoted.*@experimentalclass equals extends MacroAnnotation:  def transform(using Quotes)(    definition: quotes.reflect.Definition,    companion: Option[quotes.reflect.Definition]  ): List[quotes.reflect.Definition] =    import quotes.reflect.*    definition match      case ClassDef(className, ctr, parents, self, body) =>        val cls = definition.symbol        val constructorParameters = ctr.paramss.collect { case clause: TermParamClause => clause }        if constructorParameters.size != 1 || constructorParameters.head.params.isEmpty then          report.errorAndAbort("@equals class must have a single argument list with at least one argument", ctr.pos)        def checkNotOverridden(sym: Symbol): Unit =          if sym.overridingSymbol(cls).exists then            report.error(s"Cannot override ${sym.name} in a @equals class")        val fields = body.collect {          case vdef: ValDef if vdef.symbol.flags.is(Flags.ParamAccessor) =>            Select(This(cls), vdef.symbol).asExpr        }        val equalsSym = Symbol.requiredMethod("java.lang.Object.equals")        checkNotOverridden(equalsSym)        val equalsOverrideSym = Symbol.newMethod(cls, "equals", equalsSym.info, Flags.Override, Symbol.noSymbol)        def equalsOverrideDefBody(argss: List[List[Tree]]): Option[Term] =          given Quotes = equalsOverrideSym.asQuotes          cls.typeRef.asType match            case '[c] =>              Some(equalsExpr[c](argss.head.head.asExpr, fields).asTerm)        val equalsOverrideDef = DefDef(equalsOverrideSym, equalsOverrideDefBody)        val hashSym = Symbol.newVal(cls, Symbol.freshName("hash"), TypeRepr.of[Int], Flags.Private | Flags.Lazy, Symbol.noSymbol)        val hashVal = ValDef(hashSym, Some(hashCodeExpr(className, fields)(using hashSym.asQuotes).asTerm))        val hashCodeSym = Symbol.requiredMethod("java.lang.Object.hashCode")        checkNotOverridden(hashCodeSym)        val hashCodeOverrideSym = Symbol.newMethod(cls, "hashCode", hashCodeSym.info, Flags.Override, Symbol.noSymbol)        val hashCodeOverrideDef = DefDef(hashCodeOverrideSym, _ => Some(Ref(hashSym)))        val newBody = equalsOverrideDef :: hashVal :: hashCodeOverrideDef :: body        List(ClassDef.copy(definition)(className, ctr, parents, self, newBody))      case _ =>        report.error("Annotation only supports `class`")        List(definition)  end transform  private def equalsExpr[T: Type](that: Expr[Any], thisFields: List[Expr[Any]])(using Quotes): Expr[Boolean] =    '{      $that match        case that: T @unchecked =>          ${            val thatFields: List[Expr[Any]] =              import quotes.reflect.*              thisFields.map(field => Select('{that}.asTerm, field.asTerm.symbol).asExpr)            thisFields.zip(thatFields)              .map { case (thisField, thatField) => '{ $thisField == $thatField } }              .reduce { case (pred1, pred2) => '{ $pred1 && $pred2 } }          }        case _ => false    }  private def hashCodeExpr(className: String, thisFields: List[Expr[Any]])(using Quotes): Expr[Int] =    '{      var acc: Int = ${ Expr(scala.runtime.Statics.mix(-889275714, className.hashCode)) }      ${        Expr.block(          thisFields.map {            case '{ $field: Boolean } => '{ if $field then 1231 else 1237 }            case '{ $field: Byte } => '{ $field.toInt }            case '{ $field: Char } => '{ $field.toInt }            case '{ $field: Short } => '{ $field.toInt }            case '{ $field: Int } => field            case '{ $field: Long } => '{ scala.runtime.Statics.longHash($field) }            case '{ $field: Double } => '{ scala.runtime.Statics.doubleHash($field) }            case '{ $field: Float } => '{ scala.runtime.Statics.floatHash($field) }            case '{ $field: Null } => '{ 0 }            case '{ $field: Unit } => '{ 0 }            case field => '{ scala.runtime.Statics.anyHash($field) }          }.map(hash => '{ acc = scala.runtime.Statics.mix(acc, $hash) }),          '{ scala.runtime.Statics.finalizeHash(acc, ${Expr(thisFields.size)}) }        )      }    }

with this macro annotation a user can write

class equals extends scala.annotation.StaticAnnotation@equals class User(val name: String, val id: Int)

and the macro will modify the class definition to generate the following code

class User(val name: String, val id: Int):  override def equals(that: Any): Boolean =    that match      case that: User => this.name == that.name && this.id == that.id      case _ => false  private lazy val hash$macro$1: Int =    var acc = 515782504 // scala.runtime.Statics.mix(-889275714, "User".hashCode)    acc = scala.runtime.Statics.mix(acc, scala.runtime.Statics.anyHash(name))    acc = scala.runtime.Statics.mix(acc, id)    scala.runtime.Statics.finalizeHash(acc, 2)  override def hashCode(): Int = hash$macro$1

Value parameters

Quotes

Implicit instance of Quotes used for tree reflection

companion

Tree for the companion class or module if the definition is respectively a module or a class

definition

Tree that will be transformed

Attributes

Source
MacroAnnotation.scala
In this article
Generated with
Copyright (c) 2002-2025, LAMP/EPFL
Copyright (c) 2002-2025, LAMP/EPFL

[8]ページ先頭

©2009-2025 Movatter.jp