structio_derive/lib.rs
1//! The optional `#[derive(Structio)]` for [structio](https://docs.rs/structio).
2//!
3//! Enable structio's `derive` feature and write `#[derive(structio::Structio)]`
4//! rather than depending on this crate by name. The derive is a front end to
5//! the declaration macros: it reads the type and emits the `object!`,
6//! `array!`, `unit_enum!` or `tagged_enum!` invocation you would have written,
7//! with the attributes translated to that macro's syntax. The impls, the key
8//! map, the required-field mask and every rule about what is accepted are
9//! the macros' own, so a derived type and a declared type behave identically.
10//!
11//! The attributes and what each expands to are documented at
12//! [`docs/derive.md`](https://github.com/stephenberry/structio/blob/main/docs/derive.md).
13//!
14//! This crate has no dependencies. It walks `proc_macro::TokenStream` itself,
15//! which is a few hundred lines for the shapes it has to recognize and keeps
16//! the derive's own build under a second.
17
18mod attr;
19mod cursor;
20mod emit;
21mod parse;
22
23use proc_macro::{Delimiter, Group, Ident, Literal, Punct, Spacing, Span, TokenStream, TokenTree};
24
25/// A refusal, pointed at the token that caused it.
26pub(crate) struct Error {
27 span: Span,
28 message: String,
29}
30
31impl Error {
32 pub(crate) fn new(span: Span, message: impl Into<String>) -> Self {
33 Error {
34 span,
35 message: message.into(),
36 }
37 }
38
39 /// `::core::compile_error!("message")` with every token at the span, so
40 /// the diagnostic lands on the user's attribute or field and not on the
41 /// derive.
42 fn into_compile_error(self) -> TokenStream {
43 let span = self.span;
44 let at = |mut tt: TokenTree| {
45 tt.set_span(span);
46 tt
47 };
48 let punct = |ch, spacing| at(TokenTree::Punct(Punct::new(ch, spacing)));
49 let mut message = Literal::string(&self.message);
50 message.set_span(span);
51 let tokens = [
52 punct(':', Spacing::Joint),
53 punct(':', Spacing::Alone),
54 at(TokenTree::Ident(Ident::new("core", span))),
55 punct(':', Spacing::Joint),
56 punct(':', Spacing::Alone),
57 at(TokenTree::Ident(Ident::new("compile_error", span))),
58 punct('!', Spacing::Alone),
59 at(TokenTree::Group(Group::new(
60 Delimiter::Brace,
61 TokenStream::from(TokenTree::Literal(message)),
62 ))),
63 ];
64 tokens.into_iter().collect()
65 }
66}
67
68pub(crate) type Result<T> = std::result::Result<T, Error>;
69
70/// Declare a struct's or enum's schema from its definition.
71///
72/// See [structio's documentation](https://docs.rs/structio) for the attributes.
73/// In short: `#[structio(rename_all = "camelCase")]`, `#[structio(tag =
74/// "kind")]`, `#[structio(array)]`, `#[structio(transparent)]`,
75/// `#[structio(json)]`, `#[structio(beve)]` or `#[structio(write_only)]` on
76/// the type; `#[structio(rename = "key")]`, `#[structio(alias = "key")]`,
77/// `#[structio(skip)]`, `#[structio(required)]` and `#[structio(with =
78/// "Adapter")]` on a field; `#[structio(rename = "name")]` and
79/// `#[structio(alias = "name")]` on a variant.
80#[proc_macro_derive(Structio, attributes(structio))]
81pub fn derive_structio(input: TokenStream) -> TokenStream {
82 match parse::parse(input).and_then(|input| emit::expand(&input)) {
83 Ok(tokens) => tokens,
84 Err(error) => error.into_compile_error(),
85 }
86}