Skip to main content

facet_macros_impl/
plugin.rs

1//! Plugin system for facet derive macro.
2//!
3//! This module implements the plugin chain pattern that allows external crates
4//! to hook into `#[derive(Facet)]` and generate additional trait implementations.
5//!
6//! ## How it works
7//!
8//! 1. User writes `#[derive(Facet)]` with `#[facet(derive(Error))]`
9//! 2. `facet_macros` detects the `derive(...)` attribute
10//! 3. It chains to the first plugin: `::facet_error::__facet_derive!`
11//! 4. Each plugin adds itself to the chain and forwards to the next (or finalize)
12//! 5. `__facet_finalize!` parses ONCE and generates all code
13//!
14//! ## Plugin naming convention
15//!
16//! `#[facet(derive(Foo))]` maps to `::facet_foo::__facet_derive!`
17//! (lowercase the trait name, prefix with `facet_`)
18
19use crate::{Attribute, AttributeInner, FacetInner, IParse, Ident, ToTokenIter, TokenStream};
20use quote::quote;
21
22/// A plugin reference - either a simple name or a full path.
23///
24/// - `Error` → convention-based lookup (`::facet_error`)
25/// - `some_crate::SomeTrait` → explicit path (`::some_crate`)
26#[derive(Debug, Clone)]
27pub enum PluginRef {
28    /// Simple name like `Error` - uses convention `::facet_{snake_case}`
29    Simple(String),
30    /// Explicit path like `some_crate::SomeTrait` - uses the crate part directly
31    Path {
32        /// The crate name (e.g., `some_crate`)
33        crate_name: String,
34        /// The plugin/trait name (e.g., `SomeTrait`)
35        plugin_name: String,
36    },
37}
38
39impl PluginRef {
40    /// Get the crate path for this plugin reference.
41    pub fn crate_path(&self) -> TokenStream {
42        match self {
43            PluginRef::Simple(name) => {
44                let snake_case = to_snake_case(name);
45                let crate_name = format!("facet_{snake_case}");
46                let crate_ident = quote::format_ident!("{}", crate_name);
47                quote! { ::#crate_ident }
48            }
49            PluginRef::Path { crate_name, .. } => {
50                let crate_ident = quote::format_ident!("{}", crate_name);
51                quote! { ::#crate_ident }
52            }
53        }
54    }
55}
56
57/// Extract plugin references from `#[facet(derive(Plugin1, Plugin2, ...))]` attributes.
58///
59/// Supports both simple names and explicit paths:
60/// - `#[facet(derive(Error))]` → `PluginRef::Simple("Error")`
61/// - `#[facet(derive(some_crate::SomeTrait))]` → `PluginRef::Path { crate_name: "some_crate", plugin_name: "SomeTrait" }`
62pub fn extract_derive_plugins(attrs: &[Attribute]) -> Vec<PluginRef> {
63    let mut plugins = Vec::new();
64
65    for attr in attrs {
66        if let AttributeInner::Facet(facet_attr) = &attr.body.content {
67            for inner in facet_attr.inner.content.iter().map(|d| &d.value) {
68                if let FacetInner::Simple(simple) = inner
69                    && simple.key == "derive"
70                {
71                    // Parse the args to get plugin names
72                    if let Some(ref args) = simple.args {
73                        match args {
74                            crate::AttrArgs::Parens(parens) => {
75                                // Parse comma-separated items (either idents or paths)
76                                plugins.extend(parse_plugin_list(&parens.content));
77                            }
78                            crate::AttrArgs::Equals(_) => {
79                                // derive = Something syntax (unusual but handle it)
80                            }
81                        }
82                    }
83                }
84            }
85        }
86    }
87
88    plugins
89}
90
91/// Parse a comma-separated list of plugin references (idents or paths).
92fn parse_plugin_list(tokens: &[crate::TokenTree]) -> Vec<PluginRef> {
93    let mut plugins = Vec::new();
94    let mut iter = tokens.iter().cloned().peekable();
95
96    while iter.peek().is_some() {
97        // Collect tokens until comma or end
98        let mut item_tokens = Vec::new();
99        while let Some(tt) = iter.peek() {
100            if let proc_macro2::TokenTree::Punct(p) = tt
101                && p.as_char() == ','
102            {
103                iter.next(); // consume comma
104                break;
105            }
106            item_tokens.push(iter.next().unwrap());
107        }
108
109        // Parse the collected tokens as either a simple ident or a path
110        if let Some(plugin_ref) = parse_plugin_ref(&item_tokens) {
111            plugins.push(plugin_ref);
112        }
113    }
114
115    plugins
116}
117
118/// Parse a single plugin reference from a sequence of tokens.
119fn parse_plugin_ref(tokens: &[proc_macro2::TokenTree]) -> Option<PluginRef> {
120    if tokens.is_empty() {
121        return None;
122    }
123
124    // Check if it's a path (contains ::)
125    let has_path_sep = tokens.windows(2).any(|w| {
126        matches!((&w[0], &w[1]),
127            (proc_macro2::TokenTree::Punct(p1), proc_macro2::TokenTree::Punct(p2))
128            if p1.as_char() == ':' && p2.as_char() == ':')
129    });
130
131    if has_path_sep {
132        // Parse as path: crate_name::PluginName
133        // For now, just support single-segment crate name
134        let mut iter = tokens.iter();
135
136        // First ident is crate name
137        let crate_name = match iter.next() {
138            Some(proc_macro2::TokenTree::Ident(id)) => id.to_string(),
139            _ => return None,
140        };
141
142        // Skip ::
143        match (iter.next(), iter.next()) {
144            (Some(proc_macro2::TokenTree::Punct(p1)), Some(proc_macro2::TokenTree::Punct(p2)))
145                if p1.as_char() == ':' && p2.as_char() == ':' => {}
146            _ => return None,
147        }
148
149        // Last ident is plugin name
150        let plugin_name = match iter.next() {
151            Some(proc_macro2::TokenTree::Ident(id)) => id.to_string(),
152            _ => return None,
153        };
154
155        Some(PluginRef::Path {
156            crate_name,
157            plugin_name,
158        })
159    } else {
160        // Simple ident
161        match tokens.first() {
162            Some(proc_macro2::TokenTree::Ident(id)) => Some(PluginRef::Simple(id.to_string())),
163            _ => None,
164        }
165    }
166}
167
168/// Convert a plugin name to its crate path.
169///
170/// `Error` → `::facet_error`
171/// `Display` → `::facet_display`
172pub fn plugin_to_crate_path(plugin_name: &str) -> TokenStream {
173    // Convert PascalCase to snake_case and prefix with facet_
174    let snake_case = to_snake_case(plugin_name);
175    let crate_name = format!("facet_{snake_case}");
176    let crate_ident = quote::format_ident!("{}", crate_name);
177    quote! { ::#crate_ident }
178}
179
180/// Convert PascalCase to snake_case.
181fn to_snake_case(s: &str) -> String {
182    let mut result = String::new();
183    for (i, c) in s.chars().enumerate() {
184        if c.is_uppercase() {
185            if i > 0 {
186                result.push('_');
187            }
188            result.push(c.to_ascii_lowercase());
189        } else {
190            result.push(c);
191        }
192    }
193    result
194}
195
196/// Strip `#[facet(derive(...))]` and plugin-specific attributes from a token stream.
197///
198/// This filters out the plugin-system-specific attributes before passing
199/// the tokens to the normal Facet processing, which would otherwise reject
200/// "derive" as an unknown attribute.
201///
202/// Currently strips:
203/// - `derive(...)` - plugin registration
204/// - `error::from` - facet-error plugin attribute
205/// - `error::source` - facet-error plugin attribute
206/// - Any `namespace::key` pattern (for future plugins)
207///
208/// Handles combined attributes like `#[facet(rename_all = "...", derive(Default))]`
209/// by removing only the plugin-specific parts and keeping other attributes.
210fn strip_derive_attrs(tokens: TokenStream) -> TokenStream {
211    let mut result = TokenStream::new();
212    let mut iter = tokens.into_iter().peekable();
213
214    while let Some(tt) = iter.next() {
215        // Check for # followed by [...]
216        if let proc_macro2::TokenTree::Punct(p) = &tt
217            && p.as_char() == '#'
218            && let Some(proc_macro2::TokenTree::Group(g)) = iter.peek()
219            && g.delimiter() == proc_macro2::Delimiter::Bracket
220        {
221            // This is an attribute - check if it's a facet attribute
222            let inner = g.stream();
223            if let Some(filtered) = strip_plugin_items_from_facet_attr(&inner) {
224                if filtered.is_empty() {
225                    // All items were stripped - skip the entire attribute
226                    iter.next(); // consume the group
227                    continue;
228                } else {
229                    // Some items remain - emit the filtered attribute
230                    result.extend(std::iter::once(tt));
231                    iter.next(); // consume the original group
232                    result.extend(std::iter::once(proc_macro2::TokenTree::Group(
233                        proc_macro2::Group::new(proc_macro2::Delimiter::Bracket, filtered),
234                    )));
235                    continue;
236                }
237            }
238        }
239        result.extend(std::iter::once(tt));
240    }
241
242    result
243}
244
245/// Strip plugin-specific items from inside a facet attribute.
246///
247/// Returns Some(filtered_tokens) if this is a facet attribute, None otherwise.
248/// The filtered_tokens will have plugin items removed (derive, namespace::key).
249/// If all items are plugin items, returns Some(empty stream).
250fn strip_plugin_items_from_facet_attr(inner: &TokenStream) -> Option<TokenStream> {
251    let mut iter = inner.clone().into_iter().peekable();
252
253    // Check for "facet" identifier
254    let facet_ident = match iter.next() {
255        Some(proc_macro2::TokenTree::Ident(id)) if id == "facet" => id,
256        _ => return None,
257    };
258
259    // Check for (...) group
260    let group = match iter.next() {
261        Some(proc_macro2::TokenTree::Group(g))
262            if g.delimiter() == proc_macro2::Delimiter::Parenthesis =>
263        {
264            g
265        }
266        _ => return None,
267    };
268
269    // Parse and filter the items inside facet(...)
270    let filtered_content = strip_plugin_items_from_content(group.stream());
271
272    // Reconstruct the attribute
273    let mut result = TokenStream::new();
274    result.extend(std::iter::once(proc_macro2::TokenTree::Ident(facet_ident)));
275    result.extend(std::iter::once(proc_macro2::TokenTree::Group(
276        proc_macro2::Group::new(proc_macro2::Delimiter::Parenthesis, filtered_content),
277    )));
278
279    Some(result)
280}
281
282/// Strip plugin-specific items from the content of a facet(...) attribute.
283///
284/// Items are comma-separated. Plugin items are:
285/// - `derive(...)` - plugin registration
286/// - `namespace::key` patterns (e.g., error::from, error::source)
287fn strip_plugin_items_from_content(content: TokenStream) -> TokenStream {
288    let mut items: Vec<TokenStream> = Vec::new();
289
290    // Parse comma-separated items
291    let mut current_item = TokenStream::new();
292    let tokens: Vec<proc_macro2::TokenTree> = content.into_iter().collect();
293
294    for tt in &tokens {
295        // Check for comma separator
296        if let proc_macro2::TokenTree::Punct(p) = tt
297            && p.as_char() == ','
298        {
299            // End of current item
300            if !current_item.is_empty() && !is_plugin_item(&current_item) {
301                items.push(current_item);
302            }
303            current_item = TokenStream::new();
304            continue;
305        }
306
307        current_item.extend(std::iter::once(tt.clone()));
308    }
309
310    // Don't forget the last item
311    if !current_item.is_empty() && !is_plugin_item(&current_item) {
312        items.push(current_item);
313    }
314
315    // Reconstruct with commas
316    let mut result = TokenStream::new();
317    for (idx, item) in items.iter().enumerate() {
318        if idx > 0 {
319            result.extend(std::iter::once(proc_macro2::TokenTree::Punct(
320                proc_macro2::Punct::new(',', proc_macro2::Spacing::Alone),
321            )));
322        }
323        result.extend(item.clone());
324    }
325
326    result
327}
328
329/// Check if an item within facet(...) is a plugin-specific item.
330///
331/// Returns true for:
332/// - `derive(...)` - plugin registration
333///
334/// NOTE: We intentionally do NOT strip `namespace::key` patterns here.
335/// Extension attributes like `args::positional`, `dibs::table`, `error::from`
336/// must be preserved so they can be processed by the Facet derive and stored
337/// in the Shape's attributes field.
338fn is_plugin_item(item: &TokenStream) -> bool {
339    let mut iter = item.clone().into_iter();
340
341    if let Some(proc_macro2::TokenTree::Ident(id)) = iter.next() {
342        let name = id.to_string();
343
344        // Check for derive(...)
345        if name == "derive" {
346            return true;
347        }
348    }
349
350    false
351}
352
353/// Generate the plugin chain invocation.
354///
355/// If there are plugins, emits a chain starting with the first plugin.
356/// If no plugins, returns None (caller should proceed with normal codegen).
357pub fn generate_plugin_chain(
358    input_tokens: &TokenStream,
359    plugins: &[PluginRef],
360    facet_crate: &TokenStream,
361) -> Option<TokenStream> {
362    if plugins.is_empty() {
363        return None;
364    }
365
366    // Build the chain from right to left
367    // First plugin gets called with remaining plugins
368    let plugin_paths: Vec<TokenStream> = plugins
369        .iter()
370        .map(|p| {
371            let crate_path = p.crate_path();
372            quote! { #crate_path::__facet_invoke }
373        })
374        .collect();
375
376    let first = &plugin_paths[0];
377    let rest: Vec<_> = plugin_paths[1..].iter().collect();
378
379    let remaining = if rest.is_empty() {
380        quote! {}
381    } else {
382        quote! { #(#rest),* }
383    };
384
385    Some(quote! {
386        #first! {
387            @tokens { #input_tokens }
388            @remaining { #remaining }
389            @plugins { }
390            @facet_crate { #facet_crate }
391        }
392    })
393}
394
395/// Implementation of `__facet_finalize!` proc macro.
396///
397/// This is called at the end of the plugin chain. It:
398/// 1. Parses the type definition ONCE
399/// 2. Generates the base Facet impl
400/// 3. Evaluates each plugin's template against the parsed type
401pub fn facet_finalize(input: TokenStream) -> TokenStream {
402    // Parse the finalize invocation format:
403    // @tokens { ... }
404    // @plugins { @plugin { @name {...} @template {...} } ... }
405    // @facet_crate { ::facet }
406
407    let mut iter = input.to_token_iter();
408
409    let mut tokens: Option<TokenStream> = None;
410    let mut plugins_section: Option<TokenStream> = None;
411    let mut facet_crate: Option<TokenStream> = None;
412
413    // Parse sections
414    while let Ok(section) = iter.parse::<FinalizeSection>() {
415        match section.marker.name.to_string().as_str() {
416            "tokens" => {
417                tokens = Some(section.content.content);
418            }
419            "plugins" => {
420                plugins_section = Some(section.content.content);
421            }
422            "facet_crate" => {
423                facet_crate = Some(section.content.content);
424            }
425            other => {
426                let msg = format!("unknown section in __facet_finalize: @{other}");
427                return quote! { compile_error!(#msg); };
428            }
429        }
430    }
431
432    let tokens = match tokens {
433        Some(t) => t,
434        None => {
435            return quote! { compile_error!("__facet_finalize: missing @tokens section"); };
436        }
437    };
438
439    let facet_crate = facet_crate.unwrap_or_else(|| quote! { ::facet });
440
441    // Strip #[facet(derive(...))] attributes before processing
442    let filtered_tokens = strip_derive_attrs(tokens.clone());
443
444    // Parse the type and generate Facet impl
445    let mut type_iter = filtered_tokens.clone().to_token_iter();
446    let facet_impl = match type_iter.parse::<crate::Cons<crate::AdtDecl, crate::EndOfStream>>() {
447        Ok(it) => match it.first {
448            crate::AdtDecl::Struct(parsed) => crate::process_struct::process_struct(parsed),
449            crate::AdtDecl::Enum(parsed) => crate::process_enum::process_enum(parsed),
450        },
451        Err(err) => {
452            let msg = format!("__facet_finalize: could not parse type: {err}");
453            return quote! { compile_error!(#msg); };
454        }
455    };
456
457    // Extract and evaluate plugin templates
458    let plugin_impls = if let Some(plugins_tokens) = plugins_section {
459        // For now, just extract the templates - evaluation will come next
460        extract_plugin_templates(plugins_tokens, &filtered_tokens, &facet_crate)
461    } else {
462        vec![]
463    };
464
465    quote! {
466        #facet_impl
467        #(#plugin_impls)*
468    }
469}
470
471/// Represents a parsed plugin with its template
472struct PluginTemplate {
473    #[allow(dead_code)] // Will be used for debugging/diagnostics
474    name: String,
475    template: TokenStream,
476}
477
478/// Extract plugin templates from the @plugins section
479fn extract_plugin_templates(
480    plugins_tokens: TokenStream,
481    type_tokens: &TokenStream,
482    facet_crate: &TokenStream,
483) -> Vec<TokenStream> {
484    // Parse plugin sections
485    let plugins = parse_plugin_sections(plugins_tokens);
486
487    // Parse the type once for all plugins
488    let parsed_type = match facet_macro_parse::parse_type(type_tokens.clone()) {
489        Ok(ty) => ty,
490        Err(e) => {
491            let msg = format!("failed to parse type for plugin templates: {e}");
492            return vec![quote! { compile_error!(#msg); }];
493        }
494    };
495
496    // Evaluate each plugin's template
497    plugins
498        .into_iter()
499        .map(|plugin| evaluate_template(plugin.template, &parsed_type, facet_crate))
500        .collect()
501}
502
503/// Parse @plugin { @name {...} @template {...} } sections
504fn parse_plugin_sections(tokens: TokenStream) -> Vec<PluginTemplate> {
505    let mut plugins = Vec::new();
506    let mut iter = tokens.into_iter().peekable();
507
508    while let Some(tt) = iter.next() {
509        // Look for @plugin marker
510        if let proc_macro2::TokenTree::Punct(p) = &tt
511            && p.as_char() == '@'
512        {
513            // Next should be 'plugin' identifier
514            if let Some(proc_macro2::TokenTree::Ident(id)) = iter.peek()
515                && *id == "plugin"
516            {
517                iter.next(); // consume 'plugin'
518
519                // Next should be { ... } containing @name and @template
520                if let Some(proc_macro2::TokenTree::Group(g)) = iter.next()
521                    && g.delimiter() == proc_macro2::Delimiter::Brace
522                    && let Some(plugin) = parse_plugin_content(g.stream())
523                {
524                    plugins.push(plugin);
525                }
526            }
527        }
528    }
529
530    plugins
531}
532
533/// Parse the content of a @plugin { ... } section
534fn parse_plugin_content(tokens: TokenStream) -> Option<PluginTemplate> {
535    let mut name: Option<String> = None;
536    let mut template: Option<TokenStream> = None;
537    let mut iter = tokens.into_iter().peekable();
538
539    while let Some(tt) = iter.next() {
540        if let proc_macro2::TokenTree::Punct(p) = &tt
541            && p.as_char() == '@'
542            && let Some(proc_macro2::TokenTree::Ident(id)) = iter.peek()
543        {
544            let key = id.to_string();
545            iter.next(); // consume identifier
546
547            // Next should be { ... }
548            if let Some(proc_macro2::TokenTree::Group(g)) = iter.next()
549                && g.delimiter() == proc_macro2::Delimiter::Brace
550            {
551                match key.as_str() {
552                    "name" => {
553                        // Extract string literal from group
554                        let content = g.stream().into_iter().collect::<Vec<_>>();
555                        if let Some(proc_macro2::TokenTree::Literal(lit)) = content.first() {
556                            let s = lit.to_string();
557                            name = Some(s.trim_matches('"').to_string());
558                        }
559                    }
560                    "template" => {
561                        template = Some(g.stream());
562                    }
563                    _ => {}
564                }
565            }
566        }
567    }
568
569    match (name, template) {
570        (Some(n), Some(t)) => Some(PluginTemplate {
571            name: n,
572            template: t,
573        }),
574        _ => None,
575    }
576}
577
578/// Evaluate a template against a parsed type
579fn evaluate_template(
580    template: TokenStream,
581    parsed_type: &facet_macro_parse::PType,
582    _facet_crate: &TokenStream,
583) -> TokenStream {
584    let mut ctx = EvalContext::new(parsed_type);
585    evaluate_with_context(template, &mut ctx)
586}
587
588// ============================================================================
589// CONTEXT STACK
590// ============================================================================
591
592/// The evaluation context - a stack of nested scopes.
593///
594/// As we enter `@for_variant`, `@for_field`, `@if_attr`, etc., we push
595/// context frames. Directives like `@field_name` look up the stack to find
596/// the relevant context.
597struct EvalContext<'a> {
598    /// The parsed type we're generating code for
599    parsed_type: &'a facet_macro_parse::PType,
600
601    /// Stack of context frames (innermost last)
602    stack: Vec<ContextFrame<'a>>,
603}
604
605/// A single frame in the context stack
606enum ContextFrame<'a> {
607    /// We're inside a `@for_variant { ... }` loop
608    Variant {
609        variant: &'a facet_macro_parse::PVariant,
610    },
611
612    /// We're inside a `@for_field { ... }` loop
613    Field {
614        field: &'a facet_macro_parse::PStructField,
615        /// Index of this field (for tuple patterns like `__v0`, `__v1`)
616        index: usize,
617    },
618
619    /// We're inside a `@if_attr(ns::key) { ... }` block
620    Attr {
621        /// The matched attribute
622        attr: &'a facet_macro_parse::PFacetAttr,
623    },
624}
625
626impl<'a> EvalContext<'a> {
627    const fn new(parsed_type: &'a facet_macro_parse::PType) -> Self {
628        Self {
629            parsed_type,
630            stack: Vec::new(),
631        }
632    }
633
634    fn push(&mut self, frame: ContextFrame<'a>) {
635        self.stack.push(frame);
636    }
637
638    fn pop(&mut self) {
639        self.stack.pop();
640    }
641
642    /// Find the current variant context (if any)
643    fn current_variant(&self) -> Option<&'a facet_macro_parse::PVariant> {
644        self.stack.iter().rev().find_map(|f| match f {
645            ContextFrame::Variant { variant } => Some(*variant),
646            _ => None,
647        })
648    }
649
650    /// Find the current field context (if any)
651    fn current_field(&self) -> Option<(&'a facet_macro_parse::PStructField, usize)> {
652        self.stack.iter().rev().find_map(|f| match f {
653            ContextFrame::Field { field, index } => Some((*field, *index)),
654            _ => None,
655        })
656    }
657
658    /// Find the current attr context (if any)
659    fn current_attr(&self) -> Option<&'a facet_macro_parse::PFacetAttr> {
660        self.stack.iter().rev().find_map(|f| match f {
661            ContextFrame::Attr { attr } => Some(*attr),
662            _ => None,
663        })
664    }
665
666    /// Get the fields of the current context (variant's fields or struct's fields)
667    fn current_fields(&self) -> Option<&'a [facet_macro_parse::PStructField]> {
668        // First check if we're in a variant
669        if let Some(variant) = self.current_variant() {
670            return match &variant.kind {
671                facet_macro_parse::PVariantKind::Tuple { fields } => Some(fields),
672                facet_macro_parse::PVariantKind::Struct { fields } => Some(fields),
673                facet_macro_parse::PVariantKind::Unit => None,
674            };
675        }
676
677        // Otherwise, check if we're in a struct
678        if let facet_macro_parse::PType::Struct(s) = self.parsed_type {
679            return match &s.kind {
680                facet_macro_parse::PStructKind::Struct { fields } => Some(fields),
681                facet_macro_parse::PStructKind::TupleStruct { fields } => Some(fields),
682                facet_macro_parse::PStructKind::UnitStruct => None,
683            };
684        }
685
686        None
687    }
688
689    /// Get the attrs of the current context (field, variant, or container)
690    fn current_attrs(&self) -> &'a facet_macro_parse::PAttrs {
691        // Check field first (most specific)
692        if let Some((field, _)) = self.current_field() {
693            return &field.attrs;
694        }
695
696        // Then variant
697        if let Some(variant) = self.current_variant() {
698            return &variant.attrs;
699        }
700
701        // Finally container
702        match self.parsed_type {
703            facet_macro_parse::PType::Struct(s) => &s.container.attrs,
704            facet_macro_parse::PType::Enum(e) => &e.container.attrs,
705        }
706    }
707}
708
709// ============================================================================
710// ATTRIBUTE QUERY
711// ============================================================================
712
713/// Parsed attribute query like `error::source` or `diagnostic::label`
714struct AttrQuery {
715    ns: String,
716    key: String,
717}
718
719impl AttrQuery {
720    /// Parse from tokens like `error::source` or `diagnostic::label`
721    fn parse(tokens: TokenStream) -> Option<Self> {
722        let mut iter = tokens.into_iter();
723
724        // First: namespace ident
725        let ns = match iter.next() {
726            Some(proc_macro2::TokenTree::Ident(id)) => id.to_string(),
727            _ => return None,
728        };
729
730        // Then: ::
731        match (iter.next(), iter.next()) {
732            (Some(proc_macro2::TokenTree::Punct(p1)), Some(proc_macro2::TokenTree::Punct(p2)))
733                if p1.as_char() == ':' && p2.as_char() == ':' => {}
734            _ => return None,
735        }
736
737        // Then: key ident
738        let key = match iter.next() {
739            Some(proc_macro2::TokenTree::Ident(id)) => id.to_string(),
740            _ => return None,
741        };
742
743        Some(AttrQuery { ns, key })
744    }
745
746    /// Check if an attribute matches this query
747    fn matches(&self, attr: &facet_macro_parse::PFacetAttr) -> bool {
748        if let Some(ref ns) = attr.ns {
749            *ns == self.ns && attr.key == self.key
750        } else {
751            false
752        }
753    }
754
755    /// Find matching attribute in a list
756    fn find_in<'a>(
757        &self,
758        attrs: &'a [facet_macro_parse::PFacetAttr],
759    ) -> Option<&'a facet_macro_parse::PFacetAttr> {
760        attrs.iter().find(|a| self.matches(a))
761    }
762}
763
764// ============================================================================
765// TEMPLATE EVALUATION
766// ============================================================================
767
768/// Evaluate a template with the given context
769fn evaluate_with_context(template: TokenStream, ctx: &mut EvalContext<'_>) -> TokenStream {
770    let mut output = TokenStream::new();
771    let mut iter = template.into_iter().peekable();
772
773    while let Some(tt) = iter.next() {
774        match &tt {
775            proc_macro2::TokenTree::Punct(p) if p.as_char() == '@' => {
776                handle_directive(&mut iter, ctx, &mut output);
777            }
778            proc_macro2::TokenTree::Group(g) => {
779                // Recursively evaluate groups
780                let inner = evaluate_with_context(g.stream(), ctx);
781                let new_group = proc_macro2::Group::new(g.delimiter(), inner);
782                output.extend(std::iter::once(proc_macro2::TokenTree::Group(new_group)));
783            }
784            _ => {
785                output.extend(std::iter::once(tt));
786            }
787        }
788    }
789
790    output
791}
792
793/// Handle a directive after seeing `@`
794fn handle_directive(
795    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
796    ctx: &mut EvalContext<'_>,
797    output: &mut TokenStream,
798) {
799    let Some(next) = iter.next() else {
800        // @ at end of stream - emit it
801        output.extend(quote! { @ });
802        return;
803    };
804
805    let proc_macro2::TokenTree::Ident(directive_ident) = &next else {
806        // Not an ident - emit @ and the token
807        output.extend(quote! { @ });
808        output.extend(std::iter::once(next));
809        return;
810    };
811
812    let directive = directive_ident.to_string();
813
814    match directive.as_str() {
815        // === Type-level directives ===
816        "Self" => emit_self_type(ctx, output),
817
818        // === Looping directives ===
819        "for_variant" => handle_for_variant(iter, ctx, output),
820        "for_field" => handle_for_field(iter, ctx, output),
821
822        // === Conditional directives ===
823        "if_attr" => handle_if_attr(iter, ctx, output),
824        "if_field_attr" => handle_if_field_attr(iter, ctx, output),
825        "if_any_field_attr" => handle_if_any_field_attr(iter, ctx, output),
826        "if_struct" => handle_if_struct(iter, ctx, output),
827        "if_enum" => handle_if_enum(iter, ctx, output),
828        "if_unit_variant" => handle_if_unit_variant(iter, ctx, output),
829        "if_tuple_variant" => handle_if_tuple_variant(iter, ctx, output),
830        "if_struct_variant" => handle_if_struct_variant(iter, ctx, output),
831
832        // === Context accessors ===
833        "variant_name" => emit_variant_name(ctx, output),
834        "variant_pattern" => emit_variant_pattern(ctx, output),
835        "variant_pattern_only" => handle_variant_pattern_only(iter, ctx, output),
836        "field_name" => emit_field_name(ctx, output),
837        "field_type" => emit_field_type(ctx, output),
838        "field_expr" => emit_field_expr(ctx, output),
839        "attr_args" => emit_attr_args(ctx, output),
840        "doc" => emit_doc(ctx, output),
841
842        // === Default-related directives ===
843        "field_default_expr" => emit_field_default_expr(ctx, output),
844        "variant_default_construction" => emit_variant_default_construction(ctx, output),
845
846        // === Display-related directives ===
847        "format_doc_comment" => emit_format_doc_comment(ctx, output),
848
849        // === Unknown directive ===
850        _ => {
851            // Emit as-is (might be user code with @ symbol)
852            output.extend(quote! { @ });
853            output.extend(std::iter::once(next.clone()));
854        }
855    }
856}
857
858// ============================================================================
859// TYPE-LEVEL DIRECTIVES
860// ============================================================================
861
862fn emit_self_type(ctx: &EvalContext<'_>, output: &mut TokenStream) {
863    let name = ctx.parsed_type.name();
864    output.extend(quote! { #name });
865}
866
867// ============================================================================
868// LOOPING DIRECTIVES
869// ============================================================================
870
871/// `@for_variant { ... }` - loop over enum variants
872fn handle_for_variant(
873    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
874    ctx: &mut EvalContext<'_>,
875    output: &mut TokenStream,
876) {
877    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
878        return; // Malformed - no body
879    };
880
881    let body = body_group.stream();
882
883    // Only works for enums
884    let facet_macro_parse::PType::Enum(e) = ctx.parsed_type else {
885        return;
886    };
887
888    for variant in &e.variants {
889        ctx.push(ContextFrame::Variant { variant });
890        let expanded = evaluate_with_context(body.clone(), ctx);
891        output.extend(expanded);
892        ctx.pop();
893    }
894}
895
896/// `@for_field { ... }` - loop over fields of current context
897fn handle_for_field(
898    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
899    ctx: &mut EvalContext<'_>,
900    output: &mut TokenStream,
901) {
902    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
903        return;
904    };
905
906    let body = body_group.stream();
907
908    let Some(fields) = ctx.current_fields() else {
909        return;
910    };
911
912    // Need to collect to avoid borrow issues
913    let fields: Vec<_> = fields.iter().enumerate().collect();
914
915    for (index, field) in fields {
916        ctx.push(ContextFrame::Field { field, index });
917        let expanded = evaluate_with_context(body.clone(), ctx);
918        output.extend(expanded);
919        ctx.pop();
920    }
921}
922
923// ============================================================================
924// CONDITIONAL DIRECTIVES
925// ============================================================================
926
927/// `@if_attr(ns::key) { ... }` - conditional on current context having attr
928fn handle_if_attr(
929    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
930    ctx: &mut EvalContext<'_>,
931    output: &mut TokenStream,
932) {
933    // Parse (ns::key)
934    let Some(proc_macro2::TokenTree::Group(query_group)) = iter.next() else {
935        return;
936    };
937
938    // Parse { body }
939    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
940        return;
941    };
942
943    let Some(query) = AttrQuery::parse(query_group.stream()) else {
944        return;
945    };
946
947    let attrs = ctx.current_attrs();
948
949    if let Some(matched_attr) = query.find_in(&attrs.facet) {
950        ctx.push(ContextFrame::Attr { attr: matched_attr });
951        let expanded = evaluate_with_context(body_group.stream(), ctx);
952        output.extend(expanded);
953        ctx.pop();
954    }
955}
956
957/// `@if_field_attr(ns::key) { ... }` - find field with attr, bind field context
958///
959/// This is a combined "find + bind" - it searches all fields in current context
960/// for one with the given attribute, and if found, enters both field and attr context.
961fn handle_if_field_attr(
962    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
963    ctx: &mut EvalContext<'_>,
964    output: &mut TokenStream,
965) {
966    let Some(proc_macro2::TokenTree::Group(query_group)) = iter.next() else {
967        return;
968    };
969
970    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
971        return;
972    };
973
974    let Some(query) = AttrQuery::parse(query_group.stream()) else {
975        return;
976    };
977
978    let Some(fields) = ctx.current_fields() else {
979        return;
980    };
981
982    // Need to collect to avoid borrow issues
983    let fields: Vec<_> = fields.iter().enumerate().collect();
984
985    // Find first field with matching attr
986    for (index, field) in fields {
987        if let Some(matched_attr) = query.find_in(&field.attrs.facet) {
988            ctx.push(ContextFrame::Field { field, index });
989            ctx.push(ContextFrame::Attr { attr: matched_attr });
990            let expanded = evaluate_with_context(body_group.stream(), ctx);
991            output.extend(expanded);
992            ctx.pop(); // attr
993            ctx.pop(); // field
994            return; // Only emit once for first match
995        }
996    }
997}
998
999/// `@if_any_field_attr(ns::key) { ... }` - conditional if ANY field has attr
1000///
1001/// Unlike `@if_field_attr`, this doesn't bind a specific field - it just checks
1002/// if any field has the attribute. Useful for wrapping a `@for_field` that will
1003/// check each field individually.
1004fn handle_if_any_field_attr(
1005    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
1006    ctx: &mut EvalContext<'_>,
1007    output: &mut TokenStream,
1008) {
1009    let Some(proc_macro2::TokenTree::Group(query_group)) = iter.next() else {
1010        return;
1011    };
1012
1013    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
1014        return;
1015    };
1016
1017    let Some(query) = AttrQuery::parse(query_group.stream()) else {
1018        return;
1019    };
1020
1021    let Some(fields) = ctx.current_fields() else {
1022        return;
1023    };
1024
1025    // Check if any field has the attr
1026    let has_any = fields
1027        .iter()
1028        .any(|f| query.find_in(&f.attrs.facet).is_some());
1029
1030    if has_any {
1031        let expanded = evaluate_with_context(body_group.stream(), ctx);
1032        output.extend(expanded);
1033    }
1034}
1035
1036/// `@if_struct { ... }` - emit body only for struct types
1037fn handle_if_struct(
1038    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
1039    ctx: &mut EvalContext<'_>,
1040    output: &mut TokenStream,
1041) {
1042    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
1043        return;
1044    };
1045
1046    if matches!(ctx.parsed_type, facet_macro_parse::PType::Struct(_)) {
1047        let expanded = evaluate_with_context(body_group.stream(), ctx);
1048        output.extend(expanded);
1049    }
1050}
1051
1052/// `@if_enum { ... }` - emit body only for enum types
1053fn handle_if_enum(
1054    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
1055    ctx: &mut EvalContext<'_>,
1056    output: &mut TokenStream,
1057) {
1058    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
1059        return;
1060    };
1061
1062    if matches!(ctx.parsed_type, facet_macro_parse::PType::Enum(_)) {
1063        let expanded = evaluate_with_context(body_group.stream(), ctx);
1064        output.extend(expanded);
1065    }
1066}
1067
1068/// `@if_unit_variant { ... }` - conditional on current variant being a unit variant
1069fn handle_if_unit_variant(
1070    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
1071    ctx: &mut EvalContext<'_>,
1072    output: &mut TokenStream,
1073) {
1074    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
1075        return;
1076    };
1077
1078    let Some(variant) = ctx.current_variant() else {
1079        return;
1080    };
1081
1082    if matches!(variant.kind, facet_macro_parse::PVariantKind::Unit) {
1083        let expanded = evaluate_with_context(body_group.stream(), ctx);
1084        output.extend(expanded);
1085    }
1086}
1087
1088/// `@if_tuple_variant { ... }` - conditional on current variant being a tuple variant
1089fn handle_if_tuple_variant(
1090    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
1091    ctx: &mut EvalContext<'_>,
1092    output: &mut TokenStream,
1093) {
1094    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
1095        return;
1096    };
1097
1098    let Some(variant) = ctx.current_variant() else {
1099        return;
1100    };
1101
1102    if matches!(variant.kind, facet_macro_parse::PVariantKind::Tuple { .. }) {
1103        let expanded = evaluate_with_context(body_group.stream(), ctx);
1104        output.extend(expanded);
1105    }
1106}
1107
1108/// `@if_struct_variant { ... }` - conditional on current variant being a struct variant
1109fn handle_if_struct_variant(
1110    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
1111    ctx: &mut EvalContext<'_>,
1112    output: &mut TokenStream,
1113) {
1114    let Some(proc_macro2::TokenTree::Group(body_group)) = iter.next() else {
1115        return;
1116    };
1117
1118    let Some(variant) = ctx.current_variant() else {
1119        return;
1120    };
1121
1122    if matches!(variant.kind, facet_macro_parse::PVariantKind::Struct { .. }) {
1123        let expanded = evaluate_with_context(body_group.stream(), ctx);
1124        output.extend(expanded);
1125    }
1126}
1127
1128// ============================================================================
1129// CONTEXT ACCESSORS
1130// ============================================================================
1131
1132fn emit_variant_name(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1133    if let Some(variant) = ctx.current_variant()
1134        && let facet_macro_parse::IdentOrLiteral::Ident(name) = &variant.name.raw
1135    {
1136        output.extend(quote! { #name });
1137    }
1138}
1139
1140fn emit_variant_pattern(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1141    let Some(variant) = ctx.current_variant() else {
1142        return;
1143    };
1144
1145    use facet_macro_parse::{IdentOrLiteral, PVariantKind};
1146
1147    match &variant.kind {
1148        PVariantKind::Unit => {
1149            // No pattern needed for unit variants
1150        }
1151        PVariantKind::Tuple { fields } => {
1152            // Use v0, v1, etc. for legacy compatibility with @format_doc_comment
1153            // (which uses {0}, {1} placeholders that expect v0, v1, etc.)
1154            let names: Vec<_> = (0..fields.len())
1155                .map(|i| quote::format_ident!("v{}", i))
1156                .collect();
1157            output.extend(quote! { ( #(#names),* ) });
1158        }
1159        PVariantKind::Struct { fields } => {
1160            let names: Vec<_> = fields
1161                .iter()
1162                .filter_map(|f| {
1163                    if let IdentOrLiteral::Ident(id) = &f.name.raw {
1164                        Some(id.clone())
1165                    } else {
1166                        None
1167                    }
1168                })
1169                .collect();
1170            output.extend(quote! { { #(#names),* } });
1171        }
1172    }
1173}
1174
1175/// `@variant_pattern_only(ns::key) { ... }` - generate pattern binding only fields with attribute
1176fn handle_variant_pattern_only(
1177    iter: &mut std::iter::Peekable<proc_macro2::token_stream::IntoIter>,
1178    ctx: &EvalContext<'_>,
1179    output: &mut TokenStream,
1180) {
1181    let Some(proc_macro2::TokenTree::Group(query_group)) = iter.next() else {
1182        return;
1183    };
1184
1185    let Some(query) = AttrQuery::parse(query_group.stream()) else {
1186        return;
1187    };
1188
1189    let Some(variant) = ctx.current_variant() else {
1190        return;
1191    };
1192
1193    use facet_macro_parse::{IdentOrLiteral, PVariantKind};
1194
1195    match &variant.kind {
1196        PVariantKind::Unit => {
1197            // No pattern needed for unit variants
1198        }
1199        PVariantKind::Tuple { fields } => {
1200            let patterns: Vec<_> = fields
1201                .iter()
1202                .enumerate()
1203                .map(|(i, f)| {
1204                    if query.find_in(&f.attrs.facet).is_some() {
1205                        let name = quote::format_ident!("v{}", i);
1206                        quote! { #name }
1207                    } else {
1208                        quote! { _ }
1209                    }
1210                })
1211                .collect();
1212            output.extend(quote! { ( #(#patterns),* ) });
1213        }
1214        PVariantKind::Struct { fields } => {
1215            let bindings: Vec<_> = fields
1216                .iter()
1217                .filter_map(|f| {
1218                    if let IdentOrLiteral::Ident(id) = &f.name.raw {
1219                        if query.find_in(&f.attrs.facet).is_some() {
1220                            Some(quote! { #id })
1221                        } else {
1222                            Some(quote! { #id: _ })
1223                        }
1224                    } else {
1225                        None
1226                    }
1227                })
1228                .collect();
1229            output.extend(quote! { { #(#bindings),* } });
1230        }
1231    }
1232}
1233
1234fn emit_field_name(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1235    let Some((field, index)) = ctx.current_field() else {
1236        return;
1237    };
1238
1239    use facet_macro_parse::IdentOrLiteral;
1240
1241    match &field.name.raw {
1242        IdentOrLiteral::Ident(id) => {
1243            output.extend(quote! { #id });
1244        }
1245        IdentOrLiteral::Literal(_) => {
1246            // Tuple field - use generated name matching @variant_pattern (v0, v1, etc.)
1247            let name = quote::format_ident!("v{}", index);
1248            output.extend(quote! { #name });
1249        }
1250    }
1251}
1252
1253fn emit_field_type(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1254    if let Some((field, _)) = ctx.current_field() {
1255        let ty = &field.ty;
1256        output.extend(quote! { #ty });
1257    }
1258}
1259
1260fn emit_field_expr(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1261    // Same as field_name for now, but could be different for self.field access
1262    emit_field_name(ctx, output);
1263}
1264
1265fn emit_attr_args(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1266    if let Some(attr) = ctx.current_attr() {
1267        let args = &attr.args;
1268        output.extend(args.clone());
1269    }
1270}
1271
1272fn emit_doc(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1273    let attrs = ctx.current_attrs();
1274    let doc = attrs.doc.join(" ").trim().to_string();
1275    if !doc.is_empty() {
1276        output.extend(quote! { #doc });
1277    }
1278}
1279
1280// ============================================================================
1281// DEFAULT-RELATED DIRECTIVES
1282// ============================================================================
1283
1284/// `@field_default_expr` - emit the default expression for the current field
1285///
1286/// Checks for:
1287/// - `#[facet(default = literal)]` (builtin) → `literal` (direct value)
1288/// - `#[facet(default)]` (builtin, no value) → `::core::default::Default::default()`
1289/// - `#[facet(default::value = literal)]` → `literal.into()`
1290/// - `#[facet(default::func = "path")]` → `path()`
1291/// - No attribute → `::core::default::Default::default()`
1292fn emit_field_default_expr(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1293    let Some((field, _)) = ctx.current_field() else {
1294        return;
1295    };
1296
1297    // Check for builtin #[facet(default = ...)] attribute (ns is None, key is "default")
1298    if let Some(attr) = field
1299        .attrs
1300        .facet
1301        .iter()
1302        .find(|a| a.ns.is_none() && a.key == "default")
1303    {
1304        let args = &attr.args;
1305        if args.is_empty() {
1306            // #[facet(default)] without value - use Default::default()
1307            output.extend(quote! { ::core::default::Default::default() });
1308        } else {
1309            // #[facet(default = value)] - emit the value directly
1310            output.extend(quote! { #args });
1311        }
1312        return;
1313    }
1314
1315    // No default attribute - use Default::default()
1316    output.extend(quote! { ::core::default::Default::default() });
1317}
1318
1319/// `@variant_default_construction` - emit the construction for a default variant
1320///
1321/// - Unit variant → nothing
1322/// - Tuple variant → (Default::default(), ...)
1323/// - Struct variant → { field: Default::default(), ... }
1324fn emit_variant_default_construction(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1325    use facet_macro_parse::{IdentOrLiteral, PVariantKind};
1326
1327    let Some(variant) = ctx.current_variant() else {
1328        return;
1329    };
1330
1331    match &variant.kind {
1332        PVariantKind::Unit => {
1333            // Nothing to emit
1334        }
1335        PVariantKind::Tuple { fields } => {
1336            let defaults: Vec<_> = fields.iter().map(field_default_tokens).collect();
1337            output.extend(quote! { ( #(#defaults),* ) });
1338        }
1339        PVariantKind::Struct { fields } => {
1340            let field_inits: Vec<_> = fields
1341                .iter()
1342                .filter_map(|f| {
1343                    if let IdentOrLiteral::Ident(name) = &f.name.raw {
1344                        let default_expr = field_default_tokens(f);
1345                        Some(quote! { #name: #default_expr })
1346                    } else {
1347                        None
1348                    }
1349                })
1350                .collect();
1351            output.extend(quote! { { #(#field_inits),* } });
1352        }
1353    }
1354}
1355
1356/// Helper to generate the default expression tokens for a field
1357fn field_default_tokens(field: &facet_macro_parse::PStructField) -> TokenStream {
1358    // Check for builtin #[facet(default = ...)] attribute (ns is None, key is "default")
1359    if let Some(attr) = field
1360        .attrs
1361        .facet
1362        .iter()
1363        .find(|a| a.ns.is_none() && a.key == "default")
1364    {
1365        let args = &attr.args;
1366        if args.is_empty() {
1367            // #[facet(default)] without value - use Default::default()
1368            return quote! { ::core::default::Default::default() };
1369        } else {
1370            // #[facet(default = value)] - emit the value directly
1371            return quote! { #args };
1372        }
1373    }
1374
1375    // No default attribute - use Default::default()
1376    quote! { ::core::default::Default::default() }
1377}
1378
1379// ============================================================================
1380// LEGACY DIRECTIVES (for backwards compatibility with existing facet-error)
1381// ============================================================================
1382
1383/// Legacy `@format_doc_comment` - emits doc comment as format string with args
1384fn emit_format_doc_comment(ctx: &EvalContext<'_>, output: &mut TokenStream) {
1385    use facet_macro_parse::PVariantKind;
1386
1387    let Some(variant) = ctx.current_variant() else {
1388        return;
1389    };
1390
1391    let doc = variant.attrs.doc.join(" ").trim().to_string();
1392    let format_str = if doc.is_empty() {
1393        variant.name.original.clone()
1394    } else {
1395        doc
1396    };
1397
1398    // Check if format string uses positional args like {0}
1399    match &variant.kind {
1400        PVariantKind::Unit => {
1401            output.extend(quote! { #format_str });
1402        }
1403        PVariantKind::Tuple { fields } => {
1404            if format_str.contains("{0}") {
1405                // Use v0, v1, etc. to match legacy @variant_pattern
1406                let field_names: Vec<_> = (0..fields.len())
1407                    .map(|i| quote::format_ident!("v{}", i))
1408                    .collect();
1409                output.extend(quote! { #format_str, #(#field_names),* });
1410            } else {
1411                output.extend(quote! { #format_str });
1412            }
1413        }
1414        PVariantKind::Struct { fields: _ } => {
1415            // For struct variants, Rust will resolve field names in format string
1416            output.extend(quote! { #format_str });
1417        }
1418    }
1419}
1420
1421// Grammar for parsing finalize sections
1422crate::unsynn! {
1423    /// Section marker like `@tokens`, `@plugins`
1424    struct FinalizeSectionMarker {
1425        _at: crate::At,
1426        name: Ident,
1427    }
1428
1429    /// A braced section like `@tokens { ... }`
1430    struct FinalizeSection {
1431        marker: FinalizeSectionMarker,
1432        content: crate::BraceGroupContaining<TokenStream>,
1433    }
1434}
1435
1436#[cfg(test)]
1437mod tests {
1438    use super::*;
1439    use crate::IParse;
1440    use quote::quote;
1441
1442    #[test]
1443    fn test_to_snake_case() {
1444        assert_eq!(to_snake_case("Error"), "error");
1445        assert_eq!(to_snake_case("Display"), "display");
1446        assert_eq!(to_snake_case("PartialEq"), "partial_eq");
1447        assert_eq!(to_snake_case("FromStr"), "from_str");
1448    }
1449
1450    #[test]
1451    fn test_extract_derive_plugins() {
1452        let input = quote! {
1453            #[derive(Facet, Debug)]
1454            #[facet(derive(Error))]
1455            #[repr(u8)]
1456            pub enum MyError {
1457                Disconnect(u32),
1458            }
1459        };
1460
1461        let mut iter = input.to_token_iter();
1462        let parsed = iter.parse::<crate::Enum>().expect("Failed to parse enum");
1463
1464        let plugins = extract_derive_plugins(&parsed.attributes);
1465        assert_eq!(plugins.len(), 1);
1466        assert!(matches!(&plugins[0], PluginRef::Simple(name) if name == "Error"));
1467    }
1468
1469    #[test]
1470    fn test_extract_multiple_plugins() {
1471        let input = quote! {
1472            #[facet(derive(Error, Display))]
1473            pub enum MyError {
1474                Unknown,
1475            }
1476        };
1477
1478        let mut iter = input.to_token_iter();
1479        let parsed = iter.parse::<crate::Enum>().expect("Failed to parse enum");
1480
1481        let plugins = extract_derive_plugins(&parsed.attributes);
1482        assert_eq!(plugins.len(), 2);
1483        assert!(matches!(&plugins[0], PluginRef::Simple(name) if name == "Error"));
1484        assert!(matches!(&plugins[1], PluginRef::Simple(name) if name == "Display"));
1485    }
1486
1487    #[test]
1488    fn test_extract_path_plugins() {
1489        let input = quote! {
1490            #[facet(derive(Error, facet_default::Default))]
1491            pub enum MyError {
1492                Unknown,
1493            }
1494        };
1495
1496        let mut iter = input.to_token_iter();
1497        let parsed = iter.parse::<crate::Enum>().expect("Failed to parse enum");
1498
1499        let plugins = extract_derive_plugins(&parsed.attributes);
1500        assert_eq!(plugins.len(), 2);
1501        assert!(matches!(&plugins[0], PluginRef::Simple(name) if name == "Error"));
1502        assert!(
1503            matches!(&plugins[1], PluginRef::Path { crate_name, plugin_name } if crate_name == "facet_default" && plugin_name == "Default")
1504        );
1505    }
1506
1507    #[test]
1508    fn test_plugin_ref_crate_path() {
1509        let simple = PluginRef::Simple("Error".to_string());
1510        assert_eq!(simple.crate_path().to_string(), ":: facet_error");
1511
1512        let path = PluginRef::Path {
1513            crate_name: "facet_default".to_string(),
1514            plugin_name: "Default".to_string(),
1515        };
1516        assert_eq!(path.crate_path().to_string(), ":: facet_default");
1517    }
1518
1519    /// Test for issue #1679: derive(Default) combined with other attributes on the same line
1520    #[test]
1521    fn test_extract_derive_plugins_combined_attrs() {
1522        // This is the failing case from the issue: derive(Default) combined with rename_all
1523        let input = quote! {
1524            #[derive(Debug, Facet)]
1525            #[facet(rename_all = "kebab-case", derive(Default))]
1526            struct PreCommitConfig {
1527                generate_readmes: bool,
1528            }
1529        };
1530
1531        let mut iter = input.to_token_iter();
1532        let parsed = iter
1533            .parse::<crate::Struct>()
1534            .expect("Failed to parse struct");
1535
1536        let plugins = extract_derive_plugins(&parsed.attributes);
1537        assert_eq!(
1538            plugins.len(),
1539            1,
1540            "should extract derive(Default) even when combined with other attrs"
1541        );
1542        assert!(matches!(&plugins[0], PluginRef::Simple(name) if name == "Default"));
1543    }
1544
1545    /// Test for issue #1679: strip_derive_attrs should strip only derive part, keeping other attrs
1546    #[test]
1547    fn test_strip_derive_attrs_combined() {
1548        // Input with derive(Default) combined with rename_all
1549        let input = quote! {
1550            #[derive(Debug, Facet)]
1551            #[facet(rename_all = "kebab-case", derive(Default))]
1552            struct PreCommitConfig {
1553                generate_readmes: bool,
1554            }
1555        };
1556
1557        let stripped = strip_derive_attrs(input);
1558        let stripped_str = stripped.to_string();
1559
1560        // Should keep #[derive(Debug, Facet)]
1561        assert!(
1562            stripped_str.contains("derive"),
1563            "should keep #[derive(Debug, Facet)]"
1564        );
1565
1566        // Should keep rename_all in the facet attribute
1567        assert!(
1568            stripped_str.contains("rename_all"),
1569            "should keep rename_all attribute"
1570        );
1571
1572        // Should NOT contain derive(Default) in facet attribute
1573        // The original has facet(rename_all = "kebab-case", derive(Default))
1574        // After stripping, it should have facet(rename_all = "kebab-case")
1575        assert!(
1576            !stripped_str.contains("facet (rename_all = \"kebab-case\" , derive (Default))"),
1577            "should strip derive(Default) from combined attribute"
1578        );
1579    }
1580
1581    /// Test strip_derive_attrs with only derive in facet attribute
1582    #[test]
1583    fn test_strip_derive_attrs_only_derive() {
1584        let input = quote! {
1585            #[facet(derive(Default))]
1586            struct Foo {}
1587        };
1588
1589        let stripped = strip_derive_attrs(input);
1590        let stripped_str = stripped.to_string();
1591
1592        // The entire facet attribute should be stripped (or result in empty facet())
1593        // Since the facet attribute only contains derive(Default)
1594        assert!(
1595            !stripped_str.contains("derive (Default)"),
1596            "derive(Default) should be stripped"
1597        );
1598    }
1599}