File: implementation.md

package info (click to toggle)
rust-derive-deftly 1.6.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 1,652 kB
  • sloc: perl: 1,032; sh: 373; python: 227; makefile: 11
file content (596 lines) | stat: -rw-r--r-- 16,184 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
# Implementation approach - how does this work?

<!-- @dd-navbar toc .. -->
<!-- this line automatically maintained by update-navbars --><nav style="text-align: right; margin-bottom: 12px;">[ <em>docs: <a href="../index.html">crate top-level</a> | <a href="../index.html#overall-toc"><strong>overall toc</strong>, macros</a> | <a href="../doc_reference/index.html">template etc. reference</a> | <a href="https://diziet.pages.torproject.net/rust-derive-deftly/latest/guide/">guide/tutorial</a></em> ]</nav>

**You do not need to understand this in order to use derive-deftly.**

Also, you should not rely on the details here.
They don't form part of the public interface.

## Introduction

It is widely understood that proc macro invocations
cannot communicate with each other.
(Some people have tried sneaking round the back with disk files etc.
but this can break due to incremental and concurrent compilation.)

But, a proc macro can *define a `macro_rules` macro*.
Then, later proc macros can invoke that macro.
The expansion can even invoke further macros.
In this way, a proc macro invocation *can* communicate with
subsequent proc macro invocations.
(This trick is also used by
 [`ambassador`](https://crates.io/crates/ambassador).)

There is a further complication.
One macro X cannot peer into and dismantle
the expansion of another macro Y.
(This is necessary for soundness, when macros can generate `unsafe`.)
So we must sometimes define macros that apply other macros
(whose name is supplied as an argument)
to what is conceptually the first macro's output.

## Implementation approach - reusable template macros

### Expansion chaining

When a `#[derive(Deftly)]` call site invokes multiple templates,
we don't emit separate macro calls for each template.
Instead, we generate a macro call for the first template,
and pass it the names of subsequent templates.
Each template expansion is responsible for invoking the next.

This allows output from the expansion to be
threaded through the chain,
collecting additional information as we go,
and eventually analysed after *all* the expansions are done.
(This is done by putting`derive_deftly_engine`
as the final entry
in the list of macros to chain to.)

Currently, this data collection is used for
detecting unused meta attributes.

This does mean that specifying a completely unknown template
(for example, one that's misspelled or not in scope)
will prevent error reports from subsequent templates,
which is a shame.

### Overall input syntax for templates and `derive_deftly_engine!`

Because of expansion chaining, calls to the engine,
and to templates, overlap.

```rust,ignore
    macro! {
        { DRIVER.. }
        [ 1 0 AOPTIONS.. ] // not present for d_d_dengine expanding ad-hoc
                           // replaced with just "." at end of chain
    // always present, but always immediately ignored:
        ( .... )
    // these parts passed to d_d_engine only, and not in last call:
        { TEMPLATE.. }
        ( CRATE; [TOPTIONS..]
          TEMPLATE_NAME /* omitted if not available */;
          { IMPORTED_DEFINITIONS.. } // from a module (may be omitted)
          .... )
    // always present:
        // after here is simply passed through by templates:
        [
          // usually one or more of these; none at end, or ad-hoc
          CHAIN_TO ( CHAIN_PASS_AFTER_DRIVER .... )
          ..
        ]
        [ACCUM..]
        ....
    }
```

(We use the notation `....` for room we are leaving
for future expansion;
these are currently empty.)

The engine also accepts a nonoverlapping syntax for handling
template definitions which use modules - see below.

### 1. `define_derive_deftly!` macro for defining a reuseable template

Implemented in `define.rs::define_derive_deftly_func_macro`.

When used like this
```rust,ignore
    define_derive_deftly! {
        MyMacro TOPTIONS..:
        TEMPLATE..
    }
```
Expands to
```rust,ignore
    macro_rules! derive_deftly_template_Template { {
        { $($driver:tt)* }
        [ $($aoptions:tt)* ]
        ( $($future:tt)* )
        $($tpassthrough:tt)*
    } => {
        derive_deftly_engine! {
            { $($driver)* }
            [ $(aoptions)* ]
            ( )
            { TEMPLATE.. }
            ( $crate; [TOPTIONS..] Template;
              { IMPORTED_DEFINITIONS.. } // only if via modules (see below)
              )
            $($tpassthrough)*
        }
    } }
```

Except, every `$` in the TEMPLATE is replaced with `$orig_dollar`.
This is because a `macro_rules!` template
is not capable of generating a
literal in the expansion `$`.
(With the still-unstable
[`decl_macro`](https://github.com/rust-lang/rust/issues/83527)
feature, `$$` does this.)
`macro_rules!` simply passes through `$orig_dollar`
(as it does with all unrecognised variables), and
the template expansion engine treats `$orig_dollar` as just a single `$`.

(The extra `( )` parts after the driver and template
include space for future expansion.)

### 2. `#[derive_deftly(Template)]`, implemented in `#[derive(Deftly)]`

Template use is implemented in `derive.rs::derive_deftly`.

This

```rust,ignore
    #[derive(Deftly)]
    #[derive_deftly(Template[AOPTIONS..], Template2)]
    pub struct StructName { .. }
```

Generates:

```rust,ignore
    derive_deftly_template_Template! {
        { #[derive_deftly(..)] struct StructName { .. } }
        [ 1 0 AOPTIONS ]
        ( )
        [
          derive_deftly_template_Template2 ( [1 0 AOPTIONS2] () )
          derive_deftly_engine ( . () )
        ]
        [] // (nothing accumulated yet)
    }
```

### 3. Actual expansion

The first call to `derive_deftly_template_Template!`
is expanded according to the `macro_rules!` definition,
resulting in a call to `derive_deftly_engine`:

```rust,ignore
    derive_deftly_engine! {
        { #[derive_deftly(..)] pub struct StructName { .. } }
        [ 1 0 AOPTIONS ]
        ( )
        { TEMPLATE.. }
        ( $crate; [TOPTIONS..] Template; )
        [
          derive_deftly_template_Template2 ( [1 0 AOPTIONS2] () )
          derive_deftly_engine ( . () )
        ]
        [] // ACCUM
    }
```

Implemented in `engine.rs::derive_deftly_engine_func_macro`,
this performs the actual template expansion.

### 4. Chaining to the next macro

`derive_deftly_engine!`
then invokes the next template.

In the above example, it outputs:

```rust,ignore
    derive_deftly_template_Template2! {
        { #[derive_deftly(..)] pub struct StructName { .. } }
        [ 1 0 AOPTIONS2 ]
        ( ) // threaded through from the call site
        [ derive_deftly_engine ( . () ) ]
        [_name Template _meta_used [..] _meta_recog [..]] // ACCUM
    }
```

`ACCUM` accumulates information from successive expansions,
and is actually parsed only in the next step.

#### Used meta attribute syntax

`_meta_used` introduces the meta attributes from the driver,
which were used by this template,
in the following syntax:
```text
  ::VARIANT // entries until next :: are for this variant
  .field    // entries until next :: or . are for this field
  ( // each (..) corresponds to one #[deftly()], and matches its tree
    somemeta(
      value_used =, // value was used
      value_used +, // value was used but in context with a default
      bool_tested ?, // boolean was tested
      , // skip a node (subtree) not used at all
      .. // trailing skipped notes are omitted
    )
  ) // empty trailing groups are omitted
```

So for example this

```rust,ignore
struct S {
  #[adhoc(foo(bar, baz))]
  #[adhoc(wombat, zoo = "Berlin")]
  f: T,
}
```

might correspond to this

```text
_meta [
    // nothing (don't need field name even)
    .f () (, zoo?)
    .f () (wombat?, zoo=)
    .f (foo=(bar?, baz?)) (wombat?, zoo?)
]
```

This representation helps minimise the amount of text
which needs to be passed through all the macro chains,
when only a few attributes are used by each template,
while
still being unambiguous and detecting desynchronisation.

Instead of a `[..]` list, `_meta_used` can also be `*`,
meaning that no checking should be done.

### Recognised meta attribute syntax

`_meta_recog` introduces the meta attributes which
might conceivably be recognised by this template.
This is determined statically, by scanning the template.

The information is used only if there are unused attributes,
to assist with producing good error messages.

`_meta_recog` takes a `[ ]` list containing items like `fmeta(foo(bar))`,
or ``fmeta(foo(bar))?` when recognised only as a boolean,
or ``fmeta(foo(bar))+` when only used as a value (without any boolean tests).

### 5. Reporting unused meta attributes

At the end of the list of chained templates,
is (the driver's version of) `derive_deftly_engine!`.
So after all the templates have been processed,
instead of invoking the next template,
we return directly to the engine:

```rust,ignore
    derive_deftly_engine! {
        { #[derive_deftly(..)] pub struct StructName { .. } }
        .
        ( )
        [] // end of the chain
        [_name Template _meta_used [..] _meta_recog [..] _name Template2 ..]
    }
```

The accumulated parts are all of the form: `KEYWORD DATA`;
`_KEYWORD DATA` for a part which can be safely ignored,
if unrecognised.
`DATA` is a single tt.
Each template's parts start with a `_name` part.
There can also be `error` parts which
appear when the template couldn't be parsed
(which is used to suppress "unrecognised attribute" errors).

The actually supplied attributes are compared with
the union of the used attributes,
and errors are reported for unused ones.

## Implementation approach - ad-hoc macro applications

### 1. `#[derive(Deftly)]` feature for saving struct definitions

Also implemented in `derive.rs::derive_deftly`.

When applied to (e.g.) `pub struct StructName`,
with `#[derive_deftly_adhoc]` specified
generates this

```rust,ignore
    macro_rules! derive_deftly_driver_StructName { {
        { $($template:tt)* }
        { ($orig_dollar:tt) $(future:tt)* }
        $($dpassthrough:tt)*
     } => {
        derive_deftly_engine!{
            { pub struct StructName { /* original struct definition */ } }
            /* no AOPTIONS since there's no derive() application */
            ( )
            { $($template)* }
            $($dpassthrough)*
        }
    } }
```

(Again, the extra `{ }` parts after the driver and template
include space for future expansion.)

In the `pub struct` part every `$` is replaced with `$orig_dollar`,
to use the `$` passed in at the invocation site.
(This is only relevant if the driver contains `$` somehow,
for example in helper attributes.)

### 2. `derive_deftly_adhoc!` function-like proc macro for applying to a template

Implemented in `adhoc.rs::derive_deftly_adhoc`.

When applied like this
```rust,ignore
    derive_deftly_adhoc!{
       StructName TOPTIONS..:
       TEMPLATE..
    }
```

Expands to
```rust,ignore
    derive_deftly_driver_StructName! {
       { TEMPLATE.. }
       { ($) }
       ( crate; [TOPTIONS..] /*no template name*/; )
       []
       [_meta_used *]
    }
```

The literal `$` is there to work around a limitation in `macro_rules!`,
see above.

### 3. Function-like proc macro to do the actual expansion

The result of expanding the above is this:

```rust,ignore
    derive_deftly_engine!{
        { pub struct StructName { /* original struct definition */ } }
        ( )
        { TEMPLATE.. }
        ( crate; [TOPTIONS..] /*no template name*/; )
        []
        [_meta_used *]
    }
```

`derive_deftly_engine` parses `pub struct StructName`,
and implements a bespoke template expander,
whose template syntax resembles the expansion syntax from `macro_rules`.

`crate` is just used as the expansion for `${crate}`.
(For an ad-hoc template, the local crate is correct.)

## Implementation approach - deftly definition modules

### Overall input syntax for modules and `derive_deftly_engine!`

```rust,ignore
    macro! {
	    via_modules [
            {....} // used by `macro`
		    NEXT_MACRO {....} // used by `NEXT_MACRO`
	        ..
			define_derive_deftly {....} DEFWHAT ....
	    ]
        { EARLIER_PREFIX.. }
        { MAIN_PASSTHROUGH.. }
        ( $ EXTRA_PASSTHROUGH.... )
        ....
    }
```

Loosely, the semantics of `derive_deftly_module_MODULE` are:
define a thing, which depends on MODULE's definitions.

Each `derive_deftly_module_MODULE` macro
prepends its own definitions to EARLIER\_PREFIX,
leaving MAIN\_PASSTHROUGH unchanged,
and then calls the next macro in turn.

Eventually `derive_deftly_engine` is reached, with NEXT\_MACROS empty,
and looks at `[ DEFWHAT .... ]` to know what to do.

When a template uses multiple modules,
the module macros are listed in `[ ]` in reverse order,
because the last macro in the list runs last, prepending,
so that its definitions end up first in the output.
Engine comes last because it runs on the "outside",
after all the prepending is done.
The reverse ordering is not strictly necessary -
we could define the macros to *append* and list in forwards order;
it's done this way for future compatibility
with possible other uses of the module system.

EARLIER\_PREFIX has already been dollar-escaped, using `$ orig_dollar`.
MAIN\_PASSTHROUGH has not.

### Module macro

```rust,ignore
    define_derive_deftly_module! {
        DOCS
        [export] MODULE:

        M_DEFINES
    }
```

Expands to:

```rust,ignore
    DOCS
    #[macro_export]
    macro_rules! derive_deftly_module_MODULE { {
        via_modules [
            { $($our_opts:tt)* }
		    $next_macro:path
	        { $($next_opts:tt)* }
			$(rest:tt)*
		]
        { $($predefs:tt)* }
        { $($main:tt)* }
        ( $($extra:tt)* )
        $($ignored:tt)*
    } => {
      $next_macro! {
        via_modules [ { $($next_opts)* } $($rest)* ]
        { M_DEFINES $($predefs)* }
        { $($main)* }
        ( $($extra)* )
      }
    } }
```

In M_DEFINES, dollars have to be turned into `$orig_dollar`.

### Definition of a module-using template

```rust,ignore
    define_derive_deftly! {
        use M1;
        use M2;
        DOCS MyMacro TOPTIONS..: TEMPLATE..
    }
```

Expands to:

```rust,ignore
    derive_deftly_module_M2! {
        via_modules [
		    {}
			derive_deftly_module_M1 {}
			derive_deftly_engine {} define
        ]
        { }
        { DOCS MyMacro TOPTIONS..: TEMPLATE.. }
        ( )
    }
```

Hence:

```rust,ignore
    derive_deftly_module_M1! {
        via_modules [
		    {}
			derive_deftly_engine {} define
        ]
        { M2_DEFINES }
        { DOCS MyMacro TOPTIONS..: TEMPLATE.. }
        ( )
    }
```

Hence:

```rust,ignore
    derive_deftly_engine! {
        via_modules [
		    {} define
	    ]
        { M1_DEFINES M2_DEFINES }
        { DOCS MyMacro TOPTIONS..: TEMPLATE.. }
        ( )
    }
```

Which is then handled as if it were an invocation of `define_derive_deftly!`
without any `use` statements.

The resulting template macro will additionally embody
`{ M1_DEFINES M2_DEFINES }` as `{ IMPORTED_DEFINITIONS.. }`.

### Definition of a module-using module

These are resolved early, so that they are syntax-checked
(and the names of the imported modules resolved) at the definition site:

```rust,ignore
    define_derive_deftly_module! {
        MODULE_DOCS
        [export] MODULE:

        use P1;
        use P2;

        M_DEFINES
    }
```

Expands to:

```rust,ignore
    derive_deftly_module_P2! {
        via_modules [
		    {}
			derive_deftly_module_P1 {}
			derive_deftly_engine {} defmod
		]
        { M_DEFINES }
        { MODULE_DOCS [export] MODULE: }
        ( )
    }
```

Hence:

```rust,ignore
    derive_deftly_module_P1! {
        via_modules [
		    {}
			derive_deftly_engine {} defmod
		]
        { P2_DEFINES M_DEFINES }
        { MODULE_DOCS [export] MODULE: }
        ( )
    }
```

Hence:

```rust,ignore
    derive_deftly_engine {
        via_modules [
		    {} defmod
        ]
        { P1_DEFINES P2_DEFINES M_DEFINES }
        { MODULE_DOCS [export] MODULE: }
        ( )
    }
```

Which is treated as:

```rust,ignore
    define_derive_deftly_module! {
        MODULE_DOCS [export] MODULE:
        P1_DEFINES P2_DEFINES M_DEFINES
    }
```