|
| 1 | +# Annotation processing |
| 2 | + |
| 3 | +Various macro engines used in AVSystem Commons library inspect annotations on classes, methods, |
| 4 | +parameters and types. For example, [`GenCodec`](GenCodec.md) materialization inspects annotation |
| 5 | +that may adjust how the codec is made. |
| 6 | + |
| 7 | +Because of heavy use of annotations, some common rules, conventions and utilities have been created |
| 8 | +for annotation processing in all macro engines implemented by AVSystem Commons. They allow |
| 9 | +the programmer to reduce boilerplate by reusing and grouping annotations. |
| 10 | + |
| 11 | +Mechanisms described below are all implemented in `MacroCommons` base trait used in macro implementations. |
| 12 | + |
| 13 | +<!-- START doctoc generated TOC please keep comment here to allow auto update --> |
| 14 | +<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> |
| 15 | +**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* |
| 16 | + |
| 17 | +- [Annotation processing](#annotation-processing) |
| 18 | + - [Annotation inheritance](#annotation-inheritance) |
| 19 | + - [Extending existing annotations](#extending-existing-annotations) |
| 20 | + - [Aggregating annotations](#aggregating-annotations) |
| 21 | + - [Annotation order](#annotation-order) |
| 22 | + - [`@defaultsToName`](#defaultstoname) |
| 23 | + |
| 24 | +<!-- END doctoc generated TOC please keep comment here to allow auto update --> |
| 25 | + |
| 26 | +### Annotation inheritance |
| 27 | + |
| 28 | +When a `MacroCommons`-based macro looks for annotations, they are automatically _inherited_. What that means |
| 29 | +exactly depends on the target symbol of an annotation: |
| 30 | + |
| 31 | +* Annotations applied on a class or trait are automatically inherited by all their subtraits, subclasses |
| 32 | + and objects. The only exception is when an annotation extending `NotInheritedFromSealedTypes` is applied |
| 33 | + on a `sealed` trait or class. |
| 34 | +* Annotations applied on a member (`def`, `val`, `var`) are automatically inherited by all members that |
| 35 | + implement or override it. |
| 36 | +* Annotations applied on method parameters (or type parameters) are automatically inherited by corresponding |
| 37 | + parameters in all implementing and overriding methods. |
| 38 | +* Annotations applied on `val` or `var` definitions which correspond to constructor parameters are inherited |
| 39 | + by these constructor parameters themselves. |
| 40 | + |
| 41 | +### Extending existing annotations |
| 42 | + |
| 43 | +You may create subclasses of existing annotations used by macro engines. For example, we may create a subclass |
| 44 | +of `@name` annotation used by `GenCodec` materialization: |
| 45 | + |
| 46 | +```scala |
| 47 | +import com.avsystem.commons.serialization._ |
| 48 | + |
| 49 | +class customName(override val name: String) extends name(name) |
| 50 | +``` |
| 51 | + |
| 52 | +Now, when the macro looks for `@name` annotation, it will also find `@customName` because of their |
| 53 | +subtyping relation. However, in order for the macro to be able to properly _statically_ (in compile time) |
| 54 | +extract the `name` constructor parameter, it must be declared in exactly like in the example above, i.e. |
| 55 | +it must be an `override val` constructor parameter which overrides the original one. |
| 56 | + |
| 57 | +Note that for such statically inspected annotation parameters you cannot simply pass them as super constructor |
| 58 | +parameters, e.g. the following will **NOT** work: |
| 59 | + |
| 60 | +```scala |
| 61 | +class id extends name("id") // doesn't work |
| 62 | +``` |
| 63 | + |
| 64 | +This is because super constructor parameters are not available for macros in compile time. |
| 65 | +The same effect can however be achieved using [annotation aggregates](#aggregating-annotations) |
| 66 | + |
| 67 | +### Aggregating annotations |
| 68 | + |
| 69 | +It is possible to create your own annotations which group - or aggregate - multiple other annotations. |
| 70 | +This way you can reduce boilerplate associated with annotations. |
| 71 | + |
| 72 | +In order to do that, you must create an annotation class that extends `AnnotationAggregate` and |
| 73 | +redefine its dummy `Implied` type member. You can then put your aggregated annotations on that type |
| 74 | +member, e.g. |
| 75 | + |
| 76 | +```scala |
| 77 | +import com.avsystem.commons.annotation._ |
| 78 | +import com.avsystem.commons.serialization._ |
| 79 | + |
| 80 | +class id extends AnnotationAggregate { |
| 81 | + @name("id") type Implied |
| 82 | +} |
| 83 | +``` |
| 84 | + |
| 85 | +Now, when something is annotated as `@id`, macro engines will effectively "explode" this annotation |
| 86 | +into all annotations applied on the `Implied` member. This is done recursively which means that |
| 87 | +the `Implied` member itself may be annotated with more aggregating annotations. |
| 88 | + |
| 89 | +Having to redefine the dummy `Implied` type member may seem strange. It would seem be more natural to |
| 90 | +put aggregated annotation on the aggregate itself, e.g. `@name("id") class id extends AnnotationAggregate` |
| 91 | +(this doesn't work). However, having aggregate annotation on a type member lets us refer to parameters |
| 92 | +of the aggregate itself, e.g. |
| 93 | + |
| 94 | +```scala |
| 95 | +import com.avsystem.commons.annotation._ |
| 96 | +import com.avsystem.commons.serialization._ |
| 97 | + |
| 98 | +class customName(name: String) extends AnnotationAggregate { |
| 99 | + @name(name) type Implied |
| 100 | +} |
| 101 | +``` |
| 102 | + |
| 103 | +Now, when "exploding" the aggregate, the macro engine will _statically_ replace references to |
| 104 | +constructor parameters of the aggregate inside `Implied` annotations. For example, when something |
| 105 | +is annotated as `@customName("id")` the macro will effectively see `@name("id")`. |
| 106 | + |
| 107 | +### Annotation order |
| 108 | + |
| 109 | +Scala allows annotating symbols with the same annotation multiple times. Also, annotation may be |
| 110 | +repeated because of [inheritance](#annotation-inheritance) - the same annotation may be applied on |
| 111 | +a symbol directly and inherited. In such situations, the order of annotations is well defined: |
| 112 | + |
| 113 | +* When two annotations are applied _directly_ on the same symbol, the first one takes precedence, e.g. |
| 114 | + `@name("A") @name("B") class C` - `@name("A")` has precedence over `@name("B")` |
| 115 | +* Annotations applied directly have precedence over inherited annotations. |
| 116 | +* Among inherited annotations, precedence is determined based on linearization order and therefore |
| 117 | + is analogous to how class/trait members override themselves. |
| 118 | + |
| 119 | +With respect to the order defined above, macro engines may sometimes take only the _first_ annotation |
| 120 | +of given type or sometimes inspect _all_ of them, in that order. This depends on particular annotation |
| 121 | +and how it's understood by particular macro engine. |
| 122 | + |
| 123 | +### `@defaultsToName` |
| 124 | + |
| 125 | +`@defaultsToName` is a meta-annotation that you may put on a `String` typed constructor parameter |
| 126 | +of an annotation. This parameter may then take a dummy default value (e.g. `null`) and macro engines |
| 127 | +will replace that default value with annotated symbol's original (source) name. For example: |
| 128 | + |
| 129 | +```scala |
| 130 | +class awesome(@defaultsToName rawName: String = null) extends StaticAnnotation |
| 131 | +``` |
| 132 | + |
| 133 | +Now, annotating a class with `@awesome` without giving the `rawName` argument explicitly: |
| 134 | + |
| 135 | +```scala |
| 136 | +@awesome class Klass |
| 137 | +``` |
| 138 | + |
| 139 | +is equivalent to annotating it as `@awesome("Klass")`. |
| 140 | + |
| 141 | +This of course works for all kinds of symbols that can be annotated, not only classes. |
0 commit comments