facet_macros_impl/attr_grammar/
build_struct_fields.rs

1//! Implementation of `__build_struct_fields!` proc-macro.
2//!
3//! This proc-macro handles all struct field parsing in one shot,
4//! avoiding the need for recursive macro_rules calls.
5
6use proc_macro2::{Span, TokenStream as TokenStream2, TokenTree};
7use quote::quote;
8use quote::quote_spanned;
9use unsynn::*;
10
11/// Error with span information for better diagnostics
12struct SpannedError {
13    message: String,
14    span: Span,
15    /// Optional help text (from doc comment)
16    help: Option<String>,
17}
18
19// ============================================================================
20// UNSYNN TYPE DEFINITIONS
21// ============================================================================
22
23keyword! {
24    KKrate = "krate";
25    KEnumName = "enum_name";
26    KVariantName = "variant_name";
27    KStructName = "struct_name";
28    KFields = "fields";
29    KInput = "input";
30}
31
32operator! {
33    At = "@";
34    Col = ":";
35}
36
37unsynn! {
38    /// The complete input to __build_struct_fields
39    struct BuildStructFieldsInput {
40        krate_section: KrateSection,
41        enum_name_section: EnumNameSection,
42        variant_name_section: VariantNameSection,
43        struct_name_section: StructNameSection,
44        fields_section: FieldsSection,
45        input_section: InputSection,
46    }
47
48    /// @krate { ... }
49    struct KrateSection {
50        _at: At,
51        _kw: KKrate,
52        content: BraceGroup,
53    }
54
55    /// @enum_name { ... }
56    struct EnumNameSection {
57        _at: At,
58        _kw: KEnumName,
59        content: BraceGroupContaining<Ident>,
60    }
61
62    /// @variant_name { ... }
63    struct VariantNameSection {
64        _at: At,
65        _kw: KVariantName,
66        content: BraceGroupContaining<Ident>,
67    }
68
69    /// @struct_name { ... }
70    struct StructNameSection {
71        _at: At,
72        _kw: KStructName,
73        content: BraceGroupContaining<Ident>,
74    }
75
76    /// @fields { name: opt_string, primary_key: bool }
77    /// May include doc comments: #[doc = "..."] name: opt_string
78    struct FieldsSection {
79        _at: At,
80        _kw: KFields,
81        /// Raw tokens - parsed manually to extract doc comments
82        content: BraceGroup,
83    }
84
85    /// @input { ... }
86    struct InputSection {
87        _at: At,
88        _kw: KInput,
89        content: BraceGroup,
90    }
91}
92
93// ============================================================================
94// PARSED STRUCTURES
95// ============================================================================
96
97struct ParsedBuildInput {
98    krate_path: TokenStream2,
99    enum_name: Ident,
100    variant_name: Ident,
101    struct_name: Ident,
102    fields: Vec<ParsedFieldDef>,
103    input: TokenStream2,
104}
105
106#[derive(Clone)]
107struct ParsedFieldDef {
108    name: Ident,
109    kind: FieldKind,
110    /// Doc comment for help text in errors
111    doc: Option<String>,
112}
113
114#[derive(Clone, Copy, PartialEq)]
115enum FieldKind {
116    Bool,
117    String,
118    OptString,
119    OptBool,
120    OptChar,
121    I64,
122    OptI64,
123    ListString,
124    ListI64,
125    /// Bare identifier like `cascade` or `post` - captured as &'static str
126    Ident,
127}
128
129/// Parsed field value from input
130struct ParsedField {
131    name: String,
132    #[allow(dead_code)]
133    name_span: Span,
134    value: FieldValue,
135}
136
137enum FieldValue {
138    /// String literal: `name = "foo"`
139    String(String),
140    /// Bool literal: `primary_key = true`
141    Bool(bool),
142    /// Char literal: `short = 'v'`
143    Char(char),
144    /// Integer literal: `min = 0`
145    I64(i64),
146    /// List of strings: `columns = ["id", "name"]`
147    ListString(Vec<String>),
148    /// List of integers: `values = [1, 2, 3]`
149    ListI64(Vec<i64>),
150    /// Bare identifier: `method = post`
151    Ident(String),
152    /// Flag (no value): `primary_key`
153    Flag,
154}
155
156impl BuildStructFieldsInput {
157    fn to_parsed(&self) -> std::result::Result<ParsedBuildInput, String> {
158        let krate_path = self.krate_section.content.0.stream();
159        let enum_name = self.enum_name_section.content.content.clone();
160        let variant_name = self.variant_name_section.content.content.clone();
161        let struct_name = self.struct_name_section.content.content.clone();
162
163        // Parse fields manually to extract doc comments
164        let fields = parse_field_defs_with_docs(&self.fields_section.content.0.stream())?;
165
166        let input = self.input_section.content.0.stream();
167
168        Ok(ParsedBuildInput {
169            krate_path,
170            enum_name,
171            variant_name,
172            struct_name,
173            fields,
174            input,
175        })
176    }
177}
178
179/// Parse field definitions from token stream, extracting doc comments
180fn parse_field_defs_with_docs(
181    tokens: &TokenStream2,
182) -> std::result::Result<Vec<ParsedFieldDef>, String> {
183    let tokens: Vec<TokenTree> = tokens.clone().into_iter().collect();
184    let mut fields = Vec::new();
185    let mut i = 0;
186    let mut current_doc: Option<String> = None;
187
188    while i < tokens.len() {
189        // Skip commas
190        if let TokenTree::Punct(p) = &tokens[i]
191            && p.as_char() == ','
192        {
193            i += 1;
194            continue;
195        }
196
197        // Check for doc comment: #[doc = "..."]
198        if let TokenTree::Punct(p) = &tokens[i]
199            && p.as_char() == '#'
200            && i + 1 < tokens.len()
201            && let TokenTree::Group(g) = &tokens[i + 1]
202            && g.delimiter() == proc_macro2::Delimiter::Bracket
203            && let Some(doc) = extract_doc_from_attr(&g.stream())
204        {
205            // Accumulate doc comments (for multi-line)
206            let trimmed = doc.trim();
207            if let Some(existing) = &mut current_doc {
208                existing.push(' ');
209                existing.push_str(trimmed);
210            } else {
211                current_doc = Some(trimmed.to_string());
212            }
213            i += 2;
214            continue;
215        }
216
217        // Expect field: name: kind
218        let name = match &tokens[i] {
219            TokenTree::Ident(ident) => ident.clone(),
220            other => return Err(format!("expected field name, found `{other}`")),
221        };
222        i += 1;
223
224        // Expect colon
225        if i >= tokens.len() {
226            return Err(format!("expected `:` after field name `{name}`"));
227        }
228        if let TokenTree::Punct(p) = &tokens[i] {
229            if p.as_char() != ':' {
230                return Err(format!(
231                    "expected `:` after field name `{name}`, found `{p}`"
232                ));
233            }
234        } else {
235            return Err(format!("expected `:` after field name `{name}`"));
236        }
237        i += 1;
238
239        // Expect kind
240        if i >= tokens.len() {
241            return Err(format!("expected field kind after `{name}:`"));
242        }
243        let kind_ident = match &tokens[i] {
244            TokenTree::Ident(ident) => ident.clone(),
245            other => return Err(format!("expected field kind, found `{other}`")),
246        };
247        i += 1;
248
249        let kind_str = kind_ident.to_string();
250        let kind = match kind_str.as_str() {
251            "bool" => FieldKind::Bool,
252            "string" => FieldKind::String,
253            "opt_string" => FieldKind::OptString,
254            "opt_bool" => FieldKind::OptBool,
255            "opt_char" => FieldKind::OptChar,
256            "i64" => FieldKind::I64,
257            "opt_i64" => FieldKind::OptI64,
258            "list_string" => FieldKind::ListString,
259            "list_i64" => FieldKind::ListI64,
260            "ident" => FieldKind::Ident,
261            _ => return Err(format!("unknown field kind: {kind_str}")),
262        };
263
264        fields.push(ParsedFieldDef {
265            name,
266            kind,
267            doc: current_doc.take(),
268        });
269    }
270
271    Ok(fields)
272}
273
274/// Unescape a string with Rust-style escape sequences (e.g., `\"` -> `"`)
275fn unescape_string(s: &str) -> String {
276    let mut out = String::with_capacity(s.len());
277    let mut chars = s.chars().peekable();
278
279    while let Some(c) = chars.next() {
280        if c == '\\' {
281            match chars.next() {
282                Some('\\') => out.push('\\'),
283                Some('"') => out.push('"'),
284                Some('\'') => out.push('\''),
285                Some('n') => out.push('\n'),
286                Some('r') => out.push('\r'),
287                Some('t') => out.push('\t'),
288                Some('0') => out.push('\0'),
289                Some(other) => {
290                    // Unknown escape, keep as-is
291                    out.push('\\');
292                    out.push(other);
293                }
294                None => out.push('\\'),
295            }
296        } else {
297            out.push(c);
298        }
299    }
300    out
301}
302
303fn extract_doc_from_attr(tokens: &TokenStream2) -> Option<String> {
304    let tokens: Vec<TokenTree> = tokens.clone().into_iter().collect();
305
306    // Expected: doc = "..."
307    if tokens.len() >= 3
308        && let TokenTree::Ident(ident) = &tokens[0]
309        && *ident == "doc"
310        && let TokenTree::Punct(p) = &tokens[1]
311        && p.as_char() == '='
312        && let TokenTree::Literal(lit) = &tokens[2]
313    {
314        let lit_str = lit.to_string();
315        // Remove quotes and unescape
316        if lit_str.starts_with('"') && lit_str.ends_with('"') {
317            let inner = &lit_str[1..lit_str.len() - 1];
318            return Some(unescape_string(inner.trim_start()));
319        }
320    }
321    None
322}
323
324// ============================================================================
325// ENTRY POINT
326// ============================================================================
327
328/// Parses struct field definitions and their values in one pass, generating field initialization code with comprehensive error messages.
329pub fn build_struct_fields(input: TokenStream2) -> TokenStream2 {
330    let mut iter = input.to_token_iter();
331
332    let parsed_input: BuildStructFieldsInput = match iter.parse() {
333        Ok(i) => i,
334        Err(e) => {
335            let msg = e.to_string();
336            return quote! { compile_error!(#msg); };
337        }
338    };
339
340    let input = match parsed_input.to_parsed() {
341        Ok(i) => i,
342        Err(e) => {
343            return quote! { compile_error!(#e); };
344        }
345    };
346
347    match build_struct_fields_impl(&input) {
348        Ok(tokens) => tokens,
349        Err(err) => emit_error(err),
350    }
351}
352
353fn emit_error(err: SpannedError) -> TokenStream2 {
354    let message = err.message;
355    let span = err.span;
356    let help = err.help;
357
358    // Append help text to the message
359    let full_message = if let Some(help_text) = help {
360        format!("{message}\n  = help: {help_text}")
361    } else {
362        message
363    };
364    quote_spanned! { span => compile_error!(#full_message) }
365}
366
367fn build_struct_fields_impl(
368    input: &ParsedBuildInput,
369) -> std::result::Result<TokenStream2, SpannedError> {
370    let krate_path = &input.krate_path;
371    let enum_name = &input.enum_name;
372    let variant_name = &input.variant_name;
373    let struct_name = &input.struct_name;
374
375    // Parse all field assignments from input tokens
376    let parsed_fields = parse_input_fields(&input.input, &input.fields)?;
377
378    // Build the struct fields with values
379    let field_values: Vec<TokenStream2> = input
380        .fields
381        .iter()
382        .map(|field_def| {
383            let field_name = &field_def.name;
384            let field_name_str = field_name.to_string();
385
386            // Find if this field was set in input
387            let parsed = parsed_fields.iter().find(|p| p.name == field_name_str);
388
389            let value = match (parsed, field_def.kind) {
390                (Some(p), FieldKind::String) => match &p.value {
391                    FieldValue::String(s) => quote! { #s },
392                    _ => quote! { "" }, // Will error elsewhere
393                },
394                (Some(p), FieldKind::OptString) => match &p.value {
395                    FieldValue::String(s) => quote! { Some(#s) },
396                    _ => quote! { None },
397                },
398                (Some(p), FieldKind::Bool) => match &p.value {
399                    FieldValue::Bool(b) => quote! { #b },
400                    FieldValue::Flag => quote! { true },
401                    _ => quote! { false },
402                },
403                (Some(p), FieldKind::OptBool) => match &p.value {
404                    FieldValue::Bool(b) => quote! { Some(#b) },
405                    FieldValue::Flag => quote! { Some(true) },
406                    _ => quote! { None },
407                },
408                (Some(p), FieldKind::OptChar) => match &p.value {
409                    FieldValue::Char(c) => quote! { Some(#c) },
410                    _ => quote! { None },
411                },
412                (Some(p), FieldKind::I64) => match &p.value {
413                    FieldValue::I64(n) => quote! { #n },
414                    _ => quote! { 0 }, // Will error elsewhere
415                },
416                (Some(p), FieldKind::OptI64) => match &p.value {
417                    FieldValue::I64(n) => quote! { Some(#n) },
418                    _ => quote! { None },
419                },
420                (Some(p), FieldKind::ListString) => match &p.value {
421                    FieldValue::ListString(items) => quote! { &[#(#items),*] },
422                    _ => quote! { &[] },
423                },
424                (Some(p), FieldKind::ListI64) => match &p.value {
425                    FieldValue::ListI64(items) => quote! { &[#(#items),*] },
426                    _ => quote! { &[] },
427                },
428                (Some(p), FieldKind::Ident) => match &p.value {
429                    FieldValue::Ident(s) => quote! { #s },
430                    _ => quote! { "" },
431                },
432                (None, FieldKind::String) => quote! { "" },
433                (None, FieldKind::OptString) => quote! { None },
434                (None, FieldKind::Bool) => quote! { false },
435                (None, FieldKind::OptBool) => quote! { None },
436                (None, FieldKind::OptChar) => quote! { None },
437                (None, FieldKind::I64) => quote! { 0 },
438                (None, FieldKind::OptI64) => quote! { None },
439                (None, FieldKind::ListString) => quote! { &[] },
440                (None, FieldKind::ListI64) => quote! { &[] },
441                (None, FieldKind::Ident) => quote! { "" },
442            };
443
444            quote! { #field_name: #value }
445        })
446        .collect();
447
448    Ok(quote! {
449        #krate_path::#enum_name::#variant_name(#krate_path::#struct_name {
450            #(#field_values),*
451        })
452    })
453}
454
455fn parse_input_fields(
456    input: &TokenStream2,
457    field_defs: &[ParsedFieldDef],
458) -> std::result::Result<Vec<ParsedField>, SpannedError> {
459    let tokens: Vec<TokenTree> = input.clone().into_iter().collect();
460    let mut parsed = Vec::new();
461    let mut i = 0;
462
463    while i < tokens.len() {
464        // Skip commas
465        if let TokenTree::Punct(p) = &tokens[i]
466            && p.as_char() == ','
467        {
468            i += 1;
469            continue;
470        }
471
472        // Expect identifier (field name)
473        let field_name = match &tokens[i] {
474            TokenTree::Ident(ident) => ident.clone(),
475            other => {
476                return Err(SpannedError {
477                    message: format!("expected field name, found `{other}`"),
478                    span: other.span(),
479                    help: None,
480                });
481            }
482        };
483        let field_name_str = field_name.to_string();
484        let field_span = field_name.span();
485        i += 1;
486
487        // Check for duplicate field
488        if parsed
489            .iter()
490            .any(|p: &ParsedField| p.name == field_name_str)
491        {
492            return Err(SpannedError {
493                message: format!(
494                    "duplicate field `{field_name_str}`; each field can only be specified once"
495                ),
496                span: field_span,
497                help: None,
498            });
499        }
500
501        // Find field definition
502        let field_def = field_defs.iter().find(|f| f.name == field_name_str);
503        if field_def.is_none() {
504            // Unknown field - generate helpful error
505            let known_names: Vec<_> = field_defs.iter().map(|f| f.name.to_string()).collect();
506            let suggestion = find_closest(&field_name_str, &known_names);
507            let msg = if let Some(s) = suggestion {
508                format!(
509                    "unknown field `{}`; did you mean `{}`? Known fields: {}",
510                    field_name_str,
511                    s,
512                    known_names.join(", ")
513                )
514            } else {
515                format!(
516                    "unknown field `{}`; known fields: {}",
517                    field_name_str,
518                    known_names.join(", ")
519                )
520            };
521            return Err(SpannedError {
522                message: msg,
523                span: field_span,
524                help: None,
525            });
526        }
527        let field_def = field_def.unwrap();
528
529        // Check what follows: `=` or nothing (flag) or `,` (flag)
530        if i >= tokens.len() {
531            // End of input - this is a flag
532            match field_def.kind {
533                FieldKind::Bool | FieldKind::OptBool => {
534                    parsed.push(ParsedField {
535                        name: field_name_str,
536                        name_span: field_span,
537                        value: FieldValue::Flag,
538                    });
539                }
540                FieldKind::String | FieldKind::OptString => {
541                    return Err(SpannedError {
542                        message: format!(
543                            "`{field_name_str}` requires a string value: `{field_name_str} = \"value\"`"
544                        ),
545                        span: field_span,
546                        help: field_def.doc.clone(),
547                    });
548                }
549                FieldKind::OptChar => {
550                    return Err(SpannedError {
551                        message: format!(
552                            "`{field_name_str}` requires a char value: `{field_name_str} = 'v'`"
553                        ),
554                        span: field_span,
555                        help: field_def.doc.clone(),
556                    });
557                }
558                FieldKind::I64 | FieldKind::OptI64 => {
559                    return Err(SpannedError {
560                        message: format!(
561                            "`{field_name_str}` requires an integer value: `{field_name_str} = 42`"
562                        ),
563                        span: field_span,
564                        help: field_def.doc.clone(),
565                    });
566                }
567                FieldKind::ListString => {
568                    return Err(SpannedError {
569                        message: format!(
570                            "`{field_name_str}` requires a list value: `{field_name_str} = [\"a\", \"b\"]`"
571                        ),
572                        span: field_span,
573                        help: field_def.doc.clone(),
574                    });
575                }
576                FieldKind::ListI64 => {
577                    return Err(SpannedError {
578                        message: format!(
579                            "`{field_name_str}` requires a list value: `{field_name_str} = [1, 2, 3]`"
580                        ),
581                        span: field_span,
582                        help: field_def.doc.clone(),
583                    });
584                }
585                FieldKind::Ident => {
586                    return Err(SpannedError {
587                        message: format!(
588                            "`{field_name_str}` requires an identifier value: `{field_name_str} = some_value`"
589                        ),
590                        span: field_span,
591                        help: field_def.doc.clone(),
592                    });
593                }
594            }
595            continue;
596        }
597
598        // Check for `=`
599        if let TokenTree::Punct(p) = &tokens[i] {
600            if p.as_char() == '=' {
601                i += 1;
602                // Parse value
603                if i >= tokens.len() {
604                    return Err(SpannedError {
605                        message: format!("`{field_name_str}` requires a value after `=`"),
606                        span: field_span,
607                        help: field_def.doc.clone(),
608                    });
609                }
610
611                let value_token = &tokens[i];
612                i += 1;
613
614                match field_def.kind {
615                    FieldKind::String | FieldKind::OptString => {
616                        // Expect string literal
617                        if let TokenTree::Literal(lit) = value_token {
618                            let lit_str = lit.to_string();
619                            // Remove quotes
620                            if lit_str.starts_with('\"') && lit_str.ends_with('\"') {
621                                let inner = lit_str[1..lit_str.len() - 1].to_string();
622                                parsed.push(ParsedField {
623                                    name: field_name_str,
624                                    name_span: field_span,
625                                    value: FieldValue::String(inner),
626                                });
627                            } else {
628                                return Err(SpannedError {
629                                    message: format!(
630                                        "`{field_name_str}` expects a string literal: `{field_name_str} = \"value\"`"
631                                    ),
632                                    span: value_token.span(),
633                                    help: field_def.doc.clone(),
634                                });
635                            }
636                        } else if let TokenTree::Ident(ident) = value_token {
637                            // Common mistake: using an identifier instead of a string
638                            return Err(SpannedError {
639                                message: format!(
640                                    "`{field_name_str}` expects a string literal, not an identifier; \
641                                     try `{field_name_str} = \"{ident}\"` (with quotes)"
642                                ),
643                                span: value_token.span(),
644                                help: field_def.doc.clone(),
645                            });
646                        } else {
647                            return Err(SpannedError {
648                                message: format!(
649                                    "`{field_name_str}` expects a string literal: `{field_name_str} = \"value\"`"
650                                ),
651                                span: value_token.span(),
652                                help: field_def.doc.clone(),
653                            });
654                        }
655                    }
656                    FieldKind::Bool | FieldKind::OptBool => {
657                        // Expect true/false
658                        if let TokenTree::Ident(ident) = value_token {
659                            let ident_str = ident.to_string();
660                            match ident_str.as_str() {
661                                "true" => {
662                                    parsed.push(ParsedField {
663                                        name: field_name_str,
664                                        name_span: field_span,
665                                        value: FieldValue::Bool(true),
666                                    });
667                                }
668                                "false" => {
669                                    parsed.push(ParsedField {
670                                        name: field_name_str,
671                                        name_span: field_span,
672                                        value: FieldValue::Bool(false),
673                                    });
674                                }
675                                _ => {
676                                    return Err(SpannedError {
677                                        message: format!(
678                                            "`{field_name_str}` expects `true` or `false`: `{field_name_str} = true`"
679                                        ),
680                                        span: value_token.span(),
681                                        help: field_def.doc.clone(),
682                                    });
683                                }
684                            }
685                        } else if let TokenTree::Literal(lit) = value_token {
686                            // Common mistake: using a string instead of bool
687                            let lit_str = lit.to_string();
688                            if lit_str.starts_with('"') && lit_str.ends_with('"') {
689                                let inner = &lit_str[1..lit_str.len() - 1];
690                                // Check if the string content looks like a bool
691                                let suggestion = match inner {
692                                    "true" | "yes" | "1" | "on" => "true",
693                                    "false" | "no" | "0" | "off" => "false",
694                                    _ => "true",
695                                };
696                                return Err(SpannedError {
697                                    message: format!(
698                                        "`{field_name_str}` expects `true` or `false`, not a string; \
699                                         try `{field_name_str} = {suggestion}` (without quotes)"
700                                    ),
701                                    span: value_token.span(),
702                                    help: field_def.doc.clone(),
703                                });
704                            }
705                            return Err(SpannedError {
706                                message: format!(
707                                    "`{field_name_str}` expects `true` or `false`: `{field_name_str} = true`"
708                                ),
709                                span: value_token.span(),
710                                help: field_def.doc.clone(),
711                            });
712                        } else {
713                            return Err(SpannedError {
714                                message: format!(
715                                    "`{field_name_str}` expects `true` or `false`: `{field_name_str} = true`"
716                                ),
717                                span: value_token.span(),
718                                help: field_def.doc.clone(),
719                            });
720                        }
721                    }
722                    FieldKind::OptChar => {
723                        // Expect char literal: 'v'
724                        if let TokenTree::Literal(lit) = value_token {
725                            let lit_str = lit.to_string();
726                            // Check for char literal format: 'x'
727                            if lit_str.starts_with('\'')
728                                && lit_str.ends_with('\'')
729                                && lit_str.len() >= 3
730                            {
731                                // Parse the char (handling escape sequences)
732                                let inner = &lit_str[1..lit_str.len() - 1];
733                                let c = if inner.starts_with('\\') {
734                                    // Handle escape sequences
735                                    match inner.chars().nth(1) {
736                                        Some('n') => '\n',
737                                        Some('r') => '\r',
738                                        Some('t') => '\t',
739                                        Some('\\') => '\\',
740                                        Some('\'') => '\'',
741                                        Some('0') => '\0',
742                                        Some(c) => c,
743                                        None => {
744                                            return Err(SpannedError {
745                                                message: format!(
746                                                    "`{field_name_str}` has invalid escape sequence in char literal"
747                                                ),
748                                                span: value_token.span(),
749                                                help: field_def.doc.clone(),
750                                            });
751                                        }
752                                    }
753                                } else {
754                                    inner.chars().next().unwrap_or(' ')
755                                };
756                                parsed.push(ParsedField {
757                                    name: field_name_str,
758                                    name_span: field_span,
759                                    value: FieldValue::Char(c),
760                                });
761                            } else {
762                                return Err(SpannedError {
763                                    message: format!(
764                                        "`{field_name_str}` expects a char literal: `{field_name_str} = 'v'`"
765                                    ),
766                                    span: value_token.span(),
767                                    help: field_def.doc.clone(),
768                                });
769                            }
770                        } else if let TokenTree::Ident(ident) = value_token {
771                            // Common mistake: using an identifier instead of char
772                            return Err(SpannedError {
773                                message: format!(
774                                    "`{field_name_str}` expects a char literal, not an identifier; \
775                                     try `{field_name_str} = '{ident}'` (with single quotes)"
776                                ),
777                                span: value_token.span(),
778                                help: field_def.doc.clone(),
779                            });
780                        } else {
781                            return Err(SpannedError {
782                                message: format!(
783                                    "`{field_name_str}` expects a char literal: `{field_name_str} = 'v'`"
784                                ),
785                                span: value_token.span(),
786                                help: field_def.doc.clone(),
787                            });
788                        }
789                    }
790                    FieldKind::I64 | FieldKind::OptI64 => {
791                        // Expect integer literal (possibly negative)
792                        let (value, advance) =
793                            parse_integer_value(&tokens[i - 1..], value_token, &field_name_str)?;
794                        parsed.push(ParsedField {
795                            name: field_name_str,
796                            name_span: field_span,
797                            value: FieldValue::I64(value),
798                        });
799                        // Advance past any additional tokens consumed (for negative numbers)
800                        i += advance;
801                    }
802                    FieldKind::ListString => {
803                        // Expect bracket group with string literals: ["a", "b"]
804                        if let TokenTree::Group(g) = value_token {
805                            if g.delimiter() == proc_macro2::Delimiter::Bracket {
806                                let items = parse_string_list(&g.stream())?;
807                                parsed.push(ParsedField {
808                                    name: field_name_str,
809                                    name_span: field_span,
810                                    value: FieldValue::ListString(items),
811                                });
812                            } else {
813                                // Common mistake: wrong bracket type
814                                let bracket_name = match g.delimiter() {
815                                    proc_macro2::Delimiter::Brace => "curly braces `{}`",
816                                    proc_macro2::Delimiter::Parenthesis => "parentheses `()`",
817                                    _ => "wrong delimiters",
818                                };
819                                return Err(SpannedError {
820                                    message: format!(
821                                        "`{field_name_str}` expects square brackets `[]`, not {bracket_name}; \
822                                         try `{field_name_str} = [\"a\", \"b\"]`"
823                                    ),
824                                    span: value_token.span(),
825                                    help: field_def.doc.clone(),
826                                });
827                            }
828                        } else if let TokenTree::Literal(lit) = value_token {
829                            // Common mistake: single string instead of list
830                            let lit_str = lit.to_string();
831                            if lit_str.starts_with('"') && lit_str.ends_with('"') {
832                                let inner = &lit_str[1..lit_str.len() - 1];
833                                return Err(SpannedError {
834                                    message: format!(
835                                        "`{field_name_str}` expects a list, not a single string; \
836                                         try `{field_name_str} = [\"{inner}\"]`"
837                                    ),
838                                    span: value_token.span(),
839                                    help: field_def.doc.clone(),
840                                });
841                            }
842                            return Err(SpannedError {
843                                message: format!(
844                                    "`{field_name_str}` expects a list: `{field_name_str} = [\"a\", \"b\"]`"
845                                ),
846                                span: value_token.span(),
847                                help: field_def.doc.clone(),
848                            });
849                        } else {
850                            return Err(SpannedError {
851                                message: format!(
852                                    "`{field_name_str}` expects a list: `{field_name_str} = [\"a\", \"b\"]`"
853                                ),
854                                span: value_token.span(),
855                                help: field_def.doc.clone(),
856                            });
857                        }
858                    }
859                    FieldKind::ListI64 => {
860                        // Expect bracket group with integer literals: [1, 2, 3]
861                        if let TokenTree::Group(g) = value_token {
862                            if g.delimiter() == proc_macro2::Delimiter::Bracket {
863                                let items = parse_i64_list(&g.stream())?;
864                                parsed.push(ParsedField {
865                                    name: field_name_str,
866                                    name_span: field_span,
867                                    value: FieldValue::ListI64(items),
868                                });
869                            } else {
870                                // Common mistake: wrong bracket type
871                                let bracket_name = match g.delimiter() {
872                                    proc_macro2::Delimiter::Brace => "curly braces `{}`",
873                                    proc_macro2::Delimiter::Parenthesis => "parentheses `()`",
874                                    _ => "wrong delimiters",
875                                };
876                                return Err(SpannedError {
877                                    message: format!(
878                                        "`{field_name_str}` expects square brackets `[]`, not {bracket_name}; \
879                                         try `{field_name_str} = [1, 2, 3]`"
880                                    ),
881                                    span: value_token.span(),
882                                    help: field_def.doc.clone(),
883                                });
884                            }
885                        } else if let TokenTree::Literal(lit) = value_token {
886                            // Common mistake: single number instead of list
887                            let lit_str = lit.to_string();
888                            if lit_str.chars().all(|c| c.is_ascii_digit() || c == '-') {
889                                return Err(SpannedError {
890                                    message: format!(
891                                        "`{field_name_str}` expects a list, not a single value; \
892                                         try `{field_name_str} = [{lit_str}]`"
893                                    ),
894                                    span: value_token.span(),
895                                    help: field_def.doc.clone(),
896                                });
897                            }
898                            return Err(SpannedError {
899                                message: format!(
900                                    "`{field_name_str}` expects a list: `{field_name_str} = [1, 2, 3]`"
901                                ),
902                                span: value_token.span(),
903                                help: field_def.doc.clone(),
904                            });
905                        } else {
906                            return Err(SpannedError {
907                                message: format!(
908                                    "`{field_name_str}` expects a list: `{field_name_str} = [1, 2, 3]`"
909                                ),
910                                span: value_token.span(),
911                                help: field_def.doc.clone(),
912                            });
913                        }
914                    }
915                    FieldKind::Ident => {
916                        // Expect bare identifier: method = post
917                        if let TokenTree::Ident(ident) = value_token {
918                            parsed.push(ParsedField {
919                                name: field_name_str,
920                                name_span: field_span,
921                                value: FieldValue::Ident(ident.to_string()),
922                            });
923                        } else if let TokenTree::Literal(lit) = value_token {
924                            // Common mistake: using a string instead of identifier
925                            let lit_str = lit.to_string();
926                            if lit_str.starts_with('"') && lit_str.ends_with('"') {
927                                let inner = &lit_str[1..lit_str.len() - 1];
928                                return Err(SpannedError {
929                                    message: format!(
930                                        "`{field_name_str}` expects a bare identifier, not a string; \
931                                         try `{field_name_str} = {inner}` (without quotes)"
932                                    ),
933                                    span: value_token.span(),
934                                    help: field_def.doc.clone(),
935                                });
936                            }
937                            return Err(SpannedError {
938                                message: format!(
939                                    "`{field_name_str}` expects an identifier: `{field_name_str} = some_value`"
940                                ),
941                                span: value_token.span(),
942                                help: field_def.doc.clone(),
943                            });
944                        } else {
945                            return Err(SpannedError {
946                                message: format!(
947                                    "`{field_name_str}` expects an identifier: `{field_name_str} = some_value`"
948                                ),
949                                span: value_token.span(),
950                                help: field_def.doc.clone(),
951                            });
952                        }
953                    }
954                }
955            } else if p.as_char() == ',' {
956                // Flag followed by comma
957                match field_def.kind {
958                    FieldKind::Bool | FieldKind::OptBool => {
959                        parsed.push(ParsedField {
960                            name: field_name_str,
961                            name_span: field_span,
962                            value: FieldValue::Flag,
963                        });
964                    }
965                    FieldKind::String | FieldKind::OptString => {
966                        return Err(SpannedError {
967                            message: format!(
968                                "`{field_name_str}` requires a string value: `{field_name_str} = \"value\"`"
969                            ),
970                            span: field_span,
971                            help: field_def.doc.clone(),
972                        });
973                    }
974                    FieldKind::OptChar => {
975                        return Err(SpannedError {
976                            message: format!(
977                                "`{field_name_str}` requires a char value: `{field_name_str} = 'v'`"
978                            ),
979                            span: field_span,
980                            help: field_def.doc.clone(),
981                        });
982                    }
983                    FieldKind::I64 | FieldKind::OptI64 => {
984                        return Err(SpannedError {
985                            message: format!(
986                                "`{field_name_str}` requires an integer value: `{field_name_str} = 42`"
987                            ),
988                            span: field_span,
989                            help: field_def.doc.clone(),
990                        });
991                    }
992                    FieldKind::ListString => {
993                        return Err(SpannedError {
994                            message: format!(
995                                "`{field_name_str}` requires a list value: `{field_name_str} = [\"a\", \"b\"]`"
996                            ),
997                            span: field_span,
998                            help: field_def.doc.clone(),
999                        });
1000                    }
1001                    FieldKind::ListI64 => {
1002                        return Err(SpannedError {
1003                            message: format!(
1004                                "`{field_name_str}` requires a list value: `{field_name_str} = [1, 2, 3]`"
1005                            ),
1006                            span: field_span,
1007                            help: field_def.doc.clone(),
1008                        });
1009                    }
1010                    FieldKind::Ident => {
1011                        return Err(SpannedError {
1012                            message: format!(
1013                                "`{field_name_str}` requires an identifier value: `{field_name_str} = some_value`"
1014                            ),
1015                            span: field_span,
1016                            help: field_def.doc.clone(),
1017                        });
1018                    }
1019                }
1020                i += 1;
1021            } else {
1022                return Err(SpannedError {
1023                    message: format!("expected `=` or `,` after field name `{field_name_str}`"),
1024                    span: p.span(),
1025                    help: None,
1026                });
1027            }
1028        } else {
1029            // No `=` and not end - check if it's another identifier (next field)
1030            // This means current field is a flag
1031            match field_def.kind {
1032                FieldKind::Bool | FieldKind::OptBool => {
1033                    parsed.push(ParsedField {
1034                        name: field_name_str,
1035                        name_span: field_span,
1036                        value: FieldValue::Flag,
1037                    });
1038                }
1039                FieldKind::String | FieldKind::OptString => {
1040                    return Err(SpannedError {
1041                        message: format!(
1042                            "`{field_name_str}` requires a string value: `{field_name_str} = \"value\"`"
1043                        ),
1044                        span: field_span,
1045                        help: field_def.doc.clone(),
1046                    });
1047                }
1048                FieldKind::OptChar => {
1049                    return Err(SpannedError {
1050                        message: format!(
1051                            "`{field_name_str}` requires a char value: `{field_name_str} = 'v'`"
1052                        ),
1053                        span: field_span,
1054                        help: field_def.doc.clone(),
1055                    });
1056                }
1057                FieldKind::I64 | FieldKind::OptI64 => {
1058                    return Err(SpannedError {
1059                        message: format!(
1060                            "`{field_name_str}` requires an integer value: `{field_name_str} = 42`"
1061                        ),
1062                        span: field_span,
1063                        help: field_def.doc.clone(),
1064                    });
1065                }
1066                FieldKind::ListString => {
1067                    return Err(SpannedError {
1068                        message: format!(
1069                            "`{field_name_str}` requires a list value: `{field_name_str} = [\"a\", \"b\"]`"
1070                        ),
1071                        span: field_span,
1072                        help: field_def.doc.clone(),
1073                    });
1074                }
1075                FieldKind::ListI64 => {
1076                    return Err(SpannedError {
1077                        message: format!(
1078                            "`{field_name_str}` requires a list value: `{field_name_str} = [1, 2, 3]`"
1079                        ),
1080                        span: field_span,
1081                        help: field_def.doc.clone(),
1082                    });
1083                }
1084                FieldKind::Ident => {
1085                    return Err(SpannedError {
1086                        message: format!(
1087                            "`{field_name_str}` requires an identifier value: `{field_name_str} = some_value`"
1088                        ),
1089                        span: field_span,
1090                        help: field_def.doc.clone(),
1091                    });
1092                }
1093            }
1094        }
1095    }
1096
1097    Ok(parsed)
1098}
1099
1100/// Parse an integer value, handling optional negative sign
1101fn parse_integer_value(
1102    tokens: &[TokenTree],
1103    value_token: &TokenTree,
1104    field_name: &str,
1105) -> std::result::Result<(i64, usize), SpannedError> {
1106    // Check if the value token is a negative sign
1107    if let TokenTree::Punct(p) = value_token
1108        && p.as_char() == '-'
1109    {
1110        // Next token should be the number
1111        if tokens.len() > 1
1112            && let TokenTree::Literal(lit) = &tokens[1]
1113        {
1114            let lit_str = lit.to_string();
1115            if let Ok(n) = lit_str.parse::<i64>() {
1116                return Ok((-n, 1)); // Consumed one extra token
1117            }
1118        }
1119        return Err(SpannedError {
1120            message: "expected integer literal after `-`".to_string(),
1121            span: p.span(),
1122            help: None,
1123        });
1124    }
1125
1126    // Regular positive integer
1127    if let TokenTree::Literal(lit) = value_token {
1128        let lit_str = lit.to_string();
1129        // Try to parse as integer
1130        if let Ok(n) = lit_str.parse::<i64>() {
1131            return Ok((n, 0));
1132        }
1133        // Also try parsing with suffix (like 0i64)
1134        let cleaned = lit_str.trim_end_matches(|c: char| c.is_alphabetic() || c == '_');
1135        if let Ok(n) = cleaned.parse::<i64>() {
1136            return Ok((n, 0));
1137        }
1138        // Check if it looks like a number that overflowed
1139        if cleaned.chars().all(|c| c.is_ascii_digit()) {
1140            return Err(SpannedError {
1141                message: format!(
1142                    "`{}` value `{}` is too large; this field accepts i64 (range {} to {})",
1143                    field_name,
1144                    cleaned,
1145                    i64::MIN,
1146                    i64::MAX
1147                ),
1148                span: lit.span(),
1149                help: None,
1150            });
1151        }
1152        return Err(SpannedError {
1153            message: format!("`{field_name}` expected integer literal, got `{lit_str}`"),
1154            span: lit.span(),
1155            help: None,
1156        });
1157    }
1158
1159    Err(SpannedError {
1160        message: format!("`{field_name}` expected integer literal, got `{value_token}`"),
1161        span: value_token.span(),
1162        help: None,
1163    })
1164}
1165
1166/// Parse a list of string literals from bracket contents: "a", "b", "c"
1167fn parse_string_list(stream: &TokenStream2) -> std::result::Result<Vec<String>, SpannedError> {
1168    let tokens: Vec<TokenTree> = stream.clone().into_iter().collect();
1169    let mut items = Vec::new();
1170
1171    let mut i = 0;
1172    while i < tokens.len() {
1173        // Skip commas
1174        if let TokenTree::Punct(p) = &tokens[i]
1175            && p.as_char() == ','
1176        {
1177            i += 1;
1178            continue;
1179        }
1180
1181        // Expect string literal
1182        if let TokenTree::Literal(lit) = &tokens[i] {
1183            let lit_str = lit.to_string();
1184            if lit_str.starts_with('\"') && lit_str.ends_with('\"') {
1185                let inner = lit_str[1..lit_str.len() - 1].to_string();
1186                items.push(inner);
1187                i += 1;
1188            } else {
1189                return Err(SpannedError {
1190                    message: format!("expected string literal in list, got `{lit_str}`"),
1191                    span: lit.span(),
1192                    help: None,
1193                });
1194            }
1195        } else {
1196            return Err(SpannedError {
1197                message: format!("expected string literal in list, got `{}`", tokens[i]),
1198                span: tokens[i].span(),
1199                help: None,
1200            });
1201        }
1202    }
1203
1204    Ok(items)
1205}
1206
1207/// Parse a list of integer literals from bracket contents: 1, 2, 3
1208fn parse_i64_list(stream: &TokenStream2) -> std::result::Result<Vec<i64>, SpannedError> {
1209    let tokens: Vec<TokenTree> = stream.clone().into_iter().collect();
1210    let mut items = Vec::new();
1211
1212    let mut i = 0;
1213    while i < tokens.len() {
1214        // Skip commas
1215        if let TokenTree::Punct(p) = &tokens[i] {
1216            if p.as_char() == ',' {
1217                i += 1;
1218                continue;
1219            }
1220            // Handle negative numbers
1221            if p.as_char() == '-' {
1222                if i + 1 < tokens.len()
1223                    && let TokenTree::Literal(lit) = &tokens[i + 1]
1224                {
1225                    let lit_str = lit.to_string();
1226                    if let Ok(n) = lit_str.parse::<i64>() {
1227                        items.push(-n);
1228                        i += 2;
1229                        continue;
1230                    }
1231                }
1232                return Err(SpannedError {
1233                    message: "expected integer after `-`".to_string(),
1234                    span: p.span(),
1235                    help: None,
1236                });
1237            }
1238        }
1239
1240        // Expect integer literal
1241        if let TokenTree::Literal(lit) = &tokens[i] {
1242            let lit_str = lit.to_string();
1243            if let Ok(n) = lit_str.parse::<i64>() {
1244                items.push(n);
1245                i += 1;
1246            } else {
1247                // Try stripping suffix
1248                let cleaned = lit_str.trim_end_matches(|c: char| c.is_alphabetic() || c == '_');
1249                if let Ok(n) = cleaned.parse::<i64>() {
1250                    items.push(n);
1251                    i += 1;
1252                } else {
1253                    return Err(SpannedError {
1254                        message: format!("expected integer literal in list, got `{lit_str}`"),
1255                        span: lit.span(),
1256                        help: None,
1257                    });
1258                }
1259            }
1260        } else {
1261            return Err(SpannedError {
1262                message: format!("expected integer literal in list, got `{}`", tokens[i]),
1263                span: tokens[i].span(),
1264                help: None,
1265            });
1266        }
1267    }
1268
1269    Ok(items)
1270}
1271
1272#[cfg(feature = "helpful-derive")]
1273fn find_closest<'a>(target: &str, candidates: &'a [String]) -> Option<&'a str> {
1274    candidates
1275        .iter()
1276        .filter_map(|c| {
1277            let dist = strsim::levenshtein(target, c);
1278            if dist <= 3 {
1279                Some((c.as_str(), dist))
1280            } else {
1281                None
1282            }
1283        })
1284        .min_by_key(|(_, d)| *d)
1285        .map(|(s, _)| s)
1286}
1287
1288#[cfg(not(feature = "helpful-derive"))]
1289fn find_closest<'a>(_target: &str, _candidates: &'a [String]) -> Option<&'a str> {
1290    None
1291}