@@ -307,39 +307,154 @@ run the same macros with the same inputs, and get the same augmentation library.
307
307
This allows debugging and stack traces to work consistently, and be meaningful
308
308
and useful.
309
309
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.
313
312
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
317
314
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.
320
319
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.
322
323
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`:
327
325
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 {}
331
366
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
333
373
334
374
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
336
398
337
399
If no order is defined between two macro applications, then their augmentations
338
400
are sorted based on the source offset of the macro application.
339
401
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
+
340
431
Note that when multiple applications are on the same declaration, there is a
341
432
defined order, which is the reverse source offset order.
342
433
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
+
343
458
#### Rule #3 : Each augmentation should be separated by one empty line
344
459
345
460
We need to ensure consistent whitespace across tools, and this follows standard
@@ -352,64 +467,49 @@ separating declarations.
352
467
In the future, we may decide to run ` dart format ` or some other lighter weight
353
468
formatter on augmentations which would also enforce consistent whitespace.
354
469
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:
356
478
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:
359
485
360
486
``` dart
361
- @TypeMacroOnB ()
362
- class B extends A with C implements D {
487
+ @MyMacro ()
488
+ library;
363
489
364
- }
490
+ class A { }
365
491
366
- @TypeMacroOnA()
367
- class A implements C {}
492
+ augment class A {
493
+ final int b;
368
494
369
- @TypeMacroOnC
370
- mixin C {
371
- @MemberMacroOnC()
372
- int get c;
495
+ A(this.b);
373
496
}
374
-
375
- @TypeMacroOnD1()
376
- @TypeMacroOnD2()
377
- interface class D {}
378
497
```
379
498
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.
381
502
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.
410
508
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.
413
513
414
514
## Phases
415
515
0 commit comments