Skip to content

Commit 4beca04

Browse files
committed
documented annotation processing
1 parent 6fd9a07 commit 4beca04

File tree

7 files changed

+221
-25
lines changed

7 files changed

+221
-25
lines changed

commons-macros/src/main/scala/com/avsystem/commons/macros/MacroCommons.scala

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -441,7 +441,6 @@ trait MacroCommons { bundle =>
441441

442442
def withSuperSymbols(s: Symbol): Iterator[Symbol] = s match {
443443
case cs: ClassSymbol => cs.baseClasses.iterator
444-
case ms: MethodSymbol => (ms :: ms.overrides).iterator
445444
case ps: TermSymbol if ps.isParameter =>
446445
// if this is a `val` constructor parameter, include `val` itself and its super symbols
447446
accessorFor(ps).map(pa => Iterator(s) ++ withSuperSymbols(pa)).getOrElse {
@@ -450,6 +449,12 @@ trait MacroCommons { bundle =>
450449
val paramIdx = oms.paramLists(paramListIdx).indexWhere(_.name == ps.name)
451450
withSuperSymbols(oms).map(_.asMethod.paramLists(paramListIdx)(paramIdx))
452451
}
452+
case ts: TermSymbol => (ts :: ts.overrides).iterator
453+
case ps: TypeSymbol if ps.isParameter && ps.owner.isMethod =>
454+
val oms = ps.owner.asMethod
455+
val paramIdx = oms.typeParams.indexWhere(_.name == ps.name)
456+
withSuperSymbols(oms).map(_.asMethod.typeParams(paramIdx))
457+
case ts: TypeSymbol => (ts :: ts.overrides).iterator
453458
case _ => Iterator(s)
454459
}
455460

docs/AkkaRPCFramework.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@
22

33
`commons-akka` module contains implementation of RPC framework using Akka Remoting. It supports every basic method type (procedure, function and getter), but it also allows you to define methods returning `Observable` from Monix library.
44

5+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
6+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
7+
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
8+
9+
- [Akka based typesafe RPC framework](#akka-based-typesafe-rpc-framework)
10+
- [Setup](#setup)
11+
- [Server-side setup](#server-side-setup)
12+
- [Client-side setup](#client-side-setup)
13+
- [Serialization](#serialization)
14+
- [Observable](#observable)
15+
- [Caveats](#caveats)
16+
- [Timeouts](#timeouts)
17+
18+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
19+
520
Note: For examples purpose, let's assume that we have defined rpc:
621
```scala
722
@RPC trait UserService {

docs/Annotations.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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.

docs/GenCodec.md

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,8 @@ and write a value of type `T` to an [`Output`](http://avsystem.github.io/scala-c
6262
[`Input`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/serialization/Input.html) and [`Output`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/serialization/Output.html) are abstract, raw, stream-like, mutable entities which perform the actual serialization and
6363
deserialization using some particular format hardwired into them, like JSON. Therefore, [`GenCodec`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/serialization/GenCodec.html) by itself is not
6464
bound to any format. It only depends on the fact that this format is capable of serializing following types and structures:
65-
* integer numbers up to at least 64-bit precision (i.e. `Long`)
66-
* decimal numbers up to at least 64-bit precision (i.e. `Double`)
65+
* integer numbers, arbitrarily large
66+
* decimal numbers, arbitrarily large
6767
* `Char`s, `String`s, `Boolean`s and `null`s
6868
* arbitrary byte chunks
6969
* millisecond-precision timestamps
@@ -268,11 +268,12 @@ as `{"name":"Fred","birthYear":1990}`.
268268

269269
The macro will only compile if it can find a `GenCodec` instance for every field of your case class. Otherwise, you'll get a compilation error telling you that some field can't be serialized because no implicit `GenCodec` is defined for its type. This way the macro will fully validate your case class. This is good - you'll never serialize any type by accident and if you forget to make any type serializable, the compiler will tell you about it. This way you avoid problems usually associated with runtime reflection based serialization, particularly popular in Java ecosystem.
270270

271-
In general, the serialization framework requires that the serialized representation retains order of object fields and during deserialization supplies them in exactly the same order as they were written during serialization. This is usually a reasonable assumption because most serialization formats are either textual, binary or stream-like (the word "serialization" itself indicates a sequential order).
271+
In general, the serialization framework requires that the serialized representation retains order of object fields and during deserialization supplies them in exactly the same order as they were written during serialization. This is usually a reasonable assumption because most serialization formats are either textual, binary or stream-like (the word "serialization" itself indicates a sequential order). If field order is not preserved, serialization format must support random field access instead - see [object field order](#object-field-order).
272272

273273
The codec materialized for case class guarantees that the fields are written in the order of their declaration in constructor. However, during deserialization the codec is lenient and does not require that the order of fields is the same as during serialization. It will successfully deserialize the case class as long as all the fields are present in the serialized format (in any order) or have a default value defined (either as Scala-level default parameter value or with [`@whenAbsent`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/serialization/whenAbsent.html) annotation). Any superfluous fields will be simply ignored. This allows the programmer to refactor the case class without breaking compatibility with serialization format - fields may be reordered and removed. New fields may also be added, as long as they have a default value defined.
274274

275-
The way macro materializes the codec may be customized with annotations:
275+
The way macro materializes the codec may be customized with annotations. All annotations are governed by the same
276+
[annotation processing](Annotations.md) rules.
276277

277278
* Using [`@name`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/serialization/name.html) you can change the raw field name used for serialization of each case class field.
278279

@@ -400,7 +401,8 @@ Instead of creating a single-field object, now the `materialize` macro will assu
400401

401402
### Customizing sealed hierarchy codecs
402403

403-
Similarly to case classes, sealed hierarchy codecs may be customized with annotations:
404+
Similarly to case classes, sealed hierarchy codecs may be customized with annotations. All annotations are governed by
405+
the same [annotation processing](Annotations.md) rules.
404406

405407
* Using [`@name`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/serialization/name.html) you can change the class name saved as marker field name in nested format or as marker field value in flat format.
406408

@@ -547,6 +549,8 @@ object Key extends HasGenCodec[Key[_]]
547549

548550
### Customizing annotations
549551

552+
All annotations are governed by [annotation processing](Annotations.md) rules.
553+
550554
* [`@name`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/serialization/name.html)
551555
* [`@transparent`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/serialization/transparent.html)
552556
* [`@transientDefault`](http://avsystem.github.io/scala-commons/api/com/avsystem/commons/serialization/transientDefault.html)

docs/REST.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,6 +172,12 @@ Server: Jetty(9.3.23.v20180228)
172172
{"id":"Fred-ID","name":"Fred","birthYear":1990}
173173
```
174174

175+
## Annotations
176+
177+
REST framework relies heavily on annotations for customization. All annotations are governed by
178+
the same [annotation processing](Annotations.md) rules. To use annotations more effectively and with less
179+
boilerplate, it is highly recommended to be familiar with these rules.
180+
175181
## REST API traits
176182

177183
As we saw in the quickstart example, REST API is defined by a Scala trait adjusted with annotations.
@@ -1051,6 +1057,10 @@ objects.
10511057
However, `@description` is just an example of more general mechanism - schemas, parameters and operations
10521058
can be modified arbitrarily.
10531059

1060+
Also, remember that all annotations are processed with respect to the same
1061+
[annotation processing](Annotations.md) rules. It is recommended to familiarize oneself with these rules in
1062+
order to use them more effectively and with less boilerplate.
1063+
10541064
#### Adjusting schemas
10551065

10561066
In order to adjust schemas, one can define arbitrary annotations that extend `SchemaAdjuster`.

docs/RPCFramework.md

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,31 @@
44

55
RPC framework is cross-compiled for JVM and JS, which makes it especially useful for implementing communication layer between client and server in ScalaJS applications.
66

7-
## Table of Contents
8-
9-
* [RPC traits](#rpc-traits)
10-
* [Overloaded remote methods](#overloaded-remote-methods)
11-
* [Non-abstract members](#non-abstract-members)
12-
* [Choosing a serialization mechanism](#choosing-a-serialization-mechanism)
13-
* [RPC client implementation](#rpc-client-implementation)
14-
* [Implementing the transport](#implementing-the-transport)
15-
* [Wrapping raw RPC into a "real" proxy](#wrapping-raw-rpc-into-a-real-proxy)
16-
* [Type safety](#type-safety)
17-
* [Internals](#internals)
18-
* [RPC server implementation](#rpc-server-implementation)
19-
* [RPC implementation](#rpc-implementation)
20-
* [Wrapping "real" RCP implementation into a raw RPC](#wrapping-real-rcp-implementation-into-a-raw-rpc)
21-
* [Type safety](#type-safety-1)
22-
* [Internals](#internals-1)
23-
* [RPC metadata](#rpc-metadata)
24-
* [Example](#example)
25-
* [Declaring typeclass instances explicitly](#declaring-typeclass-instances-explicitly)
7+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
8+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
9+
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
10+
11+
- [Typesafe RPC & proxy framework](#typesafe-rpc--proxy-framework)
12+
- [Table of Contents](#table-of-contents)
13+
- [RPC traits](#rpc-traits)
14+
- [Overloaded remote methods](#overloaded-remote-methods)
15+
- [Non-abstract members](#non-abstract-members)
16+
- [Choosing a serialization mechanism](#choosing-a-serialization-mechanism)
17+
- [RPC client implementation](#rpc-client-implementation)
18+
- [Implementing the transport](#implementing-the-transport)
19+
- [Wrapping raw RPC into a "real" proxy](#wrapping-raw-rpc-into-a-real-proxy)
20+
- [Type safety](#type-safety)
21+
- [Internals](#internals)
22+
- [RPC server implementation](#rpc-server-implementation)
23+
- [RPC implementation](#rpc-implementation)
24+
- [Wrapping "real" RCP implementation into a raw RPC](#wrapping-real-rcp-implementation-into-a-raw-rpc)
25+
- [Type safety](#type-safety-1)
26+
- [Internals](#internals-1)
27+
- [RPC metadata](#rpc-metadata)
28+
- [Example](#example)
29+
- [Declaring typeclass instances explicitly](#declaring-typeclass-instances-explicitly)
30+
31+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
2632

2733
## RPC traits
2834

docs/RedisDriver.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,21 @@
88
libraryDependencies += "com.avsystem.commons" %% "commons-redis" % avsCommonsVersion
99
```
1010

11+
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
12+
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
13+
**Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)*
14+
15+
- [Redis driver](#redis-driver)
16+
- [Overview](#overview)
17+
- [Quickstart example](#quickstart-example)
18+
- [APIs and clients](#apis-and-clients)
19+
- [Client types](#client-types)
20+
- [API variants](#api-variants)
21+
- [Examples](#examples)
22+
- [Benchmarks](#benchmarks)
23+
24+
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
25+
1126
## Overview
1227

1328
The module `commons-redis` contains from-the-scratch implementation of Scala driver for Redis. Its most important goals

0 commit comments

Comments
 (0)