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                visitor.visit_terminal_mut(self);
133            }
134        }
135
136        impl __walk::FoldChildren for #ty {
137            fn fold_children<F>(self, _folder: &mut F) -> Self
138            where
139                F: fold::Fold + ?Sized,
140            {
141                self
142            }
143        }
144
145        impl __walk::VisitValue for #ty {
146            fn visit_value<'ast, V>(&'ast self, visitor: &mut V)
147            where
148                V: visit::Visit<'ast> + ?Sized,
149            {
150                visitor.#visit_method(self);
151            }
152        }
153
154        impl __walk::VisitValueMut for #ty {
155            fn visit_value_mut<V>(&mut self, visitor: &mut V)
156            where
157                V: visit_mut::VisitMut + ?Sized,
158            {
159                visitor.#visit_mut_method(self);
160            }
161        }
162
163        impl __walk::FoldValue for #ty {
164            fn fold_value<F>(self, folder: &mut F) -> Self
165            where
166                F: fold::Fold + ?Sized,
167            {
168                folder.#fold_method(self)
169            }
170        }
171
172    }
173}
174
175struct SyntaxWalkFunction {
176    traversal: syn::Ident,
177    name: syn::Ident,
178    ty: Type,
179}
180
181impl Parse for SyntaxWalkFunction {
182    fn parse(input: ParseStream<'_>) -> Result<Self> {
183        let traversal = input.parse()?;
184        input.parse::<Token![,]>()?;
185        let name = input.parse()?;
186        input.parse::<Token![,]>()?;
187        let ty = input.parse()?;
188        Ok(Self {
189            traversal,
190            name,
191            ty,
192        })
193    }
194}
195
196/// Generates one free function inside a traversal module.
197#[doc(hidden)]
198#[proc_macro]
199pub fn __syntax_walk_function(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
200    let input = parse_macro_input!(input as SyntaxWalkFunction);
201    expand_syntax_walk_function(&input).into()
202}
203
204fn expand_syntax_walk_function(input: &SyntaxWalkFunction) -> TokenStream {
205    let name = &input.name;
206    let ty = &input.ty;
207    let snake = syntax::to_snake_case(&name.to_string());
208    match input.traversal.to_string().as_str() {
209        "visit" => {
210            let method = format_ident!("visit_{snake}");
211            quote! {
212                #[doc = concat!("Walks the children of a `", stringify!(#name), "` node.")]
213                pub fn #method<'ast, V>(visitor: &mut V, node: &'ast #ty)
214                where
215                    V: Visit<'ast> + ?Sized,
216                {
217                    super::__walk::WalkChildren::walk_children(node, visitor);
218                }
219            }
220        }
221        "visit_mut" => {
222            let method = format_ident!("visit_{snake}_mut");
223            quote! {
224                #[doc = concat!("Walks the mutable children of a `", stringify!(#name), "` node.")]
225                pub fn #method<V>(visitor: &mut V, node: &mut #ty)
226                where
227                    V: VisitMut + ?Sized,
228                {
229                    super::__walk::WalkChildrenMut::walk_children_mut(node, visitor);
230                }
231            }
232        }
233        "fold" => {
234            let method = format_ident!("fold_{snake}");
235            quote! {
236                #[doc = concat!("Folds the children of a `", stringify!(#name), "` node.")]
237                pub fn #method<F>(folder: &mut F, node: #ty) -> #ty
238                where
239                    F: Fold + ?Sized,
240                {
241                    super::__walk::FoldChildren::fold_children(node, folder)
242                }
243            }
244        }
245        _ => Error::new_spanned(&input.traversal, "expected `visit`, `visit_mut`, or `fold`")
246            .to_compile_error(),
247    }
248}
249
250fn is_syntax_storage(member: &Member) -> bool {
251    matches!(
252        member,
253        Member::Named(name)
254            if matches!(
255                name.to_string().as_str(),
256                "span"
257                    | "raw"
258                    | "package_qualifier"
259                    | "index_qualifier"
260                    | "leading_trivia"
261                    | "trailing_trivia"
262                    | "delimiter"
263            )
264    )
265}
266
267fn walk_struct_fields(fields: &Fields, mutable: bool) -> TokenStream {
268    let calls = fields
269        .members()
270        .filter(|member| !is_syntax_storage(member))
271        .map(|member| {
272            if mutable {
273                quote!(__walk::VisitValueMut::visit_value_mut(&mut self.#member, visitor);)
274            } else {
275                quote!(__walk::VisitValue::visit_value(&self.#member, visitor);)
276            }
277        });
278    let storage_hooks = mutable.then(|| walk_trivia_storage_mut(fields));
279    quote!(#(#calls)* #storage_hooks)
280}
281
282/// Hands the trivia a node stores itself to the mutable visitor's hooks.
283fn walk_trivia_storage_mut(fields: &Fields) -> TokenStream {
284    let has_member = |name: &str| {
285        fields
286            .members()
287            .any(|member| matches!(&member, Member::Named(ident) if ident == name))
288    };
289    let base = base_crate();
290    let terminal = has_member("trailing_trivia").then(|| quote!(visitor.visit_terminal_mut(self);));
291    let delimiter = has_member("delimiter").then(|| {
292        quote! {
293            let mut __m2_syn_delimiter = #base::DelimiterValue::delimiter(&self.delimiter);
294            visitor.visit_delimiter_mut(&mut __m2_syn_delimiter);
295            self.delimiter = #base::DelimiterValue::from_delimiter(__m2_syn_delimiter)
296                .expect("a delimiter visitor keeps the delimiter family");
297        }
298    });
299    quote!(#terminal #delimiter)
300}
301
302fn walk_enum_fields(input: &DeriveInput, mutable: bool) -> TokenStream {
303    let Data::Enum(data) = &input.data else {
304        unreachable!("enum walking requires an enum")
305    };
306    let arms = data.variants.iter().map(|variant| {
307        let name = &variant.ident;
308        let members = variant.fields.members().collect::<Vec<_>>();
309        let bindings = members
310            .iter()
311            .enumerate()
312            .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
313            .collect::<Vec<_>>();
314        let selected = members
315            .iter()
316            .zip(&bindings)
317            .filter(|(member, _)| !is_syntax_storage(member));
318        let calls = selected.map(|(_, binding)| {
319            if mutable {
320                quote!(__walk::VisitValueMut::visit_value_mut(#binding, visitor);)
321            } else {
322                quote!(__walk::VisitValue::visit_value(#binding, visitor);)
323            }
324        });
325        match &variant.fields {
326            Fields::Named(_) => {
327                let patterns = members
328                    .iter()
329                    .zip(&bindings)
330                    .map(|(member, binding)| quote!(#member: #binding));
331                quote!(Self::#name { #(#patterns),* } => { #(#calls)* })
332            }
333            Fields::Unnamed(_) => quote!(Self::#name(#(#bindings),*) => { #(#calls)* }),
334            Fields::Unit => quote!(Self::#name => {}),
335        }
336    });
337    quote!(match self { #(#arms,)* })
338}
339
340fn fold_struct(input: &DeriveInput) -> TokenStream {
341    let Data::Struct(data) = &input.data else {
342        unreachable!("struct folding requires a struct")
343    };
344    let name = &input.ident;
345    let members = data.fields.members().collect::<Vec<_>>();
346    let bindings = members
347        .iter()
348        .enumerate()
349        .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
350        .collect::<Vec<_>>();
351    let values = members.iter().zip(&bindings).map(|(member, binding)| {
352        if is_syntax_storage(member) {
353            quote!(#binding)
354        } else {
355            quote!(__walk::FoldValue::fold_value(#binding, folder))
356        }
357    });
358    match &data.fields {
359        Fields::Named(_) => {
360            let patterns = members
361                .iter()
362                .zip(&bindings)
363                .map(|(member, binding)| quote!(#member: #binding));
364            let fields = members
365                .iter()
366                .zip(values)
367                .map(|(member, value)| quote!(#member: #value));
368            quote! {
369                let #name { #(#patterns),* } = self;
370                #name { #(#fields),* }
371            }
372        }
373        Fields::Unnamed(_) => quote! {
374            let #name(#(#bindings),*) = self;
375            #name(#(#values),*)
376        },
377        Fields::Unit => quote!(self),
378    }
379}
380
381fn fold_enum(input: &DeriveInput) -> TokenStream {
382    let Data::Enum(data) = &input.data else {
383        unreachable!("enum folding requires an enum")
384    };
385    let arms = data.variants.iter().map(|variant| {
386        let name = &variant.ident;
387        let members = variant.fields.members().collect::<Vec<_>>();
388        let bindings = members
389            .iter()
390            .enumerate()
391            .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
392            .collect::<Vec<_>>();
393        let values = members.iter().zip(&bindings).map(|(member, binding)| {
394            if is_syntax_storage(member) {
395                quote!(#binding)
396            } else {
397                quote!(__walk::FoldValue::fold_value(#binding, folder))
398            }
399        });
400        match &variant.fields {
401            Fields::Named(_) => {
402                let patterns = members
403                    .iter()
404                    .zip(&bindings)
405                    .map(|(member, binding)| quote!(#member: #binding));
406                let fields = members
407                    .iter()
408                    .zip(values)
409                    .map(|(member, value)| quote!(#member: #value));
410                quote!(Self::#name { #(#patterns),* } => Self::#name { #(#fields),* })
411            }
412            Fields::Unnamed(_) => {
413                quote!(Self::#name(#(#bindings),*) => Self::#name(#(#values),*))
414            }
415            Fields::Unit => quote!(Self::#name => Self::#name),
416        }
417    });
418    quote!(match self { #(#arms,)* })
419}
420
421fn expand_syntax_walk(input: &DeriveInput) -> TokenStream {
422    let name = &input.ident;
423    if !input.generics.params.is_empty() {
424        return Error::new_spanned(input, "syntax walking requires a concrete syntax type")
425            .to_compile_error();
426    }
427    let snake = syntax::to_snake_case(&name.to_string());
428    let visit_method = format_ident!("visit_{snake}");
429    let visit_mut_method = format_ident!("visit_{snake}_mut");
430    let fold_method = format_ident!("fold_{snake}");
431    let (impl_generics, type_generics, where_clause) = input.generics.split_for_impl();
432    let visit = match &input.data {
433        Data::Struct(data) => walk_struct_fields(&data.fields, false),
434        Data::Enum(_) => walk_enum_fields(input, false),
435        Data::Union(_) => {
436            return Error::new_spanned(input, "syntax walking does not support unions")
437                .to_compile_error();
438        }
439    };
440    let visit_mut = match &input.data {
441        Data::Struct(data) => walk_struct_fields(&data.fields, true),
442        Data::Enum(_) => walk_enum_fields(input, true),
443        Data::Union(_) => unreachable!(),
444    };
445    let fold = match &input.data {
446        Data::Struct(_) => fold_struct(input),
447        Data::Enum(_) => fold_enum(input),
448        Data::Union(_) => unreachable!(),
449    };
450    quote! {
451        impl #impl_generics __walk::WalkChildren for #name #type_generics #where_clause {
452            fn walk_children<'ast, V>(&'ast self, visitor: &mut V)
453            where
454                V: visit::Visit<'ast> + ?Sized,
455            {
456                #visit
457            }
458        }
459
460        impl #impl_generics __walk::WalkChildrenMut for #name #type_generics #where_clause {
461            fn walk_children_mut<V>(&mut self, visitor: &mut V)
462            where
463                V: visit_mut::VisitMut + ?Sized,
464            {
465                #visit_mut
466            }
467        }
468
469        impl #impl_generics __walk::FoldChildren for #name #type_generics #where_clause {
470            fn fold_children<F>(self, folder: &mut F) -> Self
471            where
472                F: fold::Fold + ?Sized,
473            {
474                #fold
475            }
476        }
477
478        impl __walk::VisitValue for #name {
479            fn visit_value<'ast, V>(&'ast self, visitor: &mut V)
480            where
481                V: visit::Visit<'ast> + ?Sized,
482            {
483                visitor.#visit_method(self);
484            }
485        }
486
487        impl __walk::VisitValueMut for #name {
488            fn visit_value_mut<V>(&mut self, visitor: &mut V)
489            where
490                V: visit_mut::VisitMut + ?Sized,
491            {
492                visitor.#visit_mut_method(self);
493            }
494        }
495
496        impl __walk::FoldValue for #name {
497            fn fold_value<F>(self, folder: &mut F) -> Self
498            where
499                F: fold::Fold + ?Sized,
500            {
501                folder.#fold_method(self)
502            }
503        }
504
505    }
506}
507
508fn expand_spanned_members(members: impl IntoIterator<Item = (Member, TokenStream)>) -> TokenStream {
509    let members = members.into_iter().collect::<Vec<_>>();
510    let base = base_crate();
511    let span = quote! {#base::Spanned::try_span};
512    if members.is_empty() {
513        return quote!(None);
514    }
515    if let Some((_, value)) = members
516        .iter()
517        .find(|(member, _)| matches!(member, Member::Named(ident) if ident == "span"))
518    {
519        quote!(#span(#value))
520    } else {
521        let values = members.iter().map(|(_, value)| value);
522        quote! {
523            #base::Span::join_all(
524                [#(#base::Spanned::try_span(#values)),*]
525            )
526        }
527    }
528}
529
530fn expand_spanned(input: &DeriveInput) -> Result<TokenStream> {
531    let name = &input.ident;
532    let base = base_crate();
533    let span_ty = quote! {#base::Span};
534    let mut generics = input.generics.clone();
535    let type_parameters = input
536        .generics
537        .type_params()
538        .map(|parameter| parameter.ident.to_string())
539        .collect::<BTreeSet<_>>();
540    for ty in spanned_field_types(input)
541        .into_iter()
542        .filter(|ty| type_mentions_parameter(ty, &type_parameters))
543    {
544        generics
545            .make_where_clause()
546            .predicates
547            .push(parse_quote!(#ty: #base::Spanned));
548    }
549    let (impl_generics, type_generics, where_clause) = generics.split_for_impl();
550    let body = match &input.data {
551        Data::Struct(data) => {
552            let members = data.fields.members().map(|member| {
553                let value = quote!(&self.#member);
554                (member, value)
555            });
556            expand_spanned_members(members)
557        }
558        Data::Enum(data) => {
559            let arms = data
560                .variants
561                .iter()
562                .map(|variant| {
563                    let variant_name = &variant.ident;
564                    let members = variant.fields.members().collect::<Vec<_>>();
565                    let bindings = members
566                        .iter()
567                        .enumerate()
568                        .map(|(index, _)| format_ident!("__m2_syn_field_{index}"))
569                        .collect::<Vec<_>>();
570                    let values = bindings.iter().map(|binding| quote!(#binding));
571                    let span = expand_spanned_members(members.iter().cloned().zip(values));
572
573                    match &variant.fields {
574                        Fields::Named(_) => {
575                            let fields = members
576                                .iter()
577                                .zip(&bindings)
578                                .map(|(member, binding)| quote!(#member: #binding));
579                            quote! {
580                                Self::#variant_name { #(#fields),* } => #span
581                            }
582                        }
583                        Fields::Unnamed(_) => quote! {
584                            Self::#variant_name(#(#bindings),*) => #span
585                        },
586                        Fields::Unit => quote! {
587                            Self::#variant_name => #span
588                        },
589                    }
590                })
591                .collect::<Vec<_>>();
592            let empty_fallback = data.variants.is_empty().then(|| quote!(_ => None));
593            quote! {
594                match self {
595                    #(#arms,)*
596                    #empty_fallback
597                }
598            }
599        }
600        Data::Union(_) => {
601            return Err(Error::new_spanned(
602                name,
603                "Spanned cannot be derived for unions",
604            ));
605        }
606    };
607
608    Ok(quote! {
609        impl #impl_generics
610            #base::Spanned for #name #type_generics #where_clause
611        {
612            fn try_span(&self) -> Option<#span_ty> {
613                #body
614            }
615        }
616    })
617}
618
619fn type_mentions_parameter(ty: &impl ToTokens, parameters: &BTreeSet<String>) -> bool {
620    fn stream_mentions_parameter(stream: TokenStream, parameters: &BTreeSet<String>) -> bool {
621        stream.into_iter().any(|token| match token {
622            TokenTree::Ident(ident) => parameters.contains(&ident.to_string()),
623            TokenTree::Group(group) => stream_mentions_parameter(group.stream(), parameters),
624            _ => false,
625        })
626    }
627    stream_mentions_parameter(ty.to_token_stream(), parameters)
628}
629
630fn spanned_field_types(input: &DeriveInput) -> Vec<&Type> {
631    fn selected(fields: &Fields) -> Vec<&Type> {
632        fields
633            .iter()
634            .find(|field| field.ident.as_ref().is_some_and(|ident| ident == "span"))
635            .map_or_else(
636                || fields.iter().map(|field| &field.ty).collect(),
637                |field| vec![&field.ty],
638            )
639    }
640
641    match &input.data {
642        Data::Struct(data) => selected(&data.fields),
643        Data::Enum(data) => data
644            .variants
645            .iter()
646            .flat_map(|variant| selected(&variant.fields))
647            .collect(),
648        Data::Union(_) => Vec::new(),
649    }
650}