ptx_90_parser_span_derive/
lib.rs1use 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}