Skip to content

Commit 47a849e

Browse files
authored
Change the specified augmentation library structure for macros (#3359)
This moves us to a more user friendly format (all augmentations for a single type grouped together), and removes the incorrect statements about source offset stability. Closes #3350
1 parent db7bdbc commit 47a849e

File tree

1 file changed

+165
-65
lines changed

1 file changed

+165
-65
lines changed

working/macros/feature-specification.md

Lines changed: 165 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -307,39 +307,154 @@ run the same macros with the same inputs, and get the same augmentation library.
307307
This allows debugging and stack traces to work consistently, and be meaningful
308308
and useful.
309309
310-
It is also important that if a declaration is added in Phase 1, the source
311-
offsets to that declaration should not change after Phase 2 or 3 run. This means
312-
that tools don't need to update source offsets after each phase.
310+
We have several rules based around maintaining the consistency of generated
311+
output across tools.
313312
314-
Another important consideration is that in Phase 2, the ordering of macros is
315-
user perceptable, and so augmentation results should be serializable to disk -
316-
with stable offsets - at multiple points throughout the process.
313+
#### Rule #1: Nested augmentations on type declarations are merged
317314
318-
We have several rules based around maintaining this stability of source offsets
319-
and consistency of generated output across tools.
315+
When there are multiple augmentations of the same type declaration, they are
316+
merged into a single `augment <type> {}` block. This is easier for end users to
317+
understand. This includes multiple augmentations of the _same_ declaration, they
318+
should appear as separate declarations within the same type augmentation.
320319
321-
#### Rule #1: Each macro application appends a new independent augmentation
320+
This does result in constantly shifting source offsets between phases, and in
321+
particular throughout Phase 2 of macro expansion, given that some macros can see
322+
the outputs of other macros within that same phase.
322323
323-
While augmentations on a given type declaration can be grouped together, they
324-
are not required to be. You can have as many `augment class A {}` declarations
325-
as you want in a given augmentation library. We take advantage of that fact, and
326-
require that every macro application creates its own augmentation.
324+
For example, if both of these macros add a new declaration to `A`:
327325
328-
This means each macro application is only appending new top level augmentation
329-
declarations to the augmentation library. It only grows over time, and previous
330-
lines are never altered.
326+
```dart
327+
@AddB()
328+
@AddC()
329+
class A {}
330+
```
331+
332+
Then the resulting library should have both declarations merged into one
333+
augmentation of `A` like this:
334+
335+
```dart
336+
augment class A {
337+
void b() {}
338+
void c() {}
339+
}
340+
```
341+
342+
Note that we previously considered an "append only" approach, with no merging of
343+
augmentations. The goal was to avoid changing source offsets, but this doesn't
344+
work since later augmentations may need to add additional imports, which would
345+
result in shifting offsets anyways. Since we have to deal with the shifting
346+
offsets either way, we might as well derive user value out of it.
347+
348+
#### Rule #2: Augmentations are sorted by phase, application, then source order
349+
350+
##### Sorting by phase
351+
352+
Augmentations from earlier phases appear before augmentations from later phases:
353+
354+
```dart
355+
@AddMemberB() // Runs in phase 2, adds a member `b` to `A`.
356+
class A {}
357+
358+
@AddTypeD() // Runs in the first phase, creates the class `D`.
359+
class C {}
360+
```
361+
362+
Would result in:
363+
364+
```dart
365+
class D {}
331366
332-
#### Rule #2: Augmentations are added in application order, then source order
367+
augment class A {
368+
void b() {}
369+
}
370+
```
371+
372+
##### Sorting by application order
333373

334374
Where an application order is explicitly defined, the augmentations are appended
335-
in that same order as the primary sort.
375+
in that same order as the primary sort:
376+
377+
```dart
378+
@AugmentB() // In phase 3, augments the member `b`.
379+
class A {
380+
void b() {}
381+
382+
@AugmentC(); // In phase 3, augments the member `c`
383+
void c() {}
384+
}
385+
```
386+
387+
Since inner macro applications run first, we get the augmentation of `c` first:
388+
389+
```dart
390+
augment class A {
391+
augment void c() {}
392+
393+
augment void b() {}
394+
}
395+
```
396+
397+
##### Sort by source offset of the application
336398

337399
If no order is defined between two macro applications, then their augmentations
338400
are sorted based on the source offset of the macro application.
339401

402+
```dart
403+
class A {
404+
@AugmentB() // In phase 3, augments the member `b`.
405+
void b() {}
406+
407+
@AugmentC(); // In phase 3, augments the member `c`
408+
void c() {}
409+
}
410+
```
411+
412+
Since there is no defined application order, source order is used for the
413+
augmentation ordering:
414+
415+
```dart
416+
augment class A {
417+
augment void b() {}
418+
augment void c() {}
419+
}
420+
```
421+
422+
##### Merge type augmentations together
423+
424+
When augmenting a type declaration, if that type declaration has already been
425+
augmented then the new augmentation(s) are merged into that augmentation per the
426+
first rule. Ordering within that type augmentation follows all of these rules.
427+
428+
This only applies to `augment <type>` declarations and not _new_ type
429+
declarations.
430+
340431
Note that when multiple applications are on the same declaration, there is a
341432
defined order, which is the reverse source offset order.
342433

434+
435+
```dart
436+
@AddTopLevelFoo() // In phase two, adds a top level variable `foo`.
437+
class A {
438+
@AddC() // In phase 2, augments the member `c`.
439+
@AugmentB() // In phase 3, augments the member `b`
440+
void b() {}
441+
}
442+
```
443+
444+
Since an augmentation to `A` is added in phase 2, the augmentation of it's
445+
member `b` in phase 3 is merged into that augmentation, which puts it above the
446+
variable `foo` which was added in phase 2 (this rule takes precedence over other
447+
rules).
448+
449+
```dart
450+
augment class A {
451+
void c() {} // Added in phase 2, ran before `AddTopLevelFoo()`.
452+
augment b() {} // Added in phase 3, but merged into the previous augmentation.
453+
}
454+
455+
int foo = 1; // Added in phase 2, after `c` was added to `A`.
456+
```
457+
343458
#### Rule #3: Each augmentation should be separated by one empty line
344459

345460
We need to ensure consistent whitespace across tools, and this follows standard
@@ -352,64 +467,49 @@ separating declarations.
352467
In the future, we may decide to run `dart format` or some other lighter weight
353468
formatter on augmentations which would also enforce consistent whitespace.
354469

355-
#### Ordering example
470+
#### Rule #4: New types are declared separately from their augmentations
471+
472+
If a macro declares a new type and then later augments it, this will result in
473+
separate type declarations. One normal one followed by an augmentation of that
474+
type.
475+
476+
For example, if `MyMacro` defines a type in phase 1 and then augments it in
477+
phase 2 by adding a field and a constructor:
356478

357-
Consider the complicated situation below, and assume all these macros are
358-
applied in all 3 phases:
479+
```dart
480+
@MyMacro()
481+
library;
482+
```
483+
484+
Would become:
359485

360486
```dart
361-
@TypeMacroOnB()
362-
class B extends A with C implements D {
487+
@MyMacro()
488+
library;
363489
364-
}
490+
class A {}
365491
366-
@TypeMacroOnA()
367-
class A implements C {}
492+
augment class A {
493+
final int b;
368494
369-
@TypeMacroOnC
370-
mixin C {
371-
@MemberMacroOnC()
372-
int get c;
495+
A(this.b);
373496
}
374-
375-
@TypeMacroOnD1()
376-
@TypeMacroOnD2()
377-
interface class D {}
378497
```
379498

380-
The augmentations would appear in the following order:
499+
It would arguably be more user friendly if we merged these new declarations from
500+
phase 2 into the original declaration, but there are some technical challenges
501+
with doing so, and we do not merge them today.
381502

382-
```dart
383-
// PHASE 1 augmentations order:
384-
//
385-
// TypeMacroOnB
386-
// TypeMacroOnA
387-
// MemberMacroOnC
388-
// TypeMacroOnC
389-
// TypeMacroOnD2
390-
// TypeMacroOnD1
391-
392-
// PHASE 2 augmentations order:
393-
//
394-
// MemberMacroOnC
395-
// TypeMacroOnC
396-
// TypeMacroOnA
397-
// TypeMacroOnD2
398-
// TypeMacroOnD1
399-
// TypeMacroOnB
400-
401-
// PHASE 3 augmentations order (same as phase 1):
402-
//
403-
// TypeMacroOnB
404-
// TypeMacroOnA
405-
// MemberMacroOnC
406-
// TypeMacroOnC
407-
// TypeMacroOnD2
408-
// TypeMacroOnD1
409-
```
503+
### Augmentation library source offsets
504+
505+
Any tool doing macro expansion will necessarily have to manage changing source
506+
offsets throughout the macro expansion process. This is necessary in order to
507+
facilitate a single augmentation library for the end user at the end.
410508

411-
Remember that each of these would have their own `augment class` declarations
412-
where applicable (following the first rule).
509+
It is likely that a tool would want to initially treat things as multiple
510+
separate augmentations, and then merge them all at the end. This would avoid
511+
parsing the entire augmentation library repeatedly. Although, the import
512+
prefixes for identifiers may change once merged in this mode.
413513

414514
## Phases
415515

0 commit comments

Comments
 (0)