structio-derive 0.8.0

The optional #[derive(Structio)] for structio. Enable structio's `derive` feature rather than depending on this crate directly.
Documentation
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
//! From the derive's input to the shape of the type.
//!
//! Only what the declaration macros need is kept: the name, the generics with
//! their bounds, each field's name, each variant's name and how many values
//! it carries, and the `#[structio(..)]` attributes on all of them. Field
//! types are stepped over, since the macros never see them either: they read
//! a field through `self.name` and let the compiler find the type. A tuple
//! struct's fields are read the same way, through `self.0`, so a position is
//! a name here like any other.

use proc_macro::{Delimiter, Ident, Literal, Span, TokenStream, TokenTree};

use crate::cursor::{Cursor, find_punct, split_commas};
use crate::{Error, Result};

pub(crate) struct Input {
    pub(crate) attrs: Vec<Meta>,
    pub(crate) name: Ident,
    pub(crate) generics: Vec<Param>,
    pub(crate) shape: Shape,
}

/// One generic parameter, with the bounds it was declared with and any a
/// `where` clause added.
pub(crate) enum Param {
    /// The two tokens of `'a`, then the bounds after its `:`.
    Lifetime {
        name: [TokenTree; 2],
        bounds: Vec<TokenTree>,
    },
    Type {
        name: Ident,
        bounds: Vec<TokenTree>,
    },
    /// `const N: usize`, kept verbatim for the impl generics.
    Const {
        name: Ident,
        decl: Vec<TokenTree>,
    },
}

pub(crate) enum Shape {
    Struct(Vec<Field>),
    Enum(Vec<Variant>),
}

pub(crate) struct Field {
    pub(crate) attrs: Vec<Meta>,
    pub(crate) name: FieldName,
}

/// How a field is reached, which is the token the declaration macros take.
pub(crate) enum FieldName {
    /// `a: T`, read through `self.a`, and an object's key unless renamed.
    Named(Ident),
    /// A tuple struct's position, read through `self.0`. It carries the span
    /// of the type it stands for, since that is what the author wrote.
    Index(Literal),
}

impl FieldName {
    pub(crate) fn token(&self) -> TokenTree {
        match self {
            FieldName::Named(name) => TokenTree::Ident(name.clone()),
            FieldName::Index(index) => TokenTree::Literal(index.clone()),
        }
    }
}

pub(crate) struct Variant {
    pub(crate) attrs: Vec<Meta>,
    pub(crate) name: Ident,
    pub(crate) payload: Payload,
}

pub(crate) enum Payload {
    Unit,
    /// `Variant(T)`, the one shape the enum macros take a value in.
    One,
    /// `Variant(A, B)`, spanned at the parentheses.
    Many(Span),
    /// `Variant { a: A }`, spanned at the braces.
    Named(Span),
}

/// One entry of a `#[structio(..)]` list: `name` or `name = "value"`.
pub(crate) struct Meta {
    pub(crate) name: Ident,
    pub(crate) value: Option<Literal>,
}

pub(crate) fn parse(input: TokenStream) -> Result<Input> {
    let mut c = Cursor::new(input, Span::call_site());
    let attrs = attributes(&mut c)?;
    visibility(&mut c);

    let keyword = c.expect_ident("`struct` or `enum`")?;
    let is_enum = match keyword.to_string().as_str() {
        "struct" => false,
        "enum" => true,
        "union" => {
            return Err(Error::new(
                keyword.span(),
                "a union has no schema: which field holds the value is not \
                 something the bytes can say. Derive on a struct or an enum.",
            ));
        }
        _ => return Err(Error::new(keyword.span(), "expected `struct` or `enum`")),
    };
    let name = c.expect_ident("the type's name")?;

    let mut generics = if c.eat_punct('<').is_some() {
        params(c.until_close_angle()?)?
    } else {
        Vec::new()
    };

    // A tuple struct's fields come before its `where` clause and a named
    // struct's after, so which is read first depends on which this is.
    let tuple = if is_enum {
        None
    } else {
        c.eat_group(Delimiter::Parenthesis)
    };

    if let Some(body) = &tuple
        && body.stream().is_empty()
    {
        return Err(Error::new(
            body.span(),
            "a tuple struct with no fields has nothing to put on the wire",
        ));
    }

    if !is_enum && tuple.is_none() && !c.peek_group(Delimiter::Brace) && !c.peek_ident("where") {
        return Err(Error::new(
            c.span(),
            "a unit struct has no fields to put on the wire",
        ));
    }

    if let Some(kw) = c.eat_ident("where") {
        where_clause(&mut c, &mut generics, kw.span())?;
    }

    let shape = match &tuple {
        Some(body) => Shape::Struct(tuple_fields(body)?),
        None => {
            let body = c.expect_group(Delimiter::Brace, "the type's body")?;
            if is_enum {
                Shape::Enum(variants(&body)?)
            } else {
                Shape::Struct(fields(&body)?)
            }
        }
    };

    Ok(Input {
        attrs,
        name,
        generics,
        shape,
    })
}

/// The `#[structio(..)]` attributes in a run of outer attributes, in order.
/// Every other attribute is stepped over.
fn attributes(c: &mut Cursor) -> Result<Vec<Meta>> {
    let mut metas = Vec::new();
    while c.eat_punct('#').is_some() {
        let group = c.expect_group(Delimiter::Bracket, "an attribute")?;
        let mut inner = Cursor::inside(&group);
        if inner.eat_ident("structio").is_none() {
            continue;
        }
        match inner.eat_group(Delimiter::Parenthesis) {
            Some(list) if inner.is_empty() => metas.extend(meta_list(&list)?),
            _ => {
                return Err(Error::new(
                    group.span(),
                    "`#[structio]` takes a parenthesized list: \
                     `#[structio(rename = \"key\")]`",
                ));
            }
        }
    }
    Ok(metas)
}

fn meta_list(list: &proc_macro::Group) -> Result<Vec<Meta>> {
    let mut c = Cursor::inside(list);
    let mut metas = Vec::new();
    while !c.is_empty() {
        let name = c.expect_ident("an attribute name")?;
        let value = if c.eat_punct('=').is_some() {
            match c.next() {
                Some(TokenTree::Literal(lit)) => Some(lit),
                Some(other) => {
                    return Err(Error::new(
                        other.span(),
                        format!("`{name}` takes a string literal: `{name} = \"..\"`"),
                    ));
                }
                None => {
                    return Err(Error::new(
                        c.span(),
                        format!("`{name}` takes a string literal: `{name} = \"..\"`"),
                    ));
                }
            }
        } else {
            None
        };
        if let Some(tt) = c.peek()
            && !matches!(tt, TokenTree::Punct(p) if p.as_char() == ',')
        {
            return Err(Error::new(
                tt.span(),
                format!("expected `,` or the end of the list after `{name}`"),
            ));
        }
        c.eat_punct(',');
        metas.push(Meta { name, value });
    }
    Ok(metas)
}

/// Step over `pub`, `pub(crate)`, `pub(in path)`.
fn visibility(c: &mut Cursor) {
    if c.eat_ident("pub").is_some() {
        c.eat_group(Delimiter::Parenthesis);
    }
}

/// The parameters between a type's `<` and `>`, each with its own bounds and
/// without its default.
fn params(tokens: Vec<TokenTree>) -> Result<Vec<Param>> {
    let mut out = Vec::new();
    for piece in split_commas(tokens) {
        // Attributes on a generic parameter are legal and mean nothing here.
        let mut piece = piece;
        while matches!(piece.first(), Some(TokenTree::Punct(p)) if p.as_char() == '#') {
            piece.drain(..2);
        }
        let Some(first) = piece.first().cloned() else {
            continue;
        };
        let without_default = match find_punct(&piece, '=') {
            Some(eq) => piece[..eq].to_vec(),
            None => piece,
        };
        match first {
            TokenTree::Punct(p) if p.as_char() == '\'' => {
                let (name, rest) = without_default.split_at(2);
                let bounds = match rest.first() {
                    Some(TokenTree::Punct(colon)) if colon.as_char() == ':' => rest[1..].to_vec(),
                    _ => Vec::new(),
                };
                out.push(Param::Lifetime {
                    name: [name[0].clone(), name[1].clone()],
                    bounds,
                });
            }
            TokenTree::Ident(kw) if kw.to_string() == "const" => {
                let name = match without_default.get(1) {
                    Some(TokenTree::Ident(n)) => n.clone(),
                    _ => return Err(Error::new(kw.span(), "expected a const parameter name")),
                };
                out.push(Param::Const {
                    name,
                    decl: without_default,
                });
            }
            TokenTree::Ident(name) => {
                let bounds = match without_default.get(1) {
                    Some(TokenTree::Punct(colon)) if colon.as_char() == ':' => {
                        without_default[2..].to_vec()
                    }
                    _ => Vec::new(),
                };
                out.push(Param::Type { name, bounds });
            }
            other => return Err(Error::new(other.span(), "expected a generic parameter")),
        }
    }
    Ok(out)
}

/// Fold a `where` clause into the parameters it bounds. The declaration
/// macros take bounds inline and nothing else, so a predicate on anything but
/// a parameter of the type has nowhere to go.
fn where_clause(c: &mut Cursor, generics: &mut [Param], at: Span) -> Result<()> {
    let mut tokens = Vec::new();
    while let Some(tt) = c.peek() {
        // A named struct's clause ends at its body and a tuple struct's at the
        // `;` that closes the declaration.
        if matches!(tt, TokenTree::Group(g) if g.delimiter() == Delimiter::Brace)
            || matches!(tt, TokenTree::Punct(p) if p.as_char() == ';')
        {
            break;
        }
        tokens.push(tt.clone());
        c.next();
    }
    if tokens.is_empty() {
        return Err(Error::new(at, "expected a predicate after `where`"));
    }
    for predicate in split_commas(tokens) {
        let is_lifetime =
            matches!(predicate.first(), Some(TokenTree::Punct(p)) if p.as_char() == '\'');
        let head = if is_lifetime { 2 } else { 1 };
        let target = match (&predicate[..], predicate.get(head)) {
            ([TokenTree::Ident(id), ..], Some(TokenTree::Punct(colon)))
                if !is_lifetime && colon.as_char() == ':' =>
            {
                generics.iter_mut().find(|p| matches!(p, Param::Type { name, .. } if name.to_string() == id.to_string()))
            }
            ([_, TokenTree::Ident(id), ..], Some(TokenTree::Punct(colon)))
                if is_lifetime && colon.as_char() == ':' =>
            {
                generics.iter_mut().find(|p| matches!(p, Param::Lifetime { name, .. } if name[1].to_string() == id.to_string()))
            }
            _ => None,
        };
        let Some(param) = target else {
            return Err(Error::new(
                predicate[0].span(),
                "this predicate cannot be moved onto a parameter. The \
                 declaration macros take bounds inline, so `where` may bound \
                 the type's own parameters, as `T: Bound` or `'a: 'b`, and \
                 nothing else",
            ));
        };
        let extra = predicate[head + 1..].to_vec();
        let bounds = match param {
            Param::Type { bounds, .. } | Param::Lifetime { bounds, .. } => bounds,
            Param::Const { .. } => unreachable!("a const parameter is never matched"),
        };
        if !bounds.is_empty() {
            bounds.push(TokenTree::Punct(proc_macro::Punct::new(
                '+',
                proc_macro::Spacing::Alone,
            )));
        }
        bounds.extend(extra);
    }
    Ok(())
}

fn fields(body: &proc_macro::Group) -> Result<Vec<Field>> {
    let mut c = Cursor::inside(body);
    let mut out = Vec::new();
    while !c.is_empty() {
        let attrs = attributes(&mut c)?;
        visibility(&mut c);
        let name = c.expect_ident("a field name")?;
        c.expect_punct(':', "`:` after the field name")?;
        c.until_comma();
        out.push(Field {
            attrs,
            name: FieldName::Named(name),
        });
    }
    Ok(out)
}

/// A tuple struct's fields, whose names are their positions.
///
/// The index is given the span of the type it stands for, so an error the
/// derive reports about a field lands on what the author wrote rather than on
/// a number they did not.
fn tuple_fields(body: &proc_macro::Group) -> Result<Vec<Field>> {
    let mut c = Cursor::inside(body);
    let mut out = Vec::new();
    while !c.is_empty() {
        let attrs = attributes(&mut c)?;
        visibility(&mut c);
        let at = c.span();
        if c.until_comma().is_empty() {
            return Err(Error::new(at, "expected a field type"));
        }
        let mut index = Literal::usize_unsuffixed(out.len());
        index.set_span(at);
        out.push(Field {
            attrs,
            name: FieldName::Index(index),
        });
    }
    Ok(out)
}

fn variants(body: &proc_macro::Group) -> Result<Vec<Variant>> {
    let mut c = Cursor::inside(body);
    let mut out = Vec::new();
    while !c.is_empty() {
        let attrs = attributes(&mut c)?;
        visibility(&mut c);
        let name = c.expect_ident("a variant name")?;
        let payload = if let Some(g) = c.eat_group(Delimiter::Parenthesis) {
            let count = split_commas(g.stream().into_iter().collect()).len();
            match count {
                1 => Payload::One,
                _ => Payload::Many(g.span()),
            }
        } else if let Some(g) = c.eat_group(Delimiter::Brace) {
            Payload::Named(g.span())
        } else {
            Payload::Unit
        };
        // A discriminant is a Rust-side number and says nothing about the
        // wire, where a variant is its name.
        if c.eat_punct('=').is_some() {
            c.until_comma();
        } else {
            c.eat_punct(',');
        }
        out.push(Variant {
            attrs,
            name,
            payload,
        });
    }
    Ok(out)
}