Skip to content

Commit becef0e

Browse files
committed
macros page, gif
1 parent 0d03337 commit becef0e

File tree

2 files changed

+281
-0
lines changed

2 files changed

+281
-0
lines changed
Loading

src/content/language/macros.md

+281
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,281 @@
1+
---
2+
title: Macros (experimental)
3+
description: Learn about the experimental macros feature as it develops.
4+
---
5+
6+
[The Dart macro system][spec] is a major new language feature
7+
***currently under development*** which adds support for
8+
[static meta-programming][motivation] to the Dart language.
9+
10+
A Dart macro is a user-defineable piece of code that takes in other code as parameters
11+
and operates on it during compile time to create, modify, or add declarations.
12+
13+
You can think about the macro system in two parts: using macros and writing macros.
14+
This page covers each (at a high level, as ***the feature is still under development***)
15+
in the following sections:
16+
17+
- [**The `JsonCodable` macro**](#the-jsoncodable-macro):
18+
A ready-made macro you can try out today (behind an experiment flag)
19+
that offers a seamless solution to the
20+
common issue of tedious JSON serialization in Dart.
21+
22+
- [**The macros feature in general**](#the-macros-language-feature):
23+
Why we're adding macros to Dart, motivating use cases,
24+
benefits over existing code gen solutions,
25+
and a cursory overview of how writing macros works.
26+
27+
[spec]: https://github.com/dart-lang/language/blob/main/working/macros/feature-specification.md
28+
[motivation]: https://github.com/dart-lang/language/blob/main/working/macros/motivation.md
29+
30+
## The `JsonCodable` macro
31+
32+
:::important
33+
The `JsonCodable` macro is not stable and currently behind an [experiment flag][].
34+
Functionality is subject to change.
35+
:::
36+
37+
The [`JsonCodable`][] macro encodes and decodes
38+
user-defined Dart classes to JSON maps of type `Map<String, Object?>`.
39+
It generates two members, a `toJson` serialization method,
40+
and a `fromJson` deserialization constructor.
41+
42+
[experiment flag]: /tools/experiment-flags
43+
[`JsonCodable`]: https://github.com/dart-lang/sdk/tree/main/pkg/json
44+
45+
### Set up the experiment
46+
47+
1. [Add the package][] to your pubspec and retrieve
48+
its dependencies.
49+
2. [Add the experiment][] to the `analysis_options.yaml`
50+
file at the root of your project:
51+
52+
```yaml
53+
analyzer:
54+
enable-experiment:
55+
- macros
56+
```
57+
3. Import it in the file you plan to use it:
58+
59+
```dart
60+
import 'package:json/json.dart';
61+
```
62+
63+
4. Run your project with the experiment flag:
64+
65+
```console
66+
dart --enable-experiment=macros run bin/my_app.dart
67+
```
68+
69+
[Add the package]: /guides/packages
70+
[Add the experiment]: /tools/experiment-flags#using-experiment-flags-with-the-dart-analyzer-command-line-and-ide
71+
72+
### Use the macro
73+
74+
To use the `JsonCodable` macro, append the annotation to the class you want to serialize:
75+
76+
```dart
77+
import 'package:json/json.dart';
78+
79+
@JsonCodable() // Macro annotation.
80+
class User {
81+
final int? age;
82+
final String name;
83+
final String username;
84+
}
85+
```
86+
87+
The macro introspects the `User` class and derives the implementations of
88+
`fromJson` and `toJson` using the `User` class's fields.
89+
90+
So, without needing to define them yourself, `toJson` and `fromJson` are now
91+
available to use on objects of the annotated class:
92+
93+
```dart
94+
void main() {
95+
// Given some arbitrary JSON:
96+
var userJson = {
97+
'age': 5,
98+
'name': 'Roger',
99+
'username': 'roger1337'
100+
};
101+
102+
// Use the generated members:
103+
var user = User.fromJson(userJson);
104+
print(user);
105+
print(user.toJson());
106+
}
107+
```
108+
109+
### View the generated code
110+
111+
You can optionally view the generated code.
112+
Click on the "**Go to Augmentation**" link that appears under the annotation
113+
in your IDE (supported in VSCode and IntelliJ)
114+
to see how the macro generates `toJson` and `fromJson`.
115+
116+
If you change anything in the annotated class, you can watch the generated augmentation
117+
adjust in real time alongside your application code:
118+
119+
![A side-by-side gif of the generated augmentation updating as the code it's augmenting is updated](/assets/img/language/macro-augmentation.gif)
120+
121+
### Trigger custom diagnostics
122+
123+
The `JsonCodable` macro has built-in diagnostics that are emmitted just like
124+
diagnostics from the language itself. For example, if you try to manually
125+
declare a `toJson` method where the macro is applied, the analyzer will emit
126+
the error:
127+
128+
```dart
129+
@JsonCodable()
130+
class HasToJson {
131+
void [!toJson!]() {}
132+
// Error: Cannot generate a toJson method due to this existing one.
133+
}
134+
```
135+
136+
You can search "`DiagnosticMessage`" in the [the definition of `JsonCodable`][json]
137+
for other errors the macro will throw. For example, extending a class that isn't
138+
also serializable, or if field names don't exactly match the key names in the given JSON.
139+
140+
[the definition of `JsonCodable`]: https://github.com/dart-lang/sdk/blob/master/pkg/json/lib/json.dart
141+
142+
## The macros language feature
143+
144+
Dart macros are a *static* metaprogramming, or code generation, solution.
145+
Unlike *runtime* code generation solutions (like [build_runner][]),
146+
macros are fully integrated into the Dart language and executed by the compiler.
147+
This makes macros much more efficient than relying on an secondary tool:
148+
149+
- **Nothing extra to run**;
150+
simply call `dart ` / `flutter run <your app>` and the macro builds with your code.
151+
- **No duplicated work** or constant recompiling hurting performance;
152+
all the building and code generation happen directly in the compiler,
153+
automatically.
154+
- **Not written to disk**, so no part files or pointers to generated references;
155+
macros directly augment the *existing* class.
156+
- **No confusing/obfuscated testing**;
157+
custom diagnostics are emitted like any other message from the analyzer,
158+
directly in the IDE.
159+
160+
And also far more efficient, and far less error prone, than manually
161+
writing solutions to these types of problems yourself.
162+
163+
Check out these examples showing the same JSON serialization
164+
implemented three different ways:
165+
166+
- Using the [`JsonCodable` macro][].
167+
- Using the [`json_serializable` code gen package][].
168+
- Manually, [with `dart:convert`][].
169+
170+
[build_runner]: /tools/build_runner
171+
[`JsonCodable` macro]: https://github.com/mit-mit/sandbox/blob/main/explorations/json/dart_jsoncodable/bin/main.dart
172+
[`json_serializable` code gen package]: https://github.com/mit-mit/sandbox/blob/main/explorations/json/dart_json_serializable/bin/main.dart
173+
[with `dart:convert`]: https://github.com/mit-mit/sandbox/blob/main/explorations/json/dart_convert/bin/main.dart
174+
175+
### Use cases
176+
177+
Macros provide reusable mechanisms to address patterns characterized by tedious
178+
boilerplate, and often times the need to iterate over the fields of a class.
179+
Some common examples are:
180+
181+
- **Json serialization.** The extra tooling required to serialize JSON,
182+
like the [json_serializable][] package, isn't as efficient as it should be.
183+
The `JsonCodable` macro provides a much cleaner way to
184+
generate serialization code; [try it today](#the-jsoncodable-macro).
185+
186+
- **Data classes.** Dart's [most requested][] feature is for data classes
187+
that automatically provide a constructor, and implementations of the `==`,
188+
`hashCode`, and `copyWith()` methods for each field.
189+
Implementing the solution with macros would mean users can customize the
190+
their data classes however they see fit.
191+
192+
- **Verbose Flutter patterns.** One example is breaking down a complex `build`
193+
method into an aggregation of smaller widget classes. It's
194+
better for performance and makes the code more maintainable. Unfortunately,
195+
writing all those smaller classes requires tons of boilerplate, which discourages
196+
users. Macros could potentially provide a solution that iterates over a
197+
complex `build` method to generates smaller widget classes,
198+
greatly improving productivity and quality of Flutter code.
199+
200+
[json_serializable]: https://pub.dev/packages/json_serializable
201+
[most requested]: https://github.com/dart-lang/language/issues/314
202+
203+
### How macros work
204+
205+
:::important
206+
The macros language feature is not stable and currently behind an [experiment flag][].
207+
Functionality is highly subject to change. This section will remain very high-level until stable.
208+
:::
209+
210+
To create a macro, you write a macro declaration similar to a class,
211+
using the `macro` keyword.
212+
A macro declaration must also include an `implements` clause to define
213+
which interface the macro can be applied to.
214+
215+
For example, a macro that is applicable to classes, and adds new declarations to the class,
216+
would implement the `ClassDeclarationsMacro` interface:
217+
218+
```dart
219+
macro class MyMacro implements ClassDeclarationsMacro {
220+
const MyMacro();
221+
222+
223+
// ...
224+
}
225+
```
226+
227+
While the feature is still in development, you can find the full list of
228+
macro interfaces [in the source code][types].
229+
230+
The `MyMacro` constructor in the above example corresponds to the annotation
231+
you would use to apply the macro to a declaration.
232+
The syntax is the same as Dart's existing metadata annotation syntax:
233+
234+
```dart
235+
@MyMacro()
236+
class A {}
237+
```
238+
239+
Within the body of the macro declaration is where you define the code you want
240+
the macro to [generate](#view-the-generated-code), as well as any
241+
[diagnostics](#trigger-custom-diagnostics) you want the macro to emit.
242+
243+
At a very high-level, writing macros essentially work by using builder methods
244+
to piece together the *properties* of a declaration with *identifiers* on those
245+
properties. The macro gathers this information through deep [introspection][] of
246+
the program.
247+
248+
Macros are still under development, so that's as much detail we can go into for now.
249+
If you're curious, or would like to try it yourself behind an experiment flag,
250+
the best guidance is to take a look at the implementation of exisiting macros:
251+
252+
- Check out the [definition][json] of the `JsonCodable` macro,
253+
- Or any of the [examples][] available in the language repo.
254+
255+
[types]: https://github.com/dart-lang/sdk/blob/main/pkg/_macros/lib/src/api/macros.dart
256+
[json]: https://github.com/dart-lang/sdk/blob/master/pkg/json/lib/json.dart
257+
[augmentation]: https://github.com/dart-lang/language/blob/main/working/augmentation-libraries/feature-specification.md
258+
[examples]: https://github.com/dart-lang/language/tree/main/working/macros/example
259+
260+
## Timeline
261+
262+
The stable release date for macros is currently unknown.
263+
This is due to the complexity of their implementation.
264+
265+
Macros work by deeply [introspecting][introspection] the program in which
266+
they're applied. A macro may end up traversing distant parts of the program
267+
to gather necessary information on properties and type annotations
268+
for the declaration it's augmenting.
269+
270+
Considering their application in large code bases, where multiple macros can
271+
introspect and augment the base continuously in different places,
272+
the design of [ordering][] and [phases][] of execution is especially challenging
273+
and requires careful consideration.
274+
275+
We are working towards a stable release of the [`JsonCodable`][] macro
276+
later this year (2024), and a stable release of the full language feature
277+
(namely, writing your own macros) early next year (2025).
278+
279+
[introspection]: https://github.com/dart-lang/language/blob/main/working/macros/feature-specification.md#introspection
280+
[ordering]: https://github.com/dart-lang/language/blob/main/working/macros/feature-specification.md#ordering-in-metaprogramming
281+
[phases]: https://github.com/dart-lang/language/blob/main/working/macros/feature-specification.md#phases

0 commit comments

Comments
 (0)