Skip to main content

ptx_90_parser_span_derive/
lib.rs

1use proc_macro::TokenStream;
2use proc_macro2::Span as ProcSpan;
3use quote::{format_ident, quote};
4use syn::{parse_macro_input, Data, DeriveInput, Fields, FieldsNamed};
5
6#[proc_macro_derive(Spanned)]
7pub fn derive_spanned(input: TokenStream) -> TokenStream {
8    let input = parse_macro_input!(input as DeriveInput);
9    match impl_spanned(&input) {
10        Ok(tokens) => tokens.into(),
11        Err(err) => err.to_compile_error().into(),
12    }
13}
14
15fn impl_spanned(input: &DeriveInput) -> syn::Result<proc_macro2::TokenStream> {
16    let name = &input.ident;
17    let generics = &input.generics;
18    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
19
20    let (span_arms, set_arms) = match &input.data {
21        Data::Struct(data) => {
22            let (span_arm, set_arm) = build_match_arm(quote! { Self }, &data.fields)?;
23            (vec![span_arm], vec![set_arm])
24        }
25        Data::Enum(data) => {
26            let mut span_arms = Vec::new();
27            let mut set_arms = Vec::new();
28            for variant in &data.variants {
29                let ident = &variant.ident;
30                let path = quote! { Self::#ident };
31                let (span_arm, set_arm) = build_match_arm(path, &variant.fields)?;
32                span_arms.push(span_arm);
33                set_arms.push(set_arm);
34            }
35            (span_arms, set_arms)
36        }
37        Data::Union(_) => {
38            return Err(syn::Error::new_spanned(
39                &input.ident,
40                "Spanned cannot be derived for unions",
41            ));
42        }
43    };
44
45    let span_ty = quote! { crate::parser::Span };
46    let trait_path = quote! { crate::span::Spanned };
47    let span_match = quote! {
48        match self {
49            #(#span_arms)*
50        }
51    };
52    let set_match = quote! {
53        match self {
54            #(#set_arms)*
55        }
56    };
57
58    Ok(quote! {
59        impl #impl_generics #trait_path for #name #ty_generics #where_clause {
60            fn span(&self) -> #span_ty {
61                #span_match
62            }
63
64            fn set_span(&mut self, span: #span_ty) {
65                #set_match
66            }
67        }
68
69        impl #impl_generics #name #ty_generics #where_clause {
70            pub fn span(&self) -> #span_ty {
71                <Self as #trait_path>::span(self)
72            }
73
74            pub fn with_span(mut self, span: #span_ty) -> Self {
75                <Self as #trait_path>::set_span(&mut self, span);
76                self
77            }
78        }
79    })
80}
81
82fn build_match_arm(
83    path: proc_macro2::TokenStream,
84    fields: &Fields,
85) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
86    match fields {
87        Fields::Named(named) => build_named_arm(path, named),
88        _ => Err(syn::Error::new(
89            ProcSpan::call_site(),
90            "Spanned derive only supports structs/enums with named `span` fields",
91        )),
92    }
93}
94
95fn build_named_arm(
96    path: proc_macro2::TokenStream,
97    fields: &FieldsNamed,
98) -> syn::Result<(proc_macro2::TokenStream, proc_macro2::TokenStream)> {
99    let has_span = fields
100        .named
101        .iter()
102        .any(|field| field.ident.as_ref().is_some_and(|ident| ident == "span"));
103    if !has_span {
104        return Err(syn::Error::new(
105            ProcSpan::call_site(),
106            "Spanned derive requires a field named `span`",
107        ));
108    }
109
110    let binding = format_ident!("__span_field");
111    let span_arm = quote! {
112        #path { span: #binding, .. } => #binding.clone(),
113    };
114    let set_arm = quote! {
115        #path { span: #binding, .. } => {
116            *#binding = span;
117        }
118    };
119
120    Ok((span_arm, set_arm))
121}