facet_macros_impl/
extension.rs

1//! Code generation for extension attributes.
2
3use crate::{Delimiter, Group, Ident, PFacetAttr, Punct, Spacing, TokenStream, TokenTree};
4use quote::{ToTokens, quote, quote_spanned};
5
6/// Emits the code for an `ExtensionAttr` on a field.
7///
8/// This generates code that calls our `__ext!` proc macro, which then
9/// forwards to the extension crate's dispatcher macro with proper spans.
10///
11/// For `#[facet(kdl::child)]` on field `server: Server`:
12/// ```ignore
13/// ::facet::__ext!(kdl::child { server : Server })
14/// ```
15///
16/// For `#[facet(args::short = 'v')]` on field `verbose: bool`:
17/// ```ignore
18/// ::facet::__ext!(args::short { verbose : bool | = 'v' })
19/// ```
20pub fn emit_extension_attr_for_field(
21    ns_ident: &Ident,
22    key_ident: &Ident,
23    args: &TokenStream,
24    field_name: &impl ToTokens,
25    field_type: &TokenStream,
26    facet_crate: &TokenStream,
27) -> TokenStream {
28    if args.is_empty() {
29        // No args: ::facet::__ext!(ns::key { field : Type })
30        quote! {
31            #facet_crate::__ext!(#ns_ident::#key_ident { #field_name : #field_type })
32        }
33    } else {
34        // With args: ::facet::__ext!(ns::key { field : Type | args })
35        quote! {
36            #facet_crate::__ext!(#ns_ident::#key_ident { #field_name : #field_type | #args })
37        }
38    }
39}
40
41/// Emits the code for an `ExtensionAttr` without field context.
42///
43/// This is used for struct-level, enum-level, or variant-level attributes.
44///
45/// For `#[facet(ns::attr)]` at container level:
46/// ```ignore
47/// ::facet::__ext!(ns::attr { })
48/// ```
49///
50/// For `#[facet(ns::attr = "value")]` at container level:
51/// ```ignore
52/// ::facet::__ext!(ns::attr { | = "value" })
53/// ```
54pub fn emit_extension_attr(
55    ns_ident: &Ident,
56    key_ident: &Ident,
57    args: &TokenStream,
58    facet_crate: &TokenStream,
59) -> TokenStream {
60    if args.is_empty() {
61        // No args: ::facet::__ext!(ns::key { })
62        quote! {
63            #facet_crate::__ext!(#ns_ident::#key_ident { })
64        }
65    } else {
66        // With args: ::facet::__ext!(ns::key { | args })
67        quote! {
68            #facet_crate::__ext!(#ns_ident::#key_ident { | #args })
69        }
70    }
71}
72
73/// Emits an attribute through grammar dispatch.
74///
75/// - Builtin attrs (no namespace) → `::facet::__attr!(...)`
76/// - Namespaced attrs → `::facet::__ext!(ns::key ...)`
77pub fn emit_attr(attr: &PFacetAttr, facet_crate: &TokenStream) -> TokenStream {
78    let key = &attr.key;
79    let args = &attr.args;
80
81    match &attr.ns {
82        Some(ns) => {
83            // Namespaced: use __ext! which routes to ns::__attr!
84            emit_extension_attr(ns, key, args, facet_crate)
85        }
86        None => {
87            // Builtin: route directly to ::facet::__attr! (macro_export puts it at crate root)
88            if args.is_empty() {
89                quote! {
90                    #facet_crate::__attr!(@ns { #facet_crate::builtin } #key { })
91                }
92            } else {
93                quote! {
94                    #facet_crate::__attr!(@ns { #facet_crate::builtin } #key { | #args })
95                }
96            }
97        }
98    }
99}
100
101/// Emits an attribute through grammar dispatch, with field context.
102///
103/// - Builtin attrs (no namespace) → `::facet::__attr!(...)`
104/// - Namespaced attrs → `::facet::__ext!(ns::key ...)`
105pub fn emit_attr_for_field(
106    attr: &PFacetAttr,
107    field_name: &impl ToTokens,
108    field_type: &TokenStream,
109    facet_crate: &TokenStream,
110) -> TokenStream {
111    let key = &attr.key;
112    let args = &attr.args;
113
114    match &attr.ns {
115        Some(ns) => {
116            // Namespaced: use existing helper
117            emit_extension_attr_for_field(ns, key, args, field_name, field_type, facet_crate)
118        }
119        None => {
120            // Builtin: route directly to ::facet::__attr! (macro_export puts it at crate root)
121            if args.is_empty() {
122                quote! {
123                    #facet_crate::__attr!(@ns { #facet_crate::builtin } #key { #field_name : #field_type })
124                }
125            } else {
126                quote! {
127                    #facet_crate::__attr!(@ns { #facet_crate::builtin } #key { #field_name : #field_type | #args })
128                }
129            }
130        }
131    }
132}
133
134/// Implementation of the `__ext!` proc macro.
135///
136/// This proc macro receives extension attribute invocations and forwards them
137/// to the extension crate's dispatcher macro while preserving spans for better
138/// error messages.
139///
140/// Input format: `ns::attr_name { field : Type }` or `ns::attr_name { field : Type | args }`
141/// Output: `ns::__attr!(attr_name { field : Type })` or `ns::__attr!(attr_name { field : Type | args })`
142pub fn ext_attr(input: TokenStream) -> TokenStream {
143    let mut tokens = input.into_iter().peekable();
144
145    // Parse: ns :: attr_name { ... }
146    let ns_ident = match tokens.next() {
147        Some(TokenTree::Ident(ident)) => ident,
148        _ => {
149            return quote! {
150                ::core::compile_error!("__ext!: expected namespace identifier")
151            };
152        }
153    };
154
155    // Expect ::
156    match (tokens.next(), tokens.next()) {
157        (Some(TokenTree::Punct(p1)), Some(TokenTree::Punct(p2)))
158            if p1.as_char() == ':' && p2.as_char() == ':' => {}
159        _ => {
160            return quote! {
161                ::core::compile_error!("__ext!: expected '::'")
162            };
163        }
164    }
165
166    // Get the attribute name (this has the span we want to preserve!)
167    let attr_ident = match tokens.next() {
168        Some(TokenTree::Ident(ident)) => ident,
169        _ => {
170            return quote! {
171                ::core::compile_error!("__ext!: expected attribute name")
172            };
173        }
174    };
175
176    // Get the braced content { ... }
177    let body = match tokens.next() {
178        Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => g,
179        _ => {
180            return quote! {
181                ::core::compile_error!("__ext!: expected braced body")
182            };
183        }
184    };
185
186    // Build the output: ns::__attr!(@ns { ns } attr_name { ... })
187    // The attr_ident preserves its original span!
188    // We pass the namespace so __attr! can do `use $ns::Attr as __ExtAttr;`
189    let __attr = Ident::new("__attr", attr_ident.span());
190    let at = Punct::new('@', Spacing::Alone);
191    let ns_keyword = Ident::new("ns", attr_ident.span());
192
193    let colon1 = Punct::new(':', Spacing::Joint);
194    let colon2 = Punct::new(':', Spacing::Alone);
195    let bang = Punct::new('!', Spacing::Alone);
196
197    // Build the macro invocation tokens manually to preserve spans
198    let mut output = TokenStream::new();
199    output.extend([TokenTree::Ident(ns_ident.clone())]);
200    output.extend([TokenTree::Punct(colon1.clone())]);
201    output.extend([TokenTree::Punct(colon2.clone())]);
202    output.extend([TokenTree::Ident(__attr)]);
203    output.extend([TokenTree::Punct(bang)]);
204
205    // Build the macro arguments: (@ns { ns_ident } attr_name { ... })
206    let mut macro_args = TokenStream::new();
207    // @ns { ns_ident }
208    macro_args.extend([TokenTree::Punct(at)]);
209    macro_args.extend([TokenTree::Ident(ns_keyword)]);
210    let mut ns_group_content = TokenStream::new();
211    ns_group_content.extend([TokenTree::Ident(ns_ident)]);
212    macro_args.extend([TokenTree::Group(Group::new(
213        Delimiter::Brace,
214        ns_group_content,
215    ))]);
216    // attr_name { ... }
217    macro_args.extend([TokenTree::Ident(attr_ident)]);
218    macro_args.extend([TokenTree::Group(body)]);
219
220    let args_group = Group::new(Delimiter::Parenthesis, macro_args);
221    output.extend([TokenTree::Group(args_group)]);
222
223    output
224}
225
226/// Implementation of the `__unknown_attr!` proc macro.
227///
228/// This generates a compile_error! with the span pointing to the unknown identifier.
229///
230/// Input: `unknown_ident`
231/// Output: `compile_error!("unknown extension attribute `unknown_ident`")` with span on the ident
232pub fn unknown_attr(input: TokenStream) -> TokenStream {
233    let mut tokens = input.into_iter();
234
235    // Get the unknown attribute identifier
236    let ident = match tokens.next() {
237        Some(TokenTree::Ident(ident)) => ident,
238        _ => {
239            return quote! {
240                ::core::compile_error!("__unknown_attr!: expected identifier")
241            };
242        }
243    };
244
245    let span = ident.span();
246    let message = format!("unknown extension attribute `{ident}`");
247
248    quote_spanned! { span =>
249        ::core::compile_error!(#message)
250    }
251}
252
253/// Implementation of the `__no_args!` proc macro.
254///
255/// Generates a "does not accept arguments" error with the span pointing to the arguments.
256///
257/// Input: `"ns::attr", token`
258/// Output: `compile_error!("ns::attr does not accept arguments")` with span on token
259pub fn no_args(input: TokenStream) -> TokenStream {
260    let mut tokens = input.into_iter();
261
262    // Get the message string literal
263    let msg = match tokens.next() {
264        Some(TokenTree::Literal(lit)) => {
265            let s = lit.to_string();
266            s.trim_matches('"').to_string()
267        }
268        _ => {
269            return quote! {
270                ::core::compile_error!("__no_args!: expected string literal")
271            };
272        }
273    };
274
275    // Skip comma
276    tokens.next();
277
278    // Get token for span
279    let span = match tokens.next() {
280        Some(tt) => tt.span(),
281        None => {
282            return quote! {
283                ::core::compile_error!("__no_args!: expected token for span")
284            };
285        }
286    };
287
288    let message = format!("{msg} does not accept arguments");
289
290    quote_spanned! { span =>
291        ::core::compile_error!(#message)
292    }
293}