-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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
Macro annotations class modifications (part 2) #16454
Macro annotations class modifications (part 2) #16454
Conversation
1dc36c6
to
51c7a41
Compare
bf08d00
to
1599314
Compare
#### Add basic support for macro annotations * Introduce experimental `scala.annotations.MacroAnnotation` * Macro annotations can analyze or modify definitions * Macro annotation can add definition around the annotated definition * Added members are not visible while typing * Added members are not visible to other macro annotations * Added definition must have the same owner * Implement macro annotation expansion * Implemented at `Inlining` phase * Can use macro annotations in staged expressions (expanded when at stage 0) * Can use staged expression to implement macro annotations * Can insert calls to inline methods in macro annotations * Current limitations (to be loosened in following PRs) * Can only be used on `def`, `val`, `lazy val` and `var` * Can only add `def`, `val`, `lazy val` and `var` definitions #### Example ```scala class memoize extends MacroAnnotation: def transform(using Quotes)(tree: quotes.reflect.Definition): List[quotes.reflect.Definition] = import quotes.reflect._ tree match case DefDef(name, TermParamClause(param :: Nil) :: Nil, tpt, Some(rhsTree)) => (Ref(param.symbol).asExpr, rhsTree.asExpr) match case ('{ $paramRefExpr: t }, '{ $rhsExpr: u }) => val cacheTpe = TypeRepr.of[Map[t, u]] val cacheSymbol = Symbol.newVal(tree.symbol.owner, name + "Cache", cacheTpe, Flags.Private, Symbol.noSymbol) val cacheRhs = '{ Map.empty[t, u] }.asTerm val cacheVal = ValDef(cacheSymbol, Some(cacheRhs)) val cacheRefExpr = Ref(cacheSymbol).asExprOf[Map[t, u]] val newRhs = '{ $cacheRefExpr.getOrElseUpdate($paramRefExpr, $rhsExpr) }.asTerm val newTree = DefDef.copy(tree)(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(tree) ``` with this macro annotation a user can write ```scala @memoize def 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 ```scala val fibCache = mutable.Map.empty[Int, Int] def fib(n: Int): Int = fibCache.getOrElseUpdate( n, { println(s"compute fib of $n") if n <= 1 then n else fib(n - 1) + fib(n - 2) } ) ``` #### Based on * #15626 * https://infoscience.epfl.ch/record/294615?ln=en #### Followed by * #16454
e4d781a
to
86c6d54
Compare
Enable the addition of classes from a `MacroAnnotation`: * Can add new `class` definitions next to the annotated definition Special cases: * A top-level `def`, `val`, `var`, `lazy val` can return a `class` definition that is owned by the package or package object. Related PRs: * Follows scala#16454
Enable the addition of classes from a `MacroAnnotation`: * Can add new `class` definitions next to the annotated definition Special cases: * An annotated top-level `def`, `val`, `var`, `lazy val` can return a `class` definition that is owned by the package or package object. Related PRs: * Follows scala#16454
* Add `Symbol.freshName` * Remove `Symbol.newUniqueMethod` * Remove `Symbol.newUniqueVal` This API is necessary to be able to create new class members without having name clashes. It should also be usable to create new top level definitions that have names that do not clash. This version of unique name can be used for `val`, `def` and `class` symbols.
Enable modification of classes with `MacroAnnotation`: * Can annotate `class` to transform it * Can annotate `object` to transform the companion class Supported class modifications: * Modify the implementations of `def`, `val`, `var`, `lazy val`, `class`, `object` in the class * Add new `def`, `val`, `var`, `lazy val`, `class`, `object` members to the class * Add a new override for a `def`, `val`, `var`, `lazy val` members in the class Restrictions: * An annotation on a top-level class cannot return a top-level `def`, `val`, `var`, `lazy val`
88c6f14
to
52c9461
Compare
Enable the addition of classes from a `MacroAnnotation`: * Can add new `class` definitions next to the annotated definition Special cases: * An annotated top-level `def`, `val`, `var`, `lazy val` can return a `class` definition that is owned by the package or package object. Related PRs: * Follows scala#16454
Enable the addition of classes from a `MacroAnnotation`: * Can add new `class`/`object` definitions next to the annotated definition Special cases: * An annotated top-level `def`, `val`, `var`, `lazy val` can return a `class`/`object` definition that is owned by the package or package object. Related PRs: * Follows #16454
Enable the addition of classes from a `MacroAnnotation`: * Can add new `class` definitions next to the annotated definition Special cases: * An annotated top-level `def`, `val`, `var`, `lazy val` can return a `class` definition that is owned by the package or package object. Related PRs: * Follows scala#16454
Is it possible to transform companion object while annotate on class ? |
Would love to know this too. In Scala 2 macro annotations, the |
It is not possible with this version. What is the use case? |
A use case for me would be a macro annotation acting as a more refined @IsMapEntry
class Foo {
@Key
def id: String = "a"
@Value
def value: Int = 2
} where given AsMapEntry[Foo, String, Int] = AsMapEntry.derive // String and Int are the @Key and @Value annotated method types |
In this version of macro annotations, you cannot generate definitions that will be visible to users. The generated Why not use |
Because |
In more complex cases, I wish I would have been able to use parameters of the annotation in the additional definitions ( |
Another case is the zio accessor generation. |
Similar to the accessor macro above, generating lenses on the companion object would also be an example use case. @lenses
case class Person(name: String, age: Int)
// Would expand into
case class Person(name: String, age: Int)
object Person:
val name: Lens[Person, String] = Lens.make(_.name)
val age: List[Person, Int] = Lens.make(_.age) It seems like a trivial thing, but it'd be nice to avoid the boilerplate 😄. I wonder if there's a solution to this that doesn't require full power Scala 2-style pre-type-checked annotations. |
My best attempt: https://contributors.scala-lang.org/t/scala-3-macro-annotations-and-code-generation/6035 |
That seems like a great idea @smarter! I'll give it a try :) |
Cool! In case you haven't seen it, I have a prototype up at #16545 |
I was actually just looking at that! It would appear that it's not yet using the |
No, it's just a matter of doing the work, so you're more than welcome to take a stab at it :). This ties in with work started by @ckipp01 on structured diagnostics since we want to expose that information to IDEs, but that part should be orthogonal to the API we use in macros hopefully. |
Enable modification of classes with
MacroAnnotation
:class
to transform itobject
to transform the companion classSupported class modifications:
def
,val
,var
,lazy val
,class
,object
in the classdef
,val
,var
,lazy val
,class
,object
members to the classdef
,val
,var
,lazy val
members in the classRestrictions:
def
,val
,var
,lazy val
.Related PRs:
spliceOwner
#16513Fixes #16266