Skip to main content

m2_syn_macros/
lib.rs

1extern crate proc_macro;
2
3mod quote_m2;
4mod syntax;
5mod tokens;
6
7use proc_macro::TokenStream;
8use std::collections::BTreeSet;
9
10use proc_macro2::{TokenStream as TokenStream2, TokenTree};
11use quote::{ToTokens, format_ident, quote};
12use syn::{Data, DeriveInput, Error, Fields, Member, Result, Type, parse_macro_input, parse_quote};
13
14#[proc_macro]
15pub fn syntax(input: TokenStream) -> TokenStream {
16    match syntax::generate(input.into()) {
17        Ok(expansion) => expansion.combined().into(),
18        Err(error) => error.into_compile_error().into(),
19    }
20}
21
22#[proc_macro]
23/// Constructs an M2 token stream with recursive Rust interpolation.
24///
25/// `$(expression)` emits any value implementing `m2_syn::ToTokens`.
26/// `$[pattern in iterator] { template }` repeats a recursively quoted template
27/// with an explicit Rust `for` loop.
28pub fn quote_m2(input: TokenStream) -> TokenStream {
29    quote_m2::expand(input)
30}
31
32#[proc_macro]
33/// Quotes M2 syntax and parses it as the type inferred at the call site.
34pub fn parse_quote_m2(input: TokenStream) -> TokenStream {
35    let input = TokenStream2::from(input);
36    quote!({
37        let tokens = ::m2_syn::quote_m2!(#input);
38        ::m2_syn::parse1(tokens).expect("parse_quote_m2! input should parse")
39    })
40    .into()
41}
42
43#[proc_macro_derive(Spanned)]
44pub fn derive_spanned(input: TokenStream) -> TokenStream {
45    let input = parse_macro_input!(input as DeriveInput);
46    match expand_spanned(&input) {
47        Ok(expansion) => expansion.into(),
48        Err(error) => error.to_compile_error().into(),
49    }
50}
51
52fn expand_spanned_members(
53    members: impl IntoIterator<Item = (Member, TokenStream2)>,
54) -> TokenStream2 {
55    let members = members.into_iter().collect::<Vec<_>>();
56    let span = quote! {::m2_syn::Spanned::span};
57    if let Some((_, value)) = members
58        .iter()
59        .find(|(member, _)| matches!(member, Member::Named(ident) if ident == "span"))
60    {
61        quote!(#span(#value))
62    } else {
63        let values = members.iter().map(|(_, value)| value);
64        quote! {
65            ::m2_syn::Span::join_all(
66                [#(::m2_syn::Spanned::span(#values)),*]
67            )
68        }
69    }
70}
71
72fn expand_spanned(input: &DeriveInput) -> Result<TokenStream2> {
73    let name = &input.ident;
74    let span_ty = quote! {::m2_syn::Span};
75    let mut generics = input.generics.clone();
76    let type_parameters = input
77        .generics
78        .type_params()
79        .map(|parameter| parameter.ident.to_string())
80        .collect::<BTreeSet<_>>();
81    for ty in spanned_field_types(input)
82        .into_iter()
83        .filter(|ty| type_mentions_parameter(ty, &type_parameters))
84    {
85        generics
86            .make_where_clause()
87            .predicates
88            .push(parse_quote!(#ty: ::m2_syn::Spanned));
89    }
90    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
91    let body = match &input.data {
92        Data::Struct(data) => {
93            let members = data.fields.members().map(|member| {
94                let value = quote!(&self.#member);
95                (member, value)
96            });
97            expand_spanned_members(members)
98        }
99        Data::Enum(data) => {
100            let arms = data
101                .variants
102                .iter()
103                .map(|variant| {
104                    let variant_name = &variant.ident;
105                    let members = variant.fields.members().collect::<Vec<_>>();
106                    let bindings = members
107                        .iter()
108                        .enumerate()
109                        .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
110                        .collect::<Vec<_>>();
111                    let values = bindings.iter().map(|binding| quote!(#binding));
112                    let span = expand_spanned_members(members.iter().cloned().zip(values));
113
114                    match &variant.fields {
115                        Fields::Named(_) => {
116                            let fields = members
117                                .iter()
118                                .zip(&bindings)
119                                .map(|(member, binding)| quote!(#member: #binding));
120                            quote! {
121                                Self::#variant_name { #(#fields),* } => #span
122                            }
123                        }
124                        Fields::Unnamed(_) => quote! {
125                            Self::#variant_name(#(#bindings),*) => #span
126                        },
127                        Fields::Unit => quote! {
128                            Self::#variant_name => #span
129                        },
130                    }
131                })
132                .collect::<Vec<_>>();
133            let empty_fallback = data
134                .variants
135                .is_empty()
136                .then(|| quote!(_ => #span_ty::detached()));
137            quote! {
138                match self {
139                    #(#arms,)*
140                    #empty_fallback
141                }
142            }
143        }
144        Data::Union(_) => {
145            return Err(Error::new_spanned(
146                name,
147                "Spanned cannot be derived for unions",
148            ));
149        }
150    };
151
152    Ok(quote! {
153        impl #impl_generics
154            ::m2_syn::Spanned for #name #type_generics #where_clause
155        {
156            fn span(&self) -> #span_ty {
157                #body
158            }
159        }
160    })
161}
162
163fn type_mentions_parameter(ty: &Type, parameters: &BTreeSet<String>) -> bool {
164    fn stream_mentions_parameter(stream: TokenStream2, parameters: &BTreeSet<String>) -> bool {
165        stream.into_iter().any(|token| match token {
166            TokenTree::Ident(ident) => parameters.contains(&ident.to_string()),
167            TokenTree::Group(group) => stream_mentions_parameter(group.stream(), parameters),
168            TokenTree::Punct(_) | TokenTree::Literal(_) => false,
169        })
170    }
171
172    stream_mentions_parameter(ty.to_token_stream(), parameters)
173}
174
175fn spanned_field_types(input: &DeriveInput) -> Vec<&Type> {
176    fn selected(fields: &Fields) -> Vec<&Type> {
177        fields
178            .iter()
179            .find(|field| field.ident.as_ref().is_some_and(|ident| ident == "span"))
180            .map_or_else(
181                || fields.iter().map(|field| &field.ty).collect(),
182                |field| vec![&field.ty],
183            )
184    }
185
186    match &input.data {
187        Data::Struct(data) => selected(&data.fields),
188        Data::Enum(data) => data
189            .variants
190            .iter()
191            .flat_map(|variant| selected(&variant.fields))
192            .collect(),
193        Data::Union(_) => Vec::new(),
194    }
195}