Skip to main content

m2_syn_macros/
lib.rs

1//! Procedural macros backing the `m2-syn` typed syntax API.
2#![warn(missing_docs)]
3
4extern crate proc_macro;
5
6mod quote_m2;
7mod roles;
8mod syntax;
9mod tokens;
10
11use std::collections::BTreeSet;
12
13use proc_macro2::{TokenStream, TokenTree};
14use quote::{ToTokens, format_ident, quote};
15use syn::parse::{Parse, ParseStream};
16use syn::{
17    Data, DeriveInput, Error, Fields, Member, Result, Token, Type, parse_macro_input, parse_quote,
18};
19
20fn base_crate() -> TokenStream {
21    quote!(::m2_syn)
22}
23
24fn expand_syntax(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
25    let expansion = match syntax::generate(input.into()) {
26        Ok(expansion) => expansion,
27        Err(error) => return error.into_compile_error().into(),
28    };
29    expansion.into()
30}
31
32/// Declares typed syntax nodes and token atoms.
33#[proc_macro]
34pub fn syntax(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
35    expand_syntax(input)
36}
37
38/// Resolves a token spelling to the type `syntax!` generates for it.
39///
40/// This is what `m2_syn::Token![…]` forwards to. The mapping is pure
41/// computation over the concatenated M2 spelling, so whitespace and the
42/// boundaries between Rust tokens and string fragments have no effect. It
43/// needs no access to the schema and nothing has to be generated for it.
44#[proc_macro]
45pub fn token(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
46    let base = base_crate();
47    match tokens::token_type_name(&input.into()) {
48        Ok(name) => quote!(#base::nodes::#name).into(),
49        Err(error) => error.into_compile_error().into(),
50    }
51}
52/// Constructs an M2 token stream with recursive Rust interpolation.
53///
54/// `$(expression)` emits any value implementing `m2_syn::Lex`.
55/// `$[pattern in iterator] { template }` repeats a recursively quoted template
56/// with an explicit Rust `for` loop.
57#[proc_macro]
58pub fn quote_m2(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
59    quote_m2::expand(input.into()).into()
60}
61
62/// Quotes M2 syntax and parses it as the type inferred at the call site.
63#[proc_macro]
64pub fn parse_quote_m2(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
65    let input = TokenStream::from(input);
66    let base = base_crate();
67    quote!({
68        let tokens = #base::quote_m2!(#input);
69        #base::parse2(tokens).expect("parse_quote_m2! input should parse")
70    })
71    .into()
72}
73
74/// Derives `m2_syn::Spanned` by joining the spans of a value's fields.
75#[proc_macro_derive(Spanned)]
76pub fn derive_spanned(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
77    let input = parse_macro_input!(input as DeriveInput);
78    match expand_spanned(&input) {
79        Ok(expansion) => expansion.into(),
80        Err(error) => error.to_compile_error().into(),
81    }
82}
83
84/// Generates the child-walking implementation for one expanded syntax type.
85#[doc(hidden)]
86#[proc_macro_derive(__SyntaxWalk)]
87pub fn derive_syntax_walk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
88    let input = parse_macro_input!(input as DeriveInput);
89    expand_syntax_walk(&input).into()
90}
91
92struct SyntaxTokenWalk {
93    name: syn::Ident,
94    ty: Type,
95}
96
97impl Parse for SyntaxTokenWalk {
98    fn parse(input: ParseStream<'_>) -> Result<Self> {
99        let name = input.parse()?;
100        input.parse::<Token![,]>()?;
101        let ty = input.parse()?;
102        Ok(Self { name, ty })
103    }
104}
105
106/// Generates the leaf-walking implementation for one syntax token type.
107#[doc(hidden)]
108#[proc_macro]
109pub fn __syntax_token_walk(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
110    let SyntaxTokenWalk { name, ty } = parse_macro_input!(input as SyntaxTokenWalk);
111    expand_syntax_token_walk(&name, &ty).into()
112}
113
114fn expand_syntax_token_walk(name: &syn::Ident, ty: &Type) -> TokenStream {
115    let snake = syntax::to_snake_case(&name.to_string());
116    let visit_method = format_ident!("visit_{snake}");
117    let visit_mut_method = format_ident!("visit_{snake}_mut");
118    let fold_method = format_ident!("fold_{snake}");
119    quote! {
120        impl __walk::WalkChildren for #ty {
121            fn walk_children<'ast, V>(&'ast self, _visitor: &mut V)
122            where
123                V: visit::Visit<'ast> + ?Sized,
124            {}
125        }
126
127        impl __walk::WalkChildrenMut for #ty {
128            fn walk_children_mut<V>(&mut self, _visitor: &mut V)
129            where
130                V: visit_mut::VisitMut + ?Sized,
131            {}
132        }
133
134        impl __walk::FoldChildren for #ty {
135            fn fold_children<F>(self, _folder: &mut F) -> Self
136            where
137                F: fold::Fold + ?Sized,
138            {
139                self
140            }
141        }
142
143        impl __walk::VisitValue for #ty {
144            fn visit_value<'ast, V>(&'ast self, visitor: &mut V)
145            where
146                V: visit::Visit<'ast> + ?Sized,
147            {
148                visitor.#visit_method(self);
149            }
150        }
151
152        impl __walk::VisitValueMut for #ty {
153            fn visit_value_mut<V>(&mut self, visitor: &mut V)
154            where
155                V: visit_mut::VisitMut + ?Sized,
156            {
157                visitor.#visit_mut_method(self);
158            }
159        }
160
161        impl __walk::FoldValue for #ty {
162            fn fold_value<F>(self, folder: &mut F) -> Self
163            where
164                F: fold::Fold + ?Sized,
165            {
166                folder.#fold_method(self)
167            }
168        }
169
170    }
171}
172
173struct SyntaxWalkFunction {
174    traversal: syn::Ident,
175    name: syn::Ident,
176    ty: Type,
177}
178
179impl Parse for SyntaxWalkFunction {
180    fn parse(input: ParseStream<'_>) -> Result<Self> {
181        let traversal = input.parse()?;
182        input.parse::<Token![,]>()?;
183        let name = input.parse()?;
184        input.parse::<Token![,]>()?;
185        let ty = input.parse()?;
186        Ok(Self {
187            traversal,
188            name,
189            ty,
190        })
191    }
192}
193
194/// Generates one free function inside a traversal module.
195#[doc(hidden)]
196#[proc_macro]
197pub fn __syntax_walk_function(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
198    let input = parse_macro_input!(input as SyntaxWalkFunction);
199    expand_syntax_walk_function(&input).into()
200}
201
202fn expand_syntax_walk_function(input: &SyntaxWalkFunction) -> TokenStream {
203    let name = &input.name;
204    let ty = &input.ty;
205    let snake = syntax::to_snake_case(&name.to_string());
206    match input.traversal.to_string().as_str() {
207        "visit" => {
208            let method = format_ident!("visit_{snake}");
209            quote! {
210                #[doc = concat!("Walks the children of a `", stringify!(#name), "` node.")]
211                pub fn #method<'ast, V>(visitor: &mut V, node: &'ast #ty)
212                where
213                    V: Visit<'ast> + ?Sized,
214                {
215                    super::__walk::WalkChildren::walk_children(node, visitor);
216                }
217            }
218        }
219        "visit_mut" => {
220            let method = format_ident!("visit_{snake}_mut");
221            quote! {
222                #[doc = concat!("Walks the mutable children of a `", stringify!(#name), "` node.")]
223                pub fn #method<V>(visitor: &mut V, node: &mut #ty)
224                where
225                    V: VisitMut + ?Sized,
226                {
227                    super::__walk::WalkChildrenMut::walk_children_mut(node, visitor);
228                }
229            }
230        }
231        "fold" => {
232            let method = format_ident!("fold_{snake}");
233            quote! {
234                #[doc = concat!("Folds the children of a `", stringify!(#name), "` node.")]
235                pub fn #method<F>(folder: &mut F, node: #ty) -> #ty
236                where
237                    F: Fold + ?Sized,
238                {
239                    super::__walk::FoldChildren::fold_children(node, folder)
240                }
241            }
242        }
243        _ => Error::new_spanned(&input.traversal, "expected `visit`, `visit_mut`, or `fold`")
244            .to_compile_error(),
245    }
246}
247
248fn is_syntax_storage(member: &Member) -> bool {
249    matches!(
250        member,
251        Member::Named(name)
252            if matches!(
253                name.to_string().as_str(),
254                "span"
255                    | "raw"
256                    | "package_qualifier"
257                    | "index_qualifier"
258                    | "leading_trivia"
259                    | "trailing_trivia"
260                    | "delimiter"
261                    | "comments"
262            )
263    )
264}
265
266fn walk_struct_fields(fields: &Fields, mutable: bool) -> TokenStream {
267    let calls = fields
268        .members()
269        .filter(|member| !is_syntax_storage(member))
270        .map(|member| {
271            if mutable {
272                quote!(__walk::VisitValueMut::visit_value_mut(&mut self.#member, visitor);)
273            } else {
274                quote!(__walk::VisitValue::visit_value(&self.#member, visitor);)
275            }
276        });
277    quote!(#(#calls)*)
278}
279
280fn walk_enum_fields(input: &DeriveInput, mutable: bool) -> TokenStream {
281    let Data::Enum(data) = &input.data else {
282        unreachable!("enum walking requires an enum")
283    };
284    let arms = data.variants.iter().map(|variant| {
285        let name = &variant.ident;
286        let members = variant.fields.members().collect::<Vec<_>>();
287        let bindings = members
288            .iter()
289            .enumerate()
290            .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
291            .collect::<Vec<_>>();
292        let selected = members
293            .iter()
294            .zip(&bindings)
295            .filter(|(member, _)| !is_syntax_storage(member));
296        let calls = selected.map(|(_, binding)| {
297            if mutable {
298                quote!(__walk::VisitValueMut::visit_value_mut(#binding, visitor);)
299            } else {
300                quote!(__walk::VisitValue::visit_value(#binding, visitor);)
301            }
302        });
303        match &variant.fields {
304            Fields::Named(_) => {
305                let patterns = members
306                    .iter()
307                    .zip(&bindings)
308                    .map(|(member, binding)| quote!(#member: #binding));
309                quote!(Self::#name { #(#patterns),* } => { #(#calls)* })
310            }
311            Fields::Unnamed(_) => quote!(Self::#name(#(#bindings),*) => { #(#calls)* }),
312            Fields::Unit => quote!(Self::#name => {}),
313        }
314    });
315    quote!(match self { #(#arms,)* })
316}
317
318fn fold_struct(input: &DeriveInput) -> TokenStream {
319    let Data::Struct(data) = &input.data else {
320        unreachable!("struct folding requires a struct")
321    };
322    let name = &input.ident;
323    let members = data.fields.members().collect::<Vec<_>>();
324    let bindings = members
325        .iter()
326        .enumerate()
327        .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
328        .collect::<Vec<_>>();
329    let values = members.iter().zip(&bindings).map(|(member, binding)| {
330        if is_syntax_storage(member) {
331            quote!(#binding)
332        } else {
333            quote!(__walk::FoldValue::fold_value(#binding, folder))
334        }
335    });
336    match &data.fields {
337        Fields::Named(_) => {
338            let patterns = members
339                .iter()
340                .zip(&bindings)
341                .map(|(member, binding)| quote!(#member: #binding));
342            let fields = members
343                .iter()
344                .zip(values)
345                .map(|(member, value)| quote!(#member: #value));
346            quote! {
347                let #name { #(#patterns),* } = self;
348                #name { #(#fields),* }
349            }
350        }
351        Fields::Unnamed(_) => quote! {
352            let #name(#(#bindings),*) = self;
353            #name(#(#values),*)
354        },
355        Fields::Unit => quote!(self),
356    }
357}
358
359fn fold_enum(input: &DeriveInput) -> TokenStream {
360    let Data::Enum(data) = &input.data else {
361        unreachable!("enum folding requires an enum")
362    };
363    let arms = data.variants.iter().map(|variant| {
364        let name = &variant.ident;
365        let members = variant.fields.members().collect::<Vec<_>>();
366        let bindings = members
367            .iter()
368            .enumerate()
369            .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
370            .collect::<Vec<_>>();
371        let values = members.iter().zip(&bindings).map(|(member, binding)| {
372            if is_syntax_storage(member) {
373                quote!(#binding)
374            } else {
375                quote!(__walk::FoldValue::fold_value(#binding, folder))
376            }
377        });
378        match &variant.fields {
379            Fields::Named(_) => {
380                let patterns = members
381                    .iter()
382                    .zip(&bindings)
383                    .map(|(member, binding)| quote!(#member: #binding));
384                let fields = members
385                    .iter()
386                    .zip(values)
387                    .map(|(member, value)| quote!(#member: #value));
388                quote!(Self::#name { #(#patterns),* } => Self::#name { #(#fields),* })
389            }
390            Fields::Unnamed(_) => {
391                quote!(Self::#name(#(#bindings),*) => Self::#name(#(#values),*))
392            }
393            Fields::Unit => quote!(Self::#name => Self::#name),
394        }
395    });
396    quote!(match self { #(#arms,)* })
397}
398
399fn expand_syntax_walk(input: &DeriveInput) -> TokenStream {
400    let name = &input.ident;
401    if !input.generics.params.is_empty() {
402        return Error::new_spanned(input, "syntax walking requires a concrete syntax type")
403            .to_compile_error();
404    }
405    let snake = syntax::to_snake_case(&name.to_string());
406    let visit_method = format_ident!("visit_{snake}");
407    let visit_mut_method = format_ident!("visit_{snake}_mut");
408    let fold_method = format_ident!("fold_{snake}");
409    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
410    let visit = match &input.data {
411        Data::Struct(data) => walk_struct_fields(&data.fields, false),
412        Data::Enum(_) => walk_enum_fields(input, false),
413        Data::Union(_) => {
414            return Error::new_spanned(input, "syntax walking does not support unions")
415                .to_compile_error();
416        }
417    };
418    let visit_mut = match &input.data {
419        Data::Struct(data) => walk_struct_fields(&data.fields, true),
420        Data::Enum(_) => walk_enum_fields(input, true),
421        Data::Union(_) => unreachable!(),
422    };
423    let fold = match &input.data {
424        Data::Struct(_) => fold_struct(input),
425        Data::Enum(_) => fold_enum(input),
426        Data::Union(_) => unreachable!(),
427    };
428    quote! {
429        impl #impl_generics __walk::WalkChildren for #name #type_generics #where_clause {
430            fn walk_children<'ast, V>(&'ast self, visitor: &mut V)
431            where
432                V: visit::Visit<'ast> + ?Sized,
433            {
434                #visit
435            }
436        }
437
438        impl #impl_generics __walk::WalkChildrenMut for #name #type_generics #where_clause {
439            fn walk_children_mut<V>(&mut self, visitor: &mut V)
440            where
441                V: visit_mut::VisitMut + ?Sized,
442            {
443                #visit_mut
444            }
445        }
446
447        impl #impl_generics __walk::FoldChildren for #name #type_generics #where_clause {
448            fn fold_children<F>(self, folder: &mut F) -> Self
449            where
450                F: fold::Fold + ?Sized,
451            {
452                #fold
453            }
454        }
455
456        impl __walk::VisitValue for #name {
457            fn visit_value<'ast, V>(&'ast self, visitor: &mut V)
458            where
459                V: visit::Visit<'ast> + ?Sized,
460            {
461                visitor.#visit_method(self);
462            }
463        }
464
465        impl __walk::VisitValueMut for #name {
466            fn visit_value_mut<V>(&mut self, visitor: &mut V)
467            where
468                V: visit_mut::VisitMut + ?Sized,
469            {
470                visitor.#visit_mut_method(self);
471            }
472        }
473
474        impl __walk::FoldValue for #name {
475            fn fold_value<F>(self, folder: &mut F) -> Self
476            where
477                F: fold::Fold + ?Sized,
478            {
479                folder.#fold_method(self)
480            }
481        }
482
483    }
484}
485
486fn expand_spanned_members(members: impl IntoIterator<Item = (Member, TokenStream)>) -> TokenStream {
487    let members = members.into_iter().collect::<Vec<_>>();
488    let base = base_crate();
489    let span = quote! {#base::Spanned::try_span};
490    if members.is_empty() {
491        return quote!(None);
492    }
493    if let Some((_, value)) = members
494        .iter()
495        .find(|(member, _)| matches!(member, Member::Named(ident) if ident == "span"))
496    {
497        quote!(#span(#value))
498    } else {
499        let values = members.iter().map(|(_, value)| value);
500        quote! {
501            #base::Span::join_all(
502                [#(#base::Spanned::try_span(#values)),*]
503            )
504        }
505    }
506}
507
508fn expand_spanned(input: &DeriveInput) -> Result<TokenStream> {
509    let name = &input.ident;
510    let base = base_crate();
511    let span_ty = quote! {#base::Span};
512    let mut generics = input.generics.clone();
513    let type_parameters = input
514        .generics
515        .type_params()
516        .map(|parameter| parameter.ident.to_string())
517        .collect::<BTreeSet<_>>();
518    for ty in spanned_field_types(input)
519        .into_iter()
520        .filter(|ty| type_mentions_parameter(ty, &type_parameters))
521    {
522        generics
523            .make_where_clause()
524            .predicates
525            .push(parse_quote!(#ty: #base::Spanned));
526    }
527    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
528    let body = match &input.data {
529        Data::Struct(data) => {
530            let members = data.fields.members().map(|member| {
531                let value = quote!(&self.#member);
532                (member, value)
533            });
534            expand_spanned_members(members)
535        }
536        Data::Enum(data) => {
537            let arms = data
538                .variants
539                .iter()
540                .map(|variant| {
541                    let variant_name = &variant.ident;
542                    let members = variant.fields.members().collect::<Vec<_>>();
543                    let bindings = members
544                        .iter()
545                        .enumerate()
546                        .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
547                        .collect::<Vec<_>>();
548                    let values = bindings.iter().map(|binding| quote!(#binding));
549                    let span = expand_spanned_members(members.iter().cloned().zip(values));
550
551                    match &variant.fields {
552                        Fields::Named(_) => {
553                            let fields = members
554                                .iter()
555                                .zip(&bindings)
556                                .map(|(member, binding)| quote!(#member: #binding));
557                            quote! {
558                                Self::#variant_name { #(#fields),* } => #span
559                            }
560                        }
561                        Fields::Unnamed(_) => quote! {
562                            Self::#variant_name(#(#bindings),*) => #span
563                        },
564                        Fields::Unit => quote! {
565                            Self::#variant_name => #span
566                        },
567                    }
568                })
569                .collect::<Vec<_>>();
570            let empty_fallback = data.variants.is_empty().then(|| quote!(_ => None));
571            quote! {
572                match self {
573                    #(#arms,)*
574                    #empty_fallback
575                }
576            }
577        }
578        Data::Union(_) => {
579            return Err(Error::new_spanned(
580                name,
581                "Spanned cannot be derived for unions",
582            ));
583        }
584    };
585
586    Ok(quote! {
587        impl #impl_generics
588            #base::Spanned for #name #type_generics #where_clause
589        {
590            fn try_span(&self) -> Option<#span_ty> {
591                #body
592            }
593        }
594    })
595}
596
597fn type_mentions_parameter(ty: &impl ToTokens, parameters: &BTreeSet<String>) -> bool {
598    fn stream_mentions_parameter(stream: TokenStream, parameters: &BTreeSet<String>) -> bool {
599        stream.into_iter().any(|token| match token {
600            TokenTree::Ident(ident) => parameters.contains(&ident.to_string()),
601            TokenTree::Group(group) => stream_mentions_parameter(group.stream(), parameters),
602            _ => false,
603        })
604    }
605    stream_mentions_parameter(ty.to_token_stream(), parameters)
606}
607
608fn spanned_field_types(input: &DeriveInput) -> Vec<&Type> {
609    fn selected(fields: &Fields) -> Vec<&Type> {
610        fields
611            .iter()
612            .find(|field| field.ident.as_ref().is_some_and(|ident| ident == "span"))
613            .map_or_else(
614                || fields.iter().map(|field| &field.ty).collect(),
615                |field| vec![&field.ty],
616            )
617    }
618
619    match &input.data {
620        Data::Struct(data) => selected(&data.fields),
621        Data::Enum(data) => data
622            .variants
623            .iter()
624            .flat_map(|variant| selected(&variant.fields))
625            .collect(),
626        Data::Union(_) => Vec::new(),
627    }
628}