Skip to main content

struct_path/
lib.rs

1//! A helper macros implementation to build a string that represents struct fields path at compile time.
2//!
3//! Library provides a tiny macro implementation to reference Rust struct fields at compile time to represent its string format.
4//! This is needed to work with JSON paths, and some others protocols when we still want to rely on the compiler to avoid inconsistent changes.
5//!
6//! Features:
7//! - Fast and no macro parsing without huge deps;
8//! - Macro produces the code to verify if the specified path really exists;
9//! - Multiple fields/arrays support
10//! - Optional camelCase and PascalCase conversion support;
11//! - Optional delimiter parameter;
12//! - `#[derive(StructPath)]` to return all of a struct's declared fields via `Type::*`, without listing them by hand;
13//! - `Type::*` nested under a field path, `Parent::child.(Child::*)`, to prefix every one of `Child`'s fields with `child`;
14//!
15//! Example:
16//!
17//! ```rust,no_run
18//! use struct_path::*;
19//!
20//! fn example() {
21//!
22//! pub struct TestStructParent {
23//!     pub value_str: String,
24//!     pub value_num: u64,
25//!     pub value_child: TestStructChild,
26//!     pub opt_value_child: Option<TestStructChild>,
27//! }
28//!
29//! pub struct TestStructChild {
30//!     pub child_value_str: String,
31//!     pub child_value_num: u64,
32//! }
33//!
34//!// returns "value_str"
35//!let s1: &str = path!(TestStructParent::value_str);
36//!
37//!// returns "value_child.child_value_str"
38//!let s2: &str = path!(TestStructParent::value_child.child_value_str) ;
39//!
40//!// returns also "value_child.child_value_str"
41//!let s3: &str = path!(TestStructParent::value_child,TestStructChild::child_value_str);
42//!
43//!// options, returns "valueChild/childValueStr"
44//!let s4: &str = path!(TestStructParent::value_child.child_value_str; delim="/", case="camel") ;
45//!
46//!// `~` steps through a collection field (Vec, Option, ...) while still
47//!// verifying the path; returns "optValueChild/childValueStr"
48//!let s5: &str = path!(TestStructParent::opt_value_child~child_value_str; delim="/", case="camel") ;
49//!
50//!// returns ["value_str", "value_num"]
51//!let arr: [&str; 2] = paths!(TestStructParent::{ value_str, value_num });
52//!
53//! }
54//!
55//! ```
56//!
57//! All fields with `Type::*`, needing `#[derive(StructPath)]`; none of the calls above need it.
58//!
59//! ```rust,no_run
60//! use struct_path::*;
61//!
62//! fn example_all_fields() {
63//!
64//! #[derive(StructPath)]
65//! pub struct TestStructWithPrivate {
66//!     pub value_str: String,
67//!     value_internal: String,
68//! }
69//!
70//!// `Type::*` needs `#[derive(StructPath)]` on `Type`; returns ["value_str"],
71//!// only the field declared with plain `pub`
72//!let pub_only: [&str; 1] = paths!(TestStructWithPrivate::*);
73//!
74//!// `visibility="all"` returns every declared field instead
75//!let all_fields: [&str; 2] = paths!(TestStructWithPrivate::*; visibility="all");
76//!
77//! #[derive(StructPath)]
78//! pub struct TestStructChild {
79//!     pub child_value_str: String,
80//!     pub child_value_num: u64,
81//! }
82//!
83//! pub struct TestStructParent {
84//!     pub value_child: TestStructChild,
85//!     pub opt_value_child: Option<TestStructChild>,
86//! }
87//!
88//!// nested `Type::*`: `TestStructChild` also needs `#[derive(StructPath)]`,
89//!// and is named again inside the parens; returns
90//!// ["value_child.child_value_str", "value_child.child_value_num"]
91//!let nested: [&str; 2] = paths!(TestStructParent::value_child.(TestStructChild::*));
92//!
93//!// `~` before the parens steps through the `Option` the same way it does
94//!// for a single field; returns ["opt_value_child.child_value_str", ...]
95//!let nested_opt: [&str; 2] = paths!(TestStructParent::opt_value_child~(TestStructChild::*));
96//!
97//! }
98//!
99//! ```
100//!
101
102use convert_case::{Case, Casing};
103use proc_macro::{Delimiter, Group, Ident, Spacing, Span, TokenStream, TokenTree};
104use std::collections::HashMap;
105
106/// Converts a parse-time error into the token stream for a `compile_error!`
107/// invocation, so malformed macro input surfaces as a normal compiler error
108/// carrying the same message instead of a proc-macro panic.
109fn compile_error_for(message: String) -> TokenStream {
110    format!("compile_error!({:?})", message)
111        .parse()
112        .expect("a compile_error! invocation with an escaped string literal always parses")
113}
114
115/// Like `compile_error_for`, but for the derive parser, which walks its own
116/// item tokens rather than building a `Result<_, String>` message: the error
117/// is spanned on the offending token so it is reported there and not at the
118/// derive's call site.
119fn compile_error_at(message: &str, span: Span) -> TokenStream {
120    let mut literal = proc_macro::Literal::string(message);
121    literal.set_span(span);
122    let message_tokens = TokenStream::from(TokenTree::Literal(literal));
123    fill(
124        "compile_error!(MESSAGE);",
125        span,
126        &[("MESSAGE", &message_tokens)],
127    )
128}
129
130/// The type path and field paths found for one `Type::field[, field...]`
131/// group, kept as the user's own tokens so the generated check reports a
132/// type error on the exact token that is wrong. A field path holds its
133/// idents, `.` and `~` puncts as written; it is the only record kept of the
134/// path, so the check and the returned string can never disagree.
135type FoundStructs = Vec<(Vec<TokenTree>, Vec<Vec<TokenTree>>)>;
136
137#[proc_macro]
138pub fn paths(struct_path_stream: TokenStream) -> TokenStream {
139    match paths_impl(struct_path_stream) {
140        Ok(stream) => stream,
141        Err(message) => compile_error_for(message),
142    }
143}
144
145fn paths_impl(struct_path_stream: TokenStream) -> Result<TokenStream, String> {
146    let top_level_tokens: Vec<TokenTree> = struct_path_stream.clone().into_iter().collect();
147    if let Some(result) = try_parse_nested_star(&top_level_tokens) {
148        return result;
149    }
150    if let Some(result) = try_parse_all_fields(&struct_path_stream) {
151        return result;
152    }
153
154    let mut current_struct_name_tokens: Vec<TokenTree> = Vec::new();
155    let mut current_struct_fields: Vec<Vec<TokenTree>> = Vec::with_capacity(16);
156
157    let mut opened_struct = false;
158    let mut colons_counter = 0;
159    let mut options_opened = false;
160
161    let mut current_field_tokens: Vec<TokenTree> = Vec::new();
162
163    let mut current_option_name: Option<String> = None;
164    let mut expect_option_value: bool = false;
165
166    let mut options: HashMap<String, String> = HashMap::new();
167    let mut found_structs: FoundStructs = Vec::new();
168
169    for token_tree in struct_path_stream.into_iter() {
170        match token_tree {
171            TokenTree::Ident(id) if current_struct_name_tokens.is_empty() => {
172                current_struct_name_tokens.push(TokenTree::Ident(id));
173            }
174            TokenTree::Punct(punct)
175                if !current_struct_name_tokens.is_empty()
176                    && !opened_struct
177                    && punct == ':'
178                    && colons_counter < 2 =>
179            {
180                colons_counter += 1;
181                if colons_counter > 1 {
182                    opened_struct = true;
183                }
184            }
185            TokenTree::Ident(id) if opened_struct => {
186                colons_counter = 0;
187                current_field_tokens.push(TokenTree::Ident(id));
188            }
189            TokenTree::Punct(punct)
190                if !current_struct_name_tokens.is_empty()
191                    && opened_struct
192                    && punct == ':'
193                    && colons_counter < 2 =>
194            {
195                colons_counter += 1;
196                opened_struct = false;
197                let field_tokens = std::mem::take(&mut current_field_tokens);
198                if !fold_field_into_struct_name(field_tokens, &mut current_struct_name_tokens) {
199                    return Err(format!(
200                        "Unexpected punctuation input for struct path group parameters: {:?}",
201                        punct
202                    ));
203                }
204            }
205            TokenTree::Punct(punct) if opened_struct && (punct == '.' || punct == '~') => {
206                if current_field_tokens.is_empty() {
207                    return Err(format!(
208                        "Unexpected punctuation input for struct path group parameters: {:?}",
209                        punct
210                    ));
211                }
212                current_field_tokens.push(TokenTree::Punct(punct));
213            }
214            TokenTree::Group(group) if opened_struct && current_field_tokens.is_empty() => {
215                parse_multiple_fields(group.stream(), &mut current_struct_fields)?
216            }
217            TokenTree::Punct(punct) if !options_opened && opened_struct && punct == ',' => {
218                opened_struct = false;
219                colons_counter = 0;
220                if current_struct_name_tokens.is_empty() {
221                    return Err("Unexpected comma with empty definitions!".to_string());
222                }
223                if !current_field_tokens.is_empty() {
224                    current_struct_fields.push(std::mem::take(&mut current_field_tokens));
225                }
226                if current_struct_fields.is_empty() {
227                    return Err(format!(
228                        "Unexpected comma with empty fields for {}!",
229                        render_text(&current_struct_name_tokens)
230                    ));
231                }
232                found_structs.push((
233                    std::mem::take(&mut current_struct_name_tokens),
234                    std::mem::take(&mut current_struct_fields),
235                ));
236            }
237            TokenTree::Punct(punct) if punct == ';' && opened_struct && !options_opened => {
238                options_opened = true;
239                opened_struct = false;
240            }
241            TokenTree::Ident(id) if options_opened && !expect_option_value => {
242                current_option_name = Some(id.to_string())
243            }
244            TokenTree::Ident(id) if options_opened && expect_option_value => {
245                expect_option_value = false;
246                match current_option_name.take() {
247                    Some(option_name) => {
248                        options.insert(option_name, id.to_string());
249                    }
250                    _ => {
251                        return Err("Wrong options format".to_string());
252                    }
253                }
254            }
255            TokenTree::Literal(lit) if options_opened && expect_option_value => {
256                expect_option_value = false;
257                match current_option_name.take() {
258                    Some(option_name) => {
259                        options.insert(option_name, unquote_literal(&lit)?);
260                    }
261                    _ => {
262                        return Err("Wrong options format".to_string());
263                    }
264                }
265            }
266            TokenTree::Punct(punct) if options_opened && punct == '=' => {
267                expect_option_value = true;
268            }
269            TokenTree::Punct(punct) if options_opened && punct == ',' => {
270                expect_option_value = false;
271            }
272            // A second (or later) `;` starts another options group, so a
273            // wrapper macro can append its own options after the caller's.
274            // Groups share one key space; a repeated key takes its last value.
275            TokenTree::Punct(punct) if options_opened && punct == ';' => {
276                expect_option_value = false;
277            }
278            others => {
279                return Err(format!(
280                    "Unexpected input for struct path parameters: {:?}",
281                    others
282                ));
283            }
284        }
285    }
286
287    if !current_field_tokens.is_empty() {
288        current_struct_fields.push(std::mem::take(&mut current_field_tokens));
289    }
290    if current_struct_name_tokens.is_empty() {
291        return Err("Unexpected comma with empty definitions!".to_string());
292    }
293    if current_struct_fields.is_empty() {
294        return Err(format!(
295            "Unexpected comma with empty fields for {}!",
296            render_text(&current_struct_name_tokens)
297        ));
298    }
299    found_structs.push((current_struct_name_tokens, current_struct_fields));
300
301    let all_check_functions = generate_checks_code_for(&found_structs);
302
303    let mut all_final_fields: Vec<String> = Vec::with_capacity(16);
304
305    for (_, fields) in &found_structs {
306        for field_tokens in fields {
307            let mut final_field_path = render_text(field_tokens).replace('~', ".");
308            if !options.is_empty() {
309                final_field_path = apply_options(&options, final_field_path)?;
310            }
311            all_final_fields.push(format!("\"{}\"", final_field_path))
312        }
313    }
314
315    let fields_str = format!("[{}]", all_final_fields.join(","));
316    let fields_tokens: TokenStream = fields_str
317        .parse()
318        .map_err(|_| format!("Generated code failed to parse: {}", fields_str))?;
319    let mut block_contents = all_check_functions;
320    block_contents.extend(fields_tokens);
321    Ok(TokenStream::from(TokenTree::Group(Group::new(
322        Delimiter::Brace,
323        block_contents,
324    ))))
325}
326
327/// Reclassifies a field-path segment that turned out to be a further
328/// `::`-qualified part of the type (`tests` in `crate::tests::Struct::field`)
329/// back into the type path. Returns `false` when the pending segment is not
330/// exactly one identifier, which has no valid representation as a type-path
331/// segment; the caller then reports the input as malformed instead of
332/// building a check around it.
333fn fold_field_into_struct_name(
334    field_tokens: Vec<TokenTree>,
335    struct_name_tokens: &mut Vec<TokenTree>,
336) -> bool {
337    let is_single_ident = matches!(field_tokens.as_slice(), [TokenTree::Ident(_)]);
338    if is_single_ident {
339        struct_name_tokens.extend("::".parse::<TokenStream>().expect("`::` always parses"));
340        struct_name_tokens.extend(field_tokens);
341    }
342    is_single_ident
343}
344
345/// Renders parsed path tokens back to their text as written, `~` included,
346/// so a caller replacing it with a delimiter still sees each collection step.
347fn render_text(tokens: &[TokenTree]) -> String {
348    tokens.iter().map(|token| token.to_string()).collect()
349}
350
351fn parse_multiple_fields(
352    group_stream: TokenStream,
353    found_struct_fields: &mut Vec<Vec<TokenTree>>,
354) -> Result<(), String> {
355    let mut current_field_tokens: Vec<TokenTree> = Vec::new();
356
357    for token_tree in group_stream.into_iter() {
358        match token_tree {
359            TokenTree::Ident(id) => current_field_tokens.push(TokenTree::Ident(id)),
360            TokenTree::Punct(punct) if punct == ',' => {
361                if current_field_tokens.is_empty() {
362                    return Err(format!(
363                        "Unexpected punctuation input for struct path group parameters: {:?}",
364                        punct
365                    ));
366                }
367                found_struct_fields.push(std::mem::take(&mut current_field_tokens));
368            }
369            TokenTree::Punct(punct) if punct == '.' => {
370                if current_field_tokens.is_empty() {
371                    return Err(format!(
372                        "Unexpected punctuation input for struct path group parameters: {:?}",
373                        punct
374                    ));
375                }
376                current_field_tokens.push(TokenTree::Punct(punct));
377            }
378            others => {
379                return Err(format!(
380                    "Unexpected input for struct path group parameters: {:?}",
381                    others
382                ));
383            }
384        }
385    }
386
387    if !current_field_tokens.is_empty() {
388        found_struct_fields.push(current_field_tokens);
389    }
390    Ok(())
391}
392
393/// Recognizes the one shape `*` is allowed in: `Type::*` (optionally
394/// `crate::mod::Type::*`), followed by nothing or `; options`. Returns
395/// `None` when the input has no top-level `*` at all, so the caller falls
396/// through to the ordinary field-list parser unchanged. Any other
397/// placement of `*` (mixed with another struct or field group) is an error
398/// here rather than a fall-through, because the combined array length
399/// would then be unknown to the macro.
400fn try_parse_all_fields(struct_path_stream: &TokenStream) -> Option<Result<TokenStream, String>> {
401    let tokens: Vec<TokenTree> = struct_path_stream.clone().into_iter().collect();
402    if !tokens
403        .iter()
404        .any(|t| matches!(t, TokenTree::Punct(p) if *p == '*'))
405    {
406        return None;
407    }
408    Some(parse_all_fields_at(&tokens))
409}
410
411/// The only options `Type::*` recognizes; any other key is a `compile_error!`
412/// rather than a silently ignored typo, since there is no field list here for
413/// the user to cross-check the result against.
414const ALL_FIELDS_OPTION_KEYS: &[&str] = &["visibility", "case"];
415
416/// Reported when the `*` sits beside a second struct or field group, whether
417/// before it (`B::y, A::*`) or after (`A::*, B::y`): the combined array's
418/// length would then be unknown to the macro.
419const CANNOT_COMBINE_STAR_MESSAGE: &str = "`Type::*` cannot be combined with another struct or field group: its length is unknown to the macro";
420
421/// Reported when the type-name position is not a plain `Type::*` /
422/// `crate::module::Type::*` path and there is no comma marking a second
423/// group either -- generics on the type, a `Type::<'a>::*` turbofish, a
424/// `$t:ty` macro fragment, or a stray trailing separator.
425const EXPECTED_TYPE_PATH_MESSAGE: &str =
426    "expected a type path such as `Type::*` or `crate::module::Type::*`";
427
428/// Reported when the parenthesized group holds tokens after its own `*`:
429/// options belong after the closing `)`, not inside it.
430const NOTHING_AFTER_NESTED_STAR_MESSAGE: &str = "nothing may follow a nested `Type::*` inside the parentheses; put options after the closing `)`";
431
432/// Reported when a nested `Type::*` is followed by another struct or field
433/// group: like the bare form, its result's length is unknown to the macro
434/// once combined with anything else.
435const CANNOT_COMBINE_NESTED_STAR_MESSAGE: &str = "a nested `Type::*` cannot be combined with another struct or field group: its length is unknown to the macro";
436
437/// Reported when the nested form's own `.(`/`~(` has nothing valid before
438/// it: no field name and connector at all (`Type::(a, *)`), or a stray
439/// punct where a field name was expected (`Parent::..(Child::*)`). Named
440/// after the connector rather than after whichever token was actually found,
441/// since callers hit this from more than one parse state.
442const NESTED_STAR_MISSING_FIELD_MESSAGE: &str = "expected a field name before `.(` / `~(`";
443
444/// Reported when a nested `Type::*`'s own parentheses hold no `*` at all
445/// (`Parent::child.(f)`): the field name and connector before the parens are
446/// fine, but their content is not `Inner::*`.
447const NESTED_STAR_INNER_EXPECTED_MESSAGE: &str = "inside the parens, expected `Type::*`";
448
449/// Recognizes `paths!`'s nested `Type::*` form -- `Parent::field(.field|
450/// ~field)*.(Inner::*)` or the same ending in `~(Inner::*)`, optionally
451/// followed by `; options`. A `(...)` group on its own is not enough to mark
452/// this form: `paths!(Type::(a, b))` is master's plain field-group syntax in
453/// parenthesis rather than braces, and must still reach `parse_multiple_fields`
454/// unchanged. Only a `(...)` group directly preceded by `.`/`~`, or one that
455/// itself contains a `*`, is claimed here; every other input, including one
456/// with a `(...)` group elsewhere, falls through to the parsers that handled
457/// it before this form existed. Every top-level `(...)` group is checked in
458/// order, not just the first: `A::(x), B::c.(C::*)` has a plain field-group
459/// paren before the nested form's own, and the first one alone would never
460/// claim this input.
461fn try_parse_nested_star(tokens: &[TokenTree]) -> Option<Result<TokenStream, String>> {
462    let paren_idx = tokens.iter().enumerate().find_map(|(i, t)| {
463        let group = match t {
464            TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis => g,
465            _ => return None,
466        };
467        let follows_connector = i
468            .checked_sub(1)
469            .is_some_and(|j| matches!(&tokens[j], TokenTree::Punct(p) if *p == '.' || *p == '~'));
470        let contains_star = group
471            .stream()
472            .into_iter()
473            .any(|t| matches!(t, TokenTree::Punct(p) if p == '*'));
474        (follows_connector || contains_star).then_some(i)
475    })?;
476    Some(parse_nested_star_at(tokens, paren_idx))
477}
478
479fn parse_nested_star_at(tokens: &[TokenTree], paren_idx: usize) -> Result<TokenStream, String> {
480    let prefix_region = &tokens[..paren_idx];
481    // A second struct or field group before the nested form (`Other::x,
482    // Parent::child.(Child::*)`) would otherwise reach `parse_type_and_field_path`
483    // as one malformed type path and get the generic "expected a type path"
484    // message; the comma marks the real problem, the same way
485    // `parse_all_fields_at` tells the two apart for the bare form. Checked
486    // before the `{ ... }` group check below, since a comma marks the same
487    // "second group" mistake whether or not a `{ ... }` group happens to be
488    // one of the groups involved (`A::{x}, B::c.(C::*)` and `A::[x], B::c.(C::*)`
489    // are the same mistake and must report it the same way).
490    if contains_top_level_comma(prefix_region) {
491        return Err(CANNOT_COMBINE_NESTED_STAR_MESSAGE.to_string());
492    }
493    // With no comma present, a `{ ... }` field group still names one specific
494    // mistake more precisely than the generic type-path error below would.
495    if prefix_region
496        .iter()
497        .any(|t| matches!(t, TokenTree::Group(g) if g.delimiter() == Delimiter::Brace))
498    {
499        return Err(
500            "a `{ ... }` field group cannot appear before a nested `Type::*`; use a plain field path such as `Parent::child.(Child::*)`".to_string(),
501        );
502    }
503
504    let connector = match paren_idx.checked_sub(1).map(|i| &tokens[i]) {
505        Some(TokenTree::Punct(p)) if *p == '.' || *p == '~' => p.clone(),
506        _ => return Err(NESTED_STAR_MISSING_FIELD_MESSAGE.to_string()),
507    };
508    let head = &prefix_region[..prefix_region.len() - 1];
509    let (parent_tokens, field_tokens) = parse_type_and_field_path(head)?;
510    if field_tokens.is_empty() {
511        return Err(
512            "expected a field before the nested `Type::*`, e.g. `Parent::child.(Child::*)`"
513                .to_string(),
514        );
515    }
516    // A repeated or malformed connector (`P::o~.(C::*)`, `P::c..(C::*)`,
517    // `P::o~~(C::*)`) leaves a trailing `.`/`~` in the field path once the
518    // one connector directly before the parens is stripped off into `head`;
519    // left unchecked, that trailing punct becomes a dangling `.` in the
520    // generated check and fails with a confusing "unexpected token `;`".
521    if !matches!(field_tokens.last(), Some(TokenTree::Ident(_))) {
522        return Err(
523            "expected a field name immediately before the nested `Type::*`'s connector, e.g. `Parent::child.(Child::*)`".to_string(),
524        );
525    }
526
527    let paren_group = match &tokens[paren_idx] {
528        TokenTree::Group(g) => g,
529        _ => unreachable!(),
530    };
531    let inner_tokens: Vec<TokenTree> = paren_group.stream().into_iter().collect();
532    if inner_tokens
533        .iter()
534        .any(|t| matches!(t, TokenTree::Group(_)))
535    {
536        return Err("nesting a parenthesized group inside `Type::*` is not supported".to_string());
537    }
538    let (inner_type_tokens, star_span) = parse_bare_star_type(&inner_tokens)?;
539
540    let rest = &tokens[paren_idx + 1..];
541    let options = match rest {
542        [] => Vec::new(),
543        [TokenTree::Punct(p), remainder @ ..] if *p == ';' => parse_all_fields_options(remainder)?,
544        // A lone trailing comma with nothing following it is not a second
545        // group, just malformed input -- the same call `parse_all_fields_at`
546        // makes for the bare form's own `TestStruct::*,`, so both forms
547        // report the same message for the same shape of mistake.
548        [TokenTree::Punct(p)] if *p == ',' => return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string()),
549        _ => return Err(CANNOT_COMBINE_NESTED_STAR_MESSAGE.to_string()),
550    };
551    // `visibility`/`case` pick the inner type's names const exactly as the
552    // bare form does; `delim` additionally separates the prefix's own
553    // segments and joins the prefix to each name -- there being a prefix
554    // here is what the bare form has no `delim` option for.
555    const NESTED_STAR_OPTION_KEYS: &[&str] = &["visibility", "case", "delim"];
556    if let Some((unknown, _)) = options
557        .iter()
558        .find(|(key, _)| !NESTED_STAR_OPTION_KEYS.contains(&key.as_str()))
559    {
560        return Err(format!("Unknown option is specified: {}", unknown));
561    }
562    let (visibility_suffix, case_suffix) = names_const_suffixes(&options)?;
563    let options_map: HashMap<String, String> = options.into_iter().collect();
564
565    // `~` marks a collection step and must become `.` before options are
566    // applied, or a segment joined by `~` is treated as one field name
567    // instead of two and never gets the chosen delim or per-segment case
568    // conversion -- the same rule `path!` applies to its own field path.
569    let raw_prefix = render_text(&field_tokens).replace('~', ".");
570    let prefix_string = apply_options(&options_map, raw_prefix)?;
571    let delim_string = options_map
572        .get("delim")
573        .cloned()
574        .unwrap_or_else(|| ".".to_string());
575
576    let inner_type_stream: TokenStream = inner_type_tokens.iter().cloned().collect();
577    let parent_stream: TokenStream = parent_tokens.iter().cloned().collect();
578
579    // `.` reads the field by reference and relies on deref coercion for a
580    // `Box<Inner>` field; `~` reads it through `iter()` instead, which
581    // already yields a reference, so no leading `&` is added there. `SELF`
582    // is spanned with `star_span` rather than this call's own `Span::call_site()`,
583    // matching the closure parameter `|t: &PARENT|` the outer `fill` below
584    // builds with `star_span` too: the two must share one hygiene context or
585    // the closure body fails to resolve its own parameter with "cannot find
586    // value `t` in this scope" once this whole call is itself expanded from
587    // inside a wrapping `macro_rules!`. The surrounding `.iter()`/`.next()`/
588    // `.unwrap()` glue keeps `Span::call_site()` so it is still recognized
589    // as macro-generated rather than linted as if the user had written it.
590    let check_template = if connector == '~' {
591        "let _: &INNER = SELF.FIELD.iter().next().unwrap();"
592    } else {
593        "let _: &INNER = &SELF.FIELD;"
594    };
595    let self_param_tokens: TokenStream =
596        TokenStream::from(TokenTree::Ident(Ident::new("t", star_span)));
597    let check = fill(
598        check_template,
599        Span::call_site(),
600        &[
601            ("SELF", &self_param_tokens),
602            ("INNER", &inner_type_stream),
603            ("FIELD", &field_check_tokens(&field_tokens)),
604        ],
605    );
606
607    let names_path = format!(
608        "INNER::__STRUCT_PATH_{}_FIELDS{}",
609        visibility_suffix, case_suffix
610    );
611    let names_tokens = fill(&names_path, star_span, &[("INNER", &inner_type_stream)]);
612    let prefix_tokens = quoted_string_tokens(&prefix_string, star_span)?;
613    let delim_tokens = quoted_string_tokens(&delim_string, star_span)?;
614
615    // `NAMES` is spliced twice here (the local const's own length and its
616    // value) and nowhere else in the block, so a names const that a
617    // downstream crate cannot see (an `ALL` array capped to `pub(crate)`)
618    // reports at most these two sites instead of once per call below.
619    // Binding the joined array itself to a `const`, rather than leaving it
620    // a plain expression, forces the whole join to run at compile time: a
621    // plain call, even to a `const fn` with only `const` inputs, is not
622    // guaranteed to be folded away, so without this binding release builds
623    // still call `core::str::from_utf8` at runtime for every `paths!` site.
624    Ok(fill(
625        "{
626            const _: fn(&PARENT) = |t: &PARENT| { CHECK };
627            const __STRUCT_PATH_NAMES: [&str; NAMES.len()] = NAMES;
628            const __STRUCT_PATH_LEN: usize =
629                INNER::__struct_path_joined_len(PREFIX, DELIM, &__STRUCT_PATH_NAMES);
630            const __STRUCT_PATH_BYTES: [u8; __STRUCT_PATH_LEN] =
631                INNER::__struct_path_joined_bytes::<__STRUCT_PATH_LEN>(PREFIX, DELIM, &__STRUCT_PATH_NAMES);
632            const __STRUCT_PATH_OUT: [&str; __STRUCT_PATH_NAMES.len()] =
633                INNER::__struct_path_joined(&__STRUCT_PATH_BYTES, PREFIX, DELIM, &__STRUCT_PATH_NAMES);
634            __STRUCT_PATH_OUT
635        }",
636        star_span,
637        &[
638            ("PARENT", &parent_stream),
639            ("CHECK", &check),
640            ("INNER", &inner_type_stream),
641            ("PREFIX", &prefix_tokens),
642            ("DELIM", &delim_tokens),
643            ("NAMES", &names_tokens),
644        ],
645    ))
646}
647
648/// Parses `tokens` as `Type::field(.field|~field)*`, the same shape a single
649/// `path!` group accepts, but with no group and no options support since
650/// the nested `Type::*` form's caller already ruled those out. Returns the
651/// type's tokens and the field path's tokens (idents and `.`/`~` puncts, in
652/// declaration order and still carrying the user's own spans); an empty
653/// field path is valid here and left for the caller to judge, since
654/// `Parent::(Child::*)` and a genuinely malformed prefix are different
655/// errors.
656fn parse_type_and_field_path(
657    tokens: &[TokenTree],
658) -> Result<(Vec<TokenTree>, Vec<TokenTree>), String> {
659    let mut struct_name_tokens: Vec<TokenTree> = Vec::new();
660    let mut field_tokens: Vec<TokenTree> = Vec::new();
661    let mut opened_struct = false;
662    let mut colons_counter = 0;
663
664    for token_tree in tokens {
665        match token_tree.clone() {
666            TokenTree::Ident(id) if struct_name_tokens.is_empty() => {
667                struct_name_tokens.push(TokenTree::Ident(id));
668            }
669            TokenTree::Punct(punct)
670                if !struct_name_tokens.is_empty()
671                    && !opened_struct
672                    && punct == ':'
673                    && colons_counter < 2 =>
674            {
675                colons_counter += 1;
676                if colons_counter > 1 {
677                    opened_struct = true;
678                }
679            }
680            TokenTree::Ident(id) if opened_struct => {
681                colons_counter = 0;
682                field_tokens.push(TokenTree::Ident(id));
683            }
684            TokenTree::Punct(punct)
685                if !struct_name_tokens.is_empty()
686                    && opened_struct
687                    && punct == ':'
688                    && colons_counter < 2 =>
689            {
690                colons_counter += 1;
691                opened_struct = false;
692                let taken = std::mem::take(&mut field_tokens);
693                if !fold_field_into_struct_name(taken, &mut struct_name_tokens) {
694                    return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string());
695                }
696            }
697            TokenTree::Punct(punct) if opened_struct && (punct == '.' || punct == '~') => {
698                if field_tokens.is_empty() {
699                    return Err(NESTED_STAR_MISSING_FIELD_MESSAGE.to_string());
700                }
701                field_tokens.push(TokenTree::Punct(punct));
702            }
703            _ => return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string()),
704        }
705    }
706    if struct_name_tokens.is_empty() || !opened_struct {
707        return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string());
708    }
709    Ok((struct_name_tokens, field_tokens))
710}
711
712/// Splits `tokens` on its first `*`, checked to directly follow `Type::`,
713/// into the type path before it, the `*`'s own span, and whatever tokens
714/// remain after it. Shared by the nested form's inner type, which allows no
715/// `rest` at all, and the bare form, which allows `; options` or a second
716/// group there; each checks `rest` itself since the two forms disagree on
717/// what may follow.
718fn split_star_type(tokens: &[TokenTree]) -> Result<(&[TokenTree], Span, &[TokenTree]), String> {
719    let star_pos = tokens
720        .iter()
721        .position(|t| matches!(t, TokenTree::Punct(p) if *p == '*'))
722        .ok_or_else(|| EXPECTED_TYPE_PATH_MESSAGE.to_string())?;
723    if star_pos < 2
724        || !matches!(&tokens[star_pos - 1], TokenTree::Punct(p) if *p == ':')
725        || !matches!(&tokens[star_pos - 2], TokenTree::Punct(p) if *p == ':')
726    {
727        return Err("`*` must directly follow `Type::`".to_string());
728    }
729    let star_span = match &tokens[star_pos] {
730        TokenTree::Punct(p) => p.span(),
731        _ => unreachable!(),
732    };
733    Ok((&tokens[..star_pos - 2], star_span, &tokens[star_pos + 1..]))
734}
735
736/// Whether `tokens` is a type path that starts with `Self`, the one type
737/// a nested `Type::*`'s inner type must reject regardless of what follows
738/// (`Self`, `Self::Child`, ...): the join it drives is wrapped in a
739/// block-local `const`, and `Self` cannot be named from a nested item, only
740/// from the enclosing `impl` block itself (E0401).
741fn is_self_type_path(tokens: &[TokenTree]) -> bool {
742    matches!(tokens.first(), Some(TokenTree::Ident(id)) if id.to_string() == "Self")
743}
744
745/// Parses the parenthesized group's content as a bare `Type::*` with
746/// nothing else inside -- no options, since those belong after the closing
747/// `)`, and no second group, already ruled out by the caller. Returns the
748/// inner type's tokens and the `*`'s span, used to anchor the generated
749/// call so a missing derive or a mismatched type is reported there.
750fn parse_bare_star_type(tokens: &[TokenTree]) -> Result<(Vec<TokenTree>, Span), String> {
751    // Checked ahead of `split_star_type`, which has no `*` to report a span
752    // for otherwise: the field name and connector before the parens are
753    // already known good at this call site, so the more specific message
754    // points at the parens' own content rather than repeating the generic
755    // type-path message that content with no `*` anywhere else falls back to.
756    if !tokens
757        .iter()
758        .any(|t| matches!(t, TokenTree::Punct(p) if *p == '*'))
759    {
760        return Err(NESTED_STAR_INNER_EXPECTED_MESSAGE.to_string());
761    }
762    let (type_tokens, star_span, rest) = split_star_type(tokens)?;
763    if type_tokens.is_empty() || !is_bare_type_path(type_tokens) {
764        return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string());
765    }
766    if is_self_type_path(type_tokens) {
767        return Err(
768            "`Self` cannot be named inside a nested `Type::*`; name the type directly, e.g. `Node::next~(Node::*)`".to_string(),
769        );
770    }
771    if !rest.is_empty() {
772        return Err(NOTHING_AFTER_NESTED_STAR_MESSAGE.to_string());
773    }
774    Ok((type_tokens.to_vec(), star_span))
775}
776
777/// Splices `value`, still carrying its source escape sequences the way
778/// `unquote_literal` returns them, directly between quotes and parses the
779/// result as a `&'static str` literal spanned at `span` -- the same
780/// splice-and-parse `paths_impl` uses for its own returned field path, so a
781/// delimiter or prefix containing `delim = "\t"` decodes to the same
782/// character both places instead of the doubled backslash that
783/// `Literal::string` (which escapes its input rather than reusing it as
784/// already-escaped source text) produced here before.
785fn quoted_string_tokens(value: &str, span: Span) -> Result<TokenStream, String> {
786    let text = format!("\"{}\"", value);
787    let tokens: TokenStream = text
788        .parse()
789        .map_err(|_| format!("Generated code failed to parse: {}", text))?;
790    Ok(tokens
791        .into_iter()
792        .map(|mut token| {
793            token.set_span(span);
794            token
795        })
796        .collect())
797}
798
799fn parse_all_fields_at(tokens: &[TokenTree]) -> Result<TokenStream, String> {
800    let (type_tokens, star_span, rest) = split_star_type(tokens)?;
801    if type_tokens.is_empty() {
802        return Err("Unexpected `*` with an empty type!".to_string());
803    }
804    if !is_bare_type_path(type_tokens) {
805        // A second `Type::field` or `Type::*` group before this one folds
806        // into `type_tokens` rather than being rejected outright, since
807        // nothing here stops at the group's own `,`; a top-level comma is
808        // what tells that shape apart from a type path that is simply
809        // malformed on its own (generics, a turbofish, a macro fragment),
810        // which gets the other message instead.
811        return Err(if contains_top_level_comma(type_tokens) {
812            CANNOT_COMBINE_STAR_MESSAGE.to_string()
813        } else {
814            EXPECTED_TYPE_PATH_MESSAGE.to_string()
815        });
816    }
817
818    let options = match rest {
819        [] => Vec::new(),
820        [TokenTree::Punct(p), remainder @ ..] if *p == ';' => parse_all_fields_options(remainder)?,
821        // A second group after the `*` (`A::*, B::y`) is the same unknown-
822        // length combination as one before it; a lone trailing comma with
823        // nothing following it is not a second group, just malformed input.
824        [TokenTree::Punct(p), more @ ..] if *p == ',' && !more.is_empty() => {
825            return Err(CANNOT_COMBINE_STAR_MESSAGE.to_string())
826        }
827        _ => return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string()),
828    };
829    if let Some((unknown, _)) = options
830        .iter()
831        .find(|(key, _)| !ALL_FIELDS_OPTION_KEYS.contains(&key.as_str()))
832    {
833        return Err(format!("Unknown option is specified: {}", unknown));
834    }
835
836    let (visibility_suffix, case_suffix) = names_const_suffixes(&options)?;
837
838    let const_path = format!(
839        "TYPE::__STRUCT_PATH_{}_FIELDS{}",
840        visibility_suffix, case_suffix
841    );
842    let type_stream: TokenStream = type_tokens.iter().cloned().collect();
843    Ok(fill(&const_path, star_span, &[("TYPE", &type_stream)]))
844}
845
846/// Picks the `__STRUCT_PATH_{PUB|ALL}_FIELDS[_CAMEL|_PASCAL]` const suffix
847/// pair named by a `Type::*` call's `visibility`/`case` options, shared by
848/// the bare and nested forms so the two can never disagree on what a given
849/// option value selects.
850fn names_const_suffixes(
851    options: &[(String, String)],
852) -> Result<(&'static str, &'static str), String> {
853    let option_value = |name: &str| {
854        options
855            .iter()
856            .find(|(key, _)| key == name)
857            .map(|(_, value)| value.as_str())
858    };
859    let visibility_suffix = match option_value("visibility") {
860        None | Some("pub") => "PUB",
861        Some("all") => "ALL",
862        Some(other) => return Err(format!("Unknown visibility is specified: {}", other)),
863    };
864    let case_suffix = match option_value("case") {
865        None => "",
866        Some("camel") => "_CAMEL",
867        Some("pascal") => "_PASCAL",
868        Some(other) => return Err(format!("Unknown case is specified: {}", other)),
869    };
870    Ok((visibility_suffix, case_suffix))
871}
872
873/// Whether `tokens` contains a `,` at the top level -- the mark of a second
874/// struct or field group folded in beside `Type::*`, as opposed to a type
875/// path that is simply malformed on its own. Depth-aware over `<...>`, so a
876/// generic parameter list's own comma (`S<'a, 'b>::*`) is not mistaken for
877/// one, the same depth tracking `split_top_level_commas` uses for a field's
878/// generic type.
879fn contains_top_level_comma(tokens: &[TokenTree]) -> bool {
880    split_top_level_commas(tokens).len() > 1
881}
882
883/// Reports whether `tokens` is a bare type path -- an optional leading `::`
884/// followed by one or more identifiers joined by `::`, and nothing else.
885/// `Type::*`'s type name must be exactly this shape; anything wider (a
886/// trailing field group, a second `Type::*` group, generics, stray
887/// punctuation) is rejected by the caller before it is spliced into the
888/// generated code.
889fn is_bare_type_path(tokens: &[TokenTree]) -> bool {
890    let mut i = 0;
891    if matches!(tokens.first(), Some(TokenTree::Punct(p)) if *p == ':')
892        && matches!(tokens.get(1), Some(TokenTree::Punct(p)) if *p == ':')
893    {
894        i = 2;
895    }
896    loop {
897        if !matches!(tokens.get(i), Some(TokenTree::Ident(_))) {
898            return false;
899        }
900        i += 1;
901        if i == tokens.len() {
902            return true;
903        }
904        let is_double_colon = matches!(tokens.get(i), Some(TokenTree::Punct(p)) if *p == ':')
905            && matches!(tokens.get(i + 1), Some(TokenTree::Punct(p)) if *p == ':');
906        if !is_double_colon {
907            return false;
908        }
909        i += 2;
910    }
911}
912
913/// The one grammar `Type::*;` options accept: one or more `;`-separated
914/// groups of `key = "value"` pairs, the pairs within a group separated by
915/// `,`, each value a quoted string or char literal. A group may be empty.
916/// Unlike `path!`/`paths!`'s own option grammar -- parsed inline in their
917/// own loops, with a field list to cross-check its result against and a
918/// looser tolerance for malformed input -- `Type::*` has no such field list,
919/// so this parser accepts nothing looser: no bare key, no unquoted value, no
920/// two pairs run together without a separator, no `,` at a group's start,
921/// and no key repeated with a different value, whether within one group or
922/// across groups. Values are compared as the text written between the
923/// quotes, with escapes left undecoded: `"/"` and `'/'` are the same value,
924/// while `"/"` and `"\x2f"` are different and give the duplicate error. A
925/// trailing `,` may end a group, including just before the next `;`, since a
926/// wrapper macro appending `; key = "value"` cannot see whether its caller
927/// wrote one.
928enum OptionParseState {
929    /// At the start of a group, where a `;` closes it as an empty group.
930    GroupStart,
931    /// After a `,`, where an option name, the `;` opening the next group or
932    /// the end of input may follow, but not another `,`.
933    Key,
934    Equals,
935    Value,
936    CommaOrEnd,
937}
938
939/// Parses the `key = "value", ...; key = "value", ...` option grammar
940/// accepted after `Type::*;` (and, for the nested `Type::*` form, after the
941/// closing `)`). `tokens` starts after the `;` that opens the first group.
942/// Keeps source order so the caller can report the first unknown key as
943/// written rather than in a hash map's arbitrary order. A key repeated later
944/// in the options, in the same group or a later one, is accepted only with
945/// the same value, compared as written between the quotes with escapes left
946/// undecoded: a wrapper that appends `case = "camel"` must still compile
947/// when its caller wrote the same option, while two different values are
948/// rejected rather than one silently winning.
949fn parse_all_fields_options(tokens: &[TokenTree]) -> Result<Vec<(String, String)>, String> {
950    let mut options: Vec<(String, String)> = Vec::new();
951    let mut pending_key: Option<String> = None;
952    let mut state = OptionParseState::GroupStart;
953
954    for token_tree in tokens {
955        state = match (state, token_tree) {
956            (OptionParseState::GroupStart | OptionParseState::Key, TokenTree::Ident(id)) => {
957                pending_key = Some(id.to_string());
958                OptionParseState::Equals
959            }
960            (OptionParseState::Equals, TokenTree::Punct(p)) if *p == '=' => OptionParseState::Value,
961            (OptionParseState::Value, TokenTree::Literal(lit)) => {
962                let key = pending_key.take().expect("set on entering Equals/Value");
963                let value = unquote_literal(lit)?;
964                match options.iter().find(|(existing, _)| existing == &key) {
965                    Some((_, existing_value)) if *existing_value == value => {}
966                    Some(_) => return Err(format!("Duplicate option is specified: {}", key)),
967                    None => options.push((key, value)),
968                }
969                OptionParseState::CommaOrEnd
970            }
971            (OptionParseState::CommaOrEnd, TokenTree::Punct(p)) if *p == ',' => {
972                OptionParseState::Key
973            }
974            // Later groups exist so a wrapper macro can append its own
975            // options after the caller's. They share one key space with the
976            // first group: a key repeated in a later group with a different
977            // value is still a duplicate, not an override.
978            (
979                OptionParseState::CommaOrEnd | OptionParseState::GroupStart | OptionParseState::Key,
980                TokenTree::Punct(p),
981            ) if *p == ';' => OptionParseState::GroupStart,
982            (state, other) => {
983                let expected = match state {
984                    OptionParseState::GroupStart | OptionParseState::Key => "an option name or `;`",
985                    OptionParseState::Equals => "`=`",
986                    OptionParseState::Value => "a quoted string (\"...\") or char ('.') literal",
987                    OptionParseState::CommaOrEnd => "`,` or `;`",
988                };
989                return Err(format!(
990                    "Expected {} in `Type::*` options, found `{}`",
991                    expected, other
992                ));
993            }
994        };
995    }
996
997    match state {
998        OptionParseState::GroupStart | OptionParseState::Key | OptionParseState::CommaOrEnd => {
999            Ok(options)
1000        }
1001        OptionParseState::Equals | OptionParseState::Value => Err(format!(
1002            "Missing a value for option `{}`",
1003            pending_key.expect("set on entering Equals/Value")
1004        )),
1005    }
1006}
1007
1008#[proc_macro]
1009pub fn path(struct_path_stream: TokenStream) -> TokenStream {
1010    match path_impl(struct_path_stream) {
1011        Ok(stream) => stream,
1012        Err(message) => compile_error_for(message),
1013    }
1014}
1015
1016/// Whether `stream` contains a `*` at any depth, including inside a
1017/// parenthesized nested-`Type::*` group -- `path!` rejects both shapes with
1018/// the same message, so it needs the deeper scan `paths!`'s own two `*`
1019/// parsers do not, since each of those looks for a specific placement
1020/// rather than asking whether one exists anywhere at all.
1021fn contains_star(stream: TokenStream) -> bool {
1022    stream.into_iter().any(|token| match token {
1023        TokenTree::Punct(p) if p == '*' => true,
1024        TokenTree::Group(g) => contains_star(g.stream()),
1025        _ => false,
1026    })
1027}
1028
1029fn path_impl(struct_path_stream: TokenStream) -> Result<TokenStream, String> {
1030    // `Type::*` (all declared fields) has an array result, so only `paths!`
1031    // can return it; `path!` always returns a single string. The bare form
1032    // sits at the top level, but the nested form's `*` is inside a
1033    // parenthesized group, so this checks every depth, not just the top one.
1034    if contains_star(struct_path_stream.clone()) {
1035        return Err(
1036            "`*` for all declared fields is only supported by paths!, not path!".to_string(),
1037        );
1038    }
1039
1040    let mut current_struct_name_tokens: Vec<TokenTree> = Vec::new();
1041
1042    let mut opened_struct = false;
1043    let mut colons_counter = 0;
1044    let mut options_opened = false;
1045
1046    let mut current_field_tokens: Vec<TokenTree> = Vec::new();
1047
1048    let mut current_option_name: Option<String> = None;
1049    let mut expect_option_value: bool = false;
1050
1051    let mut options: HashMap<String, String> = HashMap::new();
1052    let mut found_structs: FoundStructs = Vec::new();
1053
1054    for token_tree in struct_path_stream.into_iter() {
1055        match token_tree {
1056            TokenTree::Ident(id) if current_struct_name_tokens.is_empty() => {
1057                current_struct_name_tokens.push(TokenTree::Ident(id));
1058            }
1059            TokenTree::Punct(punct)
1060                if !current_struct_name_tokens.is_empty()
1061                    && !opened_struct
1062                    && punct == ':'
1063                    && colons_counter < 2 =>
1064            {
1065                colons_counter += 1;
1066                if colons_counter > 1 {
1067                    opened_struct = true;
1068                }
1069            }
1070            TokenTree::Ident(id) if opened_struct => {
1071                colons_counter = 0;
1072                current_field_tokens.push(TokenTree::Ident(id));
1073            }
1074            TokenTree::Punct(punct)
1075                if !current_struct_name_tokens.is_empty()
1076                    && opened_struct
1077                    && punct == ':'
1078                    && colons_counter < 2 =>
1079            {
1080                colons_counter += 1;
1081                opened_struct = false;
1082                let field_tokens = std::mem::take(&mut current_field_tokens);
1083                if !fold_field_into_struct_name(field_tokens, &mut current_struct_name_tokens) {
1084                    return Err(format!(
1085                        "Unexpected punctuation input for struct path group parameters: {:?}",
1086                        punct
1087                    ));
1088                }
1089            }
1090            TokenTree::Punct(punct) if opened_struct && (punct == '.' || punct == '~') => {
1091                if current_field_tokens.is_empty() {
1092                    return Err(format!(
1093                        "Unexpected punctuation input for struct path group parameters: {:?}",
1094                        punct
1095                    ));
1096                }
1097                current_field_tokens.push(TokenTree::Punct(punct));
1098            }
1099            TokenTree::Punct(punct) if !options_opened && opened_struct && punct == ',' => {
1100                opened_struct = false;
1101                colons_counter = 0;
1102                if current_struct_name_tokens.is_empty() {
1103                    return Err("Unexpected comma with empty definitions!".to_string());
1104                }
1105                if current_field_tokens.is_empty() {
1106                    return Err(format!(
1107                        "Unexpected comma with empty fields for {}!",
1108                        render_text(&current_struct_name_tokens)
1109                    ));
1110                }
1111                found_structs.push((
1112                    std::mem::take(&mut current_struct_name_tokens),
1113                    vec![std::mem::take(&mut current_field_tokens)],
1114                ));
1115            }
1116            TokenTree::Punct(punct) if punct == ';' && opened_struct && !options_opened => {
1117                options_opened = true;
1118                opened_struct = false;
1119            }
1120            TokenTree::Ident(id) if options_opened && !expect_option_value => {
1121                current_option_name = Some(id.to_string())
1122            }
1123            TokenTree::Ident(id) if options_opened && expect_option_value => {
1124                expect_option_value = false;
1125                match current_option_name.take() {
1126                    Some(option_name) => {
1127                        options.insert(option_name, id.to_string());
1128                    }
1129                    _ => {
1130                        return Err("Wrong options format".to_string());
1131                    }
1132                }
1133            }
1134            TokenTree::Literal(lit) if options_opened && expect_option_value => {
1135                expect_option_value = false;
1136                match current_option_name.take() {
1137                    Some(option_name) => {
1138                        options.insert(option_name, unquote_literal(&lit)?);
1139                    }
1140                    _ => {
1141                        return Err("Wrong options format".to_string());
1142                    }
1143                }
1144            }
1145            TokenTree::Punct(punct) if options_opened && punct == '=' => {
1146                expect_option_value = true;
1147            }
1148            TokenTree::Punct(punct) if options_opened && punct == ',' => {
1149                expect_option_value = false;
1150            }
1151            // A second (or later) `;` starts another options group, so a
1152            // wrapper macro can append its own options after the caller's.
1153            // Groups share one key space; a repeated key takes its last value.
1154            TokenTree::Punct(punct) if options_opened && punct == ';' => {
1155                expect_option_value = false;
1156            }
1157            others => {
1158                return Err(format!(
1159                    "Unexpected input for struct path parameters: {:?}",
1160                    others
1161                ));
1162            }
1163        }
1164    }
1165
1166    if !current_struct_name_tokens.is_empty() && !current_field_tokens.is_empty() {
1167        found_structs.push((current_struct_name_tokens, vec![current_field_tokens]));
1168    }
1169
1170    if found_structs.is_empty() {
1171        return Err("Unexpected empty path definition!".to_string());
1172    }
1173
1174    let all_check_functions = generate_checks_code_for(&found_structs);
1175    // `~` marks a collection step and must become the path separator before
1176    // options are applied, or a segment joined by `~` is treated as one
1177    // field name instead of two and never gets the chosen delim or
1178    // per-segment case conversion.
1179    let full_field_path = found_structs
1180        .iter()
1181        .flat_map(|(_, fields)| fields.iter())
1182        .map(|tokens| render_text(tokens))
1183        .collect::<Vec<_>>()
1184        .join(".")
1185        .replace('~', ".");
1186    let final_field_path = apply_options(&options, full_field_path)?;
1187    let fields_str = format!("\"{}\"", final_field_path);
1188    let fields_tokens: TokenStream = fields_str
1189        .parse()
1190        .map_err(|_| format!("Generated code failed to parse: {}", fields_str))?;
1191    let mut block_contents = all_check_functions;
1192    block_contents.extend(fields_tokens);
1193    Ok(TokenStream::from(TokenTree::Group(Group::new(
1194        Delimiter::Brace,
1195        block_contents,
1196    ))))
1197}
1198
1199/// Builds the `const _: fn(&Type) = |t: &Type| { let _ = &t.field; };`
1200/// check for every discovered `Type::field` pair. The user's type and field
1201/// tokens keep their spans, so a type error is reported on the exact token
1202/// that is wrong; each `~` becomes `.iter().next().unwrap().` spanned at that
1203/// `~`, so a stepped-into type without `iter()` is blamed on the `~`.
1204fn generate_checks_code_for(found_structs: &FoundStructs) -> TokenStream {
1205    let mut all_check_functions = TokenStream::new();
1206    for (struct_name_tokens, struct_fields) in found_structs {
1207        let struct_name: TokenStream = struct_name_tokens.iter().cloned().collect();
1208        for field_tokens in struct_fields {
1209            all_check_functions.extend(fill(
1210                "const _: fn(&STRUCT) = |t: &STRUCT| { let _ = &t.FIELD; };",
1211                Span::call_site(),
1212                &[
1213                    ("STRUCT", &struct_name),
1214                    ("FIELD", &field_check_tokens(field_tokens)),
1215                ],
1216            ));
1217        }
1218    }
1219    all_check_functions
1220}
1221
1222/// Maps a field path's tokens to themselves, except a `~` step, which
1223/// becomes `.iter().next().unwrap().` spanned at that `~` so a stepped-into
1224/// type without `iter()` is blamed on the `~` rather than on the check as a
1225/// whole. Shared by every check this macro emits, flat or nested, so they
1226/// can never disagree on what `~` means.
1227fn field_check_tokens(field_tokens: &[TokenTree]) -> TokenStream {
1228    field_tokens
1229        .iter()
1230        .map(|token| match token {
1231            TokenTree::Punct(punct) if *punct == '~' => {
1232                fill(".iter().next().unwrap().", punct.span(), &[])
1233            }
1234            token => token.clone().into(),
1235        })
1236        .collect()
1237}
1238
1239/// Parses `template` into tokens spanned at `span`, replacing each ident
1240/// named in `substitutions` with the given tokens, which keep their own
1241/// spans. `template` must be valid Rust tokens.
1242fn fill(template: &str, span: Span, substitutions: &[(&str, &TokenStream)]) -> TokenStream {
1243    fn walk(stream: TokenStream, span: Span, subs: &[(&str, &TokenStream)]) -> TokenStream {
1244        stream
1245            .into_iter()
1246            .map(|mut token| {
1247                match &token {
1248                    TokenTree::Ident(ident) => {
1249                        let name = ident.to_string();
1250                        if let Some((_, tokens)) = subs.iter().find(|(n, _)| *n == name) {
1251                            return (*tokens).clone();
1252                        }
1253                    }
1254                    TokenTree::Group(group) => {
1255                        let stream = walk(group.stream(), span, subs);
1256                        token = TokenTree::Group(Group::new(group.delimiter(), stream));
1257                    }
1258                    _ => {}
1259                }
1260                token.set_span(span);
1261                token.into()
1262            })
1263            .collect()
1264    }
1265    walk(
1266        template.parse().expect("templates are valid tokens"),
1267        span,
1268        substitutions,
1269    )
1270}
1271
1272/// Strips the surrounding quotes from a plain string (`"..."`) or char
1273/// (`'.'`) literal. Escapes keep their source spelling because the value is
1274/// pasted back into a string literal in the generated code; decoding them
1275/// would change the output. Any other literal kind (numeric, byte string,
1276/// byte char, raw string) is rejected, since its text has no such quotes to
1277/// strip and would produce invalid generated code.
1278fn unquote_literal(lit: &proc_macro::Literal) -> Result<String, String> {
1279    let text = lit.to_string();
1280    let bytes = text.as_bytes();
1281    let is_quoted = bytes.len() >= 2
1282        && ((bytes[0] == b'"' && bytes[bytes.len() - 1] == b'"')
1283            || (bytes[0] == b'\'' && bytes[bytes.len() - 1] == b'\''));
1284    if is_quoted {
1285        Ok(text[1..text.len() - 1].to_string())
1286    } else {
1287        Err(format!(
1288            "Unsupported option value {}: expected a string (\"...\") or char ('.') literal",
1289            text
1290        ))
1291    }
1292}
1293
1294/// The case conversion shared by `path!`/`paths!`'s `case = "..."` option
1295/// and `#[derive(StructPath)]`'s `_CAMEL`/`_PASCAL` field-name constants, so
1296/// the two can never disagree on what "camel" or "pascal" means for a
1297/// snake_case field name.
1298fn convert_case_by_name(field_name: &str, case_name: &str) -> Result<String, String> {
1299    match case_name {
1300        "camel" => Ok(field_name.from_case(Case::Snake).to_case(Case::Camel)),
1301        "pascal" => Ok(field_name.from_case(Case::Snake).to_case(Case::Pascal)),
1302        another => Err(format!("Unknown case is specified: {}", another)),
1303    }
1304}
1305
1306/// Converts every name in `names` with `convert_case_by_name`, for the two
1307/// known case names the derive itself always passes; a case name it does
1308/// not recognize is a bug in the derive, not a user input to report.
1309fn convert_all(names: &[String], case_name: &str) -> Vec<String> {
1310    names
1311        .iter()
1312        .map(|n| {
1313            convert_case_by_name(n, case_name)
1314                .unwrap_or_else(|_| panic!("{:?} is always a known case", case_name))
1315        })
1316        .collect()
1317}
1318
1319fn apply_options(options: &HashMap<String, String>, field_path: String) -> Result<String, String> {
1320    let delim = options
1321        .get("delim")
1322        .as_ref()
1323        .map(|s| s.as_str())
1324        .unwrap_or(".");
1325    let case = options.get("case");
1326    let segments = field_path
1327        .split('.')
1328        .map(|field_name| match case {
1329            Some(case_value) => convert_case_by_name(field_name, case_value),
1330            None => Ok(field_name.to_string()),
1331        })
1332        .collect::<Result<Vec<String>, String>>()?;
1333    Ok(segments.join(delim))
1334}
1335
1336/// `#[derive(StructPath)]` records a struct's declared field names as
1337/// `#[doc(hidden)]` associated consts, so `paths!(Type::*)` can return them
1338/// without the caller listing every field by hand. Supports only a struct
1339/// with named fields and, at most, lifetime generic parameters; anything
1340/// else is a `compile_error!` on the offending item, spanned at the
1341/// specific token that disqualifies it.
1342#[proc_macro_derive(StructPath)]
1343pub fn derive_struct_path(input: TokenStream) -> TokenStream {
1344    match derive_struct_path_impl(input) {
1345        Ok(stream) => stream,
1346        Err(error_stream) => error_stream,
1347    }
1348}
1349
1350/// One field found in a derived struct's body: its declared name, with any
1351/// `r#` prefix stripped since the generated string is a JSON-path-style
1352/// segment rather than a Rust identifier, and whether it was declared with
1353/// plain `pub` — `pub(crate)`, `pub(super)`, `pub(in ..)` and private
1354/// fields are all "not plain pub".
1355struct DerivedField {
1356    name: String,
1357    is_plain_pub: bool,
1358}
1359
1360fn derive_struct_path_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
1361    let tokens: Vec<TokenTree> = input.into_iter().collect();
1362    let mut pos = 0;
1363
1364    skip_attributes(&tokens, &mut pos);
1365
1366    let visibility_start = pos;
1367    let (vis_len, item_is_plain_pub) = parse_visibility_prefix(&tokens[pos..]);
1368    pos += vis_len;
1369    let visibility_tokens: TokenStream = tokens[visibility_start..pos].iter().cloned().collect();
1370
1371    let (keyword, keyword_span) = match tokens.get(pos) {
1372        Some(TokenTree::Ident(id)) => (id.to_string(), id.span()),
1373        _ => {
1374            return Err(compile_error_at(
1375                "StructPath: unable to parse the derived item",
1376                Span::call_site(),
1377            ))
1378        }
1379    };
1380    if keyword != "struct" {
1381        return Err(compile_error_at(
1382            &format!(
1383                "StructPath supports structs with named fields only, not {}s",
1384                keyword
1385            ),
1386            keyword_span,
1387        ));
1388    }
1389    pos += 1;
1390
1391    let name_ident = match tokens.get(pos) {
1392        Some(TokenTree::Ident(id)) => id.clone(),
1393        _ => {
1394            return Err(compile_error_at(
1395                "StructPath: expected a struct name",
1396                keyword_span,
1397            ))
1398        }
1399    };
1400    let name_span = name_ident.span();
1401    pos += 1;
1402
1403    let (generics_impl, generics_type) = parse_lifetime_generics(&tokens, &mut pos)?;
1404    let (where_clause, fields_group) = find_fields_group(&tokens[pos..])?;
1405    let fields = parse_derived_fields(fields_group.stream())?;
1406
1407    let pub_names: Vec<String> = fields
1408        .iter()
1409        .filter(|f| f.is_plain_pub)
1410        .map(|f| f.name.clone())
1411        .collect();
1412    let all_names: Vec<String> = fields.iter().map(|f| f.name.clone()).collect();
1413
1414    let name_tokens = TokenStream::from(TokenTree::Ident(name_ident));
1415    let pub_array = array_literal_tokens(&pub_names);
1416    let pub_camel_array = array_literal_tokens(&convert_all(&pub_names, "camel"));
1417    let pub_pascal_array = array_literal_tokens(&convert_all(&pub_names, "pascal"));
1418    let all_array = array_literal_tokens(&all_names);
1419    let all_camel_array = array_literal_tokens(&convert_all(&all_names, "camel"));
1420    let all_pascal_array = array_literal_tokens(&convert_all(&all_names, "pascal"));
1421    let n_pub: TokenStream = pub_names
1422        .len()
1423        .to_string()
1424        .parse()
1425        .expect("a field count always parses as a literal");
1426    let n_all: TokenStream = all_names
1427        .len()
1428        .to_string()
1429        .parse()
1430        .expect("a field count always parses as a literal");
1431
1432    // `__STRUCT_PATH_ALL_FIELDS*` exposes every declared field's name,
1433    // private ones included. A plain `pub` struct's consts are capped to
1434    // `pub(crate)`, so a downstream crate cannot read private field names
1435    // through them and adding a private field is not a breaking change to a
1436    // `pub` array type. Any other struct visibility -- private, `pub(crate)`,
1437    // `pub(super)`, `pub(in ..)` -- reuses the struct's own visibility tokens
1438    // instead, so these consts are never wider than the struct they describe.
1439    // The `__STRUCT_PATH_PUB_FIELDS*` consts only ever list fields already
1440    // visible outside the crate, so they keep the struct's own visibility
1441    // unconditionally.
1442    let all_fields_visibility: TokenStream = if item_is_plain_pub {
1443        "pub(crate)".parse().expect("`pub(crate)` always parses")
1444    } else {
1445        visibility_tokens.clone()
1446    };
1447
1448    // The three helpers below take a names array as a runtime-shaped
1449    // parameter rather than reading `Self::__STRUCT_PATH_*_FIELDS`
1450    // themselves, so paths!'s nested `Type::*` form can pass either the PUB
1451    // or the ALL array through the same three functions. Their own
1452    // visibility therefore stays at the struct's own tokens, the same as
1453    // `__STRUCT_PATH_PUB_FIELDS*` above, rather than the `pub(crate)` cap
1454    // applied to `__STRUCT_PATH_ALL_FIELDS*`: every `paths!` call that uses
1455    // the PUB array, including from a downstream crate, calls these
1456    // helpers too, so capping them the same way as the ALL array would
1457    // break that ordinary case, not just a downstream crate's use of ALL.
1458    let generated = fill(
1459        "impl<GENERICS_IMPL> NAME<GENERICS_TYPE> WHERE_CLAUSE {
1460            #[doc(hidden)]
1461            VIS const __STRUCT_PATH_PUB_FIELDS: [&'static str; N_PUB] = PUB_FIELDS;
1462            #[doc(hidden)]
1463            VIS const __STRUCT_PATH_PUB_FIELDS_CAMEL: [&'static str; N_PUB] = PUB_FIELDS_CAMEL;
1464            #[doc(hidden)]
1465            VIS const __STRUCT_PATH_PUB_FIELDS_PASCAL: [&'static str; N_PUB] = PUB_FIELDS_PASCAL;
1466            #[doc(hidden)]
1467            ALL_VIS const __STRUCT_PATH_ALL_FIELDS: [&'static str; N_ALL] = ALL_FIELDS;
1468            #[doc(hidden)]
1469            ALL_VIS const __STRUCT_PATH_ALL_FIELDS_CAMEL: [&'static str; N_ALL] = ALL_FIELDS_CAMEL;
1470            #[doc(hidden)]
1471            ALL_VIS const __STRUCT_PATH_ALL_FIELDS_PASCAL: [&'static str; N_ALL] = ALL_FIELDS_PASCAL;
1472
1473            #[doc(hidden)]
1474            #[must_use]
1475            VIS const fn __struct_path_joined_len(prefix: &str, delim: &str, names: &[&str]) -> usize {
1476                let mut total = 0;
1477                let mut i = 0;
1478                while i < names.len() {
1479                    total += prefix.len() + delim.len() + names[i].len();
1480                    i += 1;
1481                }
1482                total
1483            }
1484
1485            #[doc(hidden)]
1486            #[must_use]
1487            VIS const fn __struct_path_joined_bytes<const TOTAL: usize>(
1488                prefix: &str,
1489                delim: &str,
1490                names: &[&str],
1491            ) -> [u8; TOTAL] {
1492                let mut buf = [0u8; TOTAL];
1493                let mut pos = 0;
1494                let mut i = 0;
1495                while i < names.len() {
1496                    let parts = [prefix.as_bytes(), delim.as_bytes(), names[i].as_bytes()];
1497                    let mut p = 0;
1498                    while p < parts.len() {
1499                        let mut b = 0;
1500                        while b < parts[p].len() {
1501                            buf[pos] = parts[p][b];
1502                            pos += 1;
1503                            b += 1;
1504                        }
1505                        p += 1;
1506                    }
1507                    i += 1;
1508                }
1509                buf
1510            }
1511
1512            #[doc(hidden)]
1513            #[must_use]
1514            VIS const fn __struct_path_joined<const N: usize, const TOTAL: usize>(
1515                bytes: &'static [u8; TOTAL],
1516                prefix: &str,
1517                delim: &str,
1518                names: &[&str; N],
1519            ) -> [&'static str; N] {
1520                let mut out = [\"\"; N];
1521                let mut pos = 0;
1522                let mut i = 0;
1523                while i < N {
1524                    let len = prefix.len() + delim.len() + names[i].len();
1525                    let (_, rest) = bytes.split_at(pos);
1526                    let (piece, _) = rest.split_at(len);
1527                    out[i] = match core::str::from_utf8(piece) {
1528                        Ok(s) => s,
1529                        Err(_) => panic!(\"struct-path: joined path is not valid UTF-8\"),
1530                    };
1531                    pos += len;
1532                    i += 1;
1533                }
1534                out
1535            }
1536        }",
1537        name_span,
1538        &[
1539            ("GENERICS_IMPL", &generics_impl),
1540            ("GENERICS_TYPE", &generics_type),
1541            ("NAME", &name_tokens),
1542            ("WHERE_CLAUSE", &where_clause),
1543            ("VIS", &visibility_tokens),
1544            ("ALL_VIS", &all_fields_visibility),
1545            ("N_PUB", &n_pub),
1546            ("N_ALL", &n_all),
1547            ("PUB_FIELDS", &pub_array),
1548            ("PUB_FIELDS_CAMEL", &pub_camel_array),
1549            ("PUB_FIELDS_PASCAL", &pub_pascal_array),
1550            ("ALL_FIELDS", &all_array),
1551            ("ALL_FIELDS_CAMEL", &all_camel_array),
1552            ("ALL_FIELDS_PASCAL", &all_pascal_array),
1553        ],
1554    );
1555    Ok(generated)
1556}
1557
1558/// Skips a run of `#[...]` attributes (including `#[doc = "..."]` doc
1559/// comments), advancing `pos` past each one.
1560fn skip_attributes(tokens: &[TokenTree], pos: &mut usize) {
1561    while matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '#') {
1562        *pos += 1;
1563        if matches!(tokens.get(*pos), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket)
1564        {
1565            *pos += 1;
1566        }
1567    }
1568}
1569
1570/// Reports how many raw tokens at the front of `tokens` are a `pub`/
1571/// `pub(...)` visibility prefix -- 0 for private, 1 for plain `pub`, 2 for
1572/// `pub(crate)`/`pub(super)`/`pub(in ..)` -- and whether that prefix is
1573/// plain `pub`. A `$v:vis`/`$fv:vis` macro_rules fragment reaches the derive
1574/// re-emitted as a `Group(Delimiter::None)`, empty when the fragment matched
1575/// private and containing the literal `pub`/`pub(...)` tokens otherwise;
1576/// this unwraps exactly one such group before classifying, and reports its
1577/// prefix length as the single outer token regardless of what was inside.
1578fn parse_visibility_prefix(tokens: &[TokenTree]) -> (usize, bool) {
1579    if let Some(TokenTree::Group(g)) = tokens.first() {
1580        if g.delimiter() == Delimiter::None {
1581            let inner: Vec<TokenTree> = g.stream().into_iter().collect();
1582            let (_, is_plain_pub) = parse_visibility_prefix(&inner);
1583            return (1, is_plain_pub);
1584        }
1585    }
1586    if matches!(tokens.first(), Some(TokenTree::Ident(id)) if id.to_string() == "pub") {
1587        if matches!(tokens.get(1), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis)
1588        {
1589            return (2, false);
1590        }
1591        return (1, true);
1592    }
1593    (0, false)
1594}
1595
1596/// Advances `pos` past a field's visibility and reports whether it was
1597/// plain `pub`: `pub(crate)`, `pub(super)`, `pub(in ..)` and private fields
1598/// all return `false`, since none of them are visible outside the crate
1599/// the way plain `pub` is.
1600fn parse_field_visibility(tokens: &[TokenTree], pos: &mut usize) -> bool {
1601    let (len, is_plain_pub) = parse_visibility_prefix(&tokens[*pos..]);
1602    *pos += len;
1603    is_plain_pub
1604}
1605
1606/// Splits `tokens` on commas that are not inside `<...>`, so a field's
1607/// type, e.g. `HashMap<String, u64>`, keeps its own comma out of the split.
1608/// Angle brackets are plain `Punct` tokens here, not a delimited `Group`, so
1609/// depth has to be tracked explicitly. A trailing comma produces no empty
1610/// trailing segment. Shared by generic-parameter and struct-field splitting.
1611///
1612/// The `>` of a `->` return-type arrow is not a closing angle bracket, so it
1613/// must not decrement `depth`; it is told apart from a real one by the `-`
1614/// immediately before it being tokenized `Joint` (no space before this `>`),
1615/// which is otherwise only true of operators Rust's grammar never places
1616/// right before `>`. Depth is also floored at 0, so a field list with no
1617/// generics at all (where an arrow's `>` would otherwise be the only `<`/`>`
1618/// seen) cannot be pushed negative and starve every comma split after it.
1619fn split_top_level_commas(tokens: &[TokenTree]) -> Vec<Vec<TokenTree>> {
1620    let mut segments = Vec::new();
1621    let mut current = Vec::new();
1622    let mut depth: i32 = 0;
1623    let mut prev_was_arrow_dash = false;
1624    for token in tokens {
1625        match token {
1626            TokenTree::Punct(p) if *p == '<' => {
1627                depth += 1;
1628                current.push(token.clone());
1629            }
1630            TokenTree::Punct(p) if *p == '>' => {
1631                if !prev_was_arrow_dash {
1632                    depth = (depth - 1).max(0);
1633                }
1634                current.push(token.clone());
1635            }
1636            TokenTree::Punct(p) if *p == ',' && depth == 0 => {
1637                segments.push(std::mem::take(&mut current));
1638            }
1639            other => current.push(other.clone()),
1640        }
1641        prev_was_arrow_dash =
1642            matches!(token, TokenTree::Punct(p) if *p == '-' && p.spacing() == Spacing::Joint);
1643    }
1644    if !current.is_empty() {
1645        segments.push(current);
1646    }
1647    segments
1648}
1649
1650/// Recognizes a single generic parameter as a lifetime, optionally with
1651/// lifetime bounds (`'a`, `'a:`, or `'a: 'b + 'c`), and returns just the
1652/// bare `'a` tokens for use in the derived impl's `Name<'a>`. Returns `None`
1653/// for a type or const parameter, which `StructPath` does not support.
1654fn lifetime_param_name(segment: &[TokenTree]) -> Option<Vec<TokenTree>> {
1655    if segment.len() < 2 {
1656        return None;
1657    }
1658    if !matches!(&segment[0], TokenTree::Punct(p) if *p == '\'') {
1659        return None;
1660    }
1661    if !matches!(&segment[1], TokenTree::Ident(_)) {
1662        return None;
1663    }
1664    let bare = segment[0..2].to_vec();
1665    if segment.len() == 2 {
1666        return Some(bare);
1667    }
1668    if !matches!(&segment[2], TokenTree::Punct(p) if *p == ':') {
1669        return None;
1670    }
1671    if segment.len() == 3 {
1672        // `'a:` with no bound after the colon -- valid Rust, equivalent to
1673        // no colon at all.
1674        return Some(bare);
1675    }
1676    let mut i = 3;
1677    loop {
1678        if i + 1 >= segment.len() {
1679            return None;
1680        }
1681        let is_lifetime = matches!(&segment[i], TokenTree::Punct(p) if *p == '\'')
1682            && matches!(&segment[i + 1], TokenTree::Ident(_));
1683        if !is_lifetime {
1684            return None;
1685        }
1686        i += 2;
1687        if i == segment.len() {
1688            break;
1689        }
1690        if !matches!(&segment[i], TokenTree::Punct(p) if *p == '+') {
1691            return None;
1692        }
1693        i += 1;
1694    }
1695    Some(bare)
1696}
1697
1698/// Parses the optional `<...>` generic parameter list starting at `*pos`,
1699/// advancing `pos` past it, and returns the tokens for `impl<GENERICS_IMPL>`
1700/// and the bare lifetime names for `Name<GENERICS_TYPE>`. Both are empty
1701/// when the struct has no generics.
1702fn parse_lifetime_generics(
1703    tokens: &[TokenTree],
1704    pos: &mut usize,
1705) -> Result<(TokenStream, TokenStream), TokenStream> {
1706    if !matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '<') {
1707        return Ok((TokenStream::new(), TokenStream::new()));
1708    }
1709    let open_span = match &tokens[*pos] {
1710        TokenTree::Punct(p) => p.span(),
1711        _ => unreachable!(),
1712    };
1713    *pos += 1;
1714    let start = *pos;
1715    let mut depth = 1;
1716    while depth > 0 {
1717        match tokens.get(*pos) {
1718            Some(TokenTree::Punct(p)) if *p == '<' => depth += 1,
1719            Some(TokenTree::Punct(p)) if *p == '>' => depth -= 1,
1720            Some(_) => {}
1721            None => {
1722                return Err(compile_error_at(
1723                    "StructPath: unterminated generic parameter list",
1724                    open_span,
1725                ))
1726            }
1727        }
1728        *pos += 1;
1729    }
1730    let generics_tokens = &tokens[start..*pos - 1];
1731
1732    let mut bare_lifetimes: Vec<TokenTree> = Vec::new();
1733    for (i, segment) in split_top_level_commas(generics_tokens)
1734        .into_iter()
1735        .enumerate()
1736    {
1737        if i > 0 {
1738            bare_lifetimes.extend(fill(",", open_span, &[]));
1739        }
1740        match lifetime_param_name(&segment) {
1741            Some(bare) => bare_lifetimes.extend(bare),
1742            None => {
1743                let span = segment.first().map(|t| t.span()).unwrap_or(open_span);
1744                return Err(compile_error_at(
1745                    "StructPath only supports lifetime generic parameters",
1746                    span,
1747                ));
1748            }
1749        }
1750    }
1751
1752    let generics_impl: TokenStream = generics_tokens.iter().cloned().collect();
1753    let generics_type: TokenStream = bare_lifetimes.into_iter().collect();
1754    Ok((generics_impl, generics_type))
1755}
1756
1757/// Finds the struct's field body in `remaining` (everything after its name
1758/// and generics) and returns the `where` clause tokens in front of it, if
1759/// any, alongside it. The body's shape is read off the item's *last* token:
1760/// a `{ ... }` group for named fields (returned with any `where` clause
1761/// before it), or a trailing `;` for a tuple or unit struct (both rejected,
1762/// since only named fields have names to record). Deciding this from the
1763/// end, rather than scanning for the first `(...)` or `{...}` seen, is what
1764/// keeps a `(...)` written inside the `where` clause itself (e.g.
1765/// `where (): Sized`) from being mistaken for a tuple-struct body: nothing
1766/// before the item's own last token can be that body.
1767fn find_fields_group(remaining: &[TokenTree]) -> Result<(TokenStream, Group), TokenStream> {
1768    match remaining.last() {
1769        Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
1770            let where_clause: TokenStream =
1771                remaining[..remaining.len() - 1].iter().cloned().collect();
1772            Ok((where_clause, g.clone()))
1773        }
1774        Some(TokenTree::Punct(p)) if *p == ';' => match remaining.first() {
1775            Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
1776                Err(compile_error_at(
1777                    "StructPath supports structs with named fields only, not tuple structs",
1778                    g.span(),
1779                ))
1780            }
1781            _ => Err(compile_error_at(
1782                "StructPath supports structs with named fields only, not unit structs",
1783                p.span(),
1784            )),
1785        },
1786        _ => Err(compile_error_at(
1787            "StructPath: expected a struct body with named fields",
1788            Span::call_site(),
1789        )),
1790    }
1791}
1792
1793/// Parses a named-field struct body into its field names and plain-`pub`
1794/// flags, in declaration order. A field's type is skipped rather than
1795/// parsed: the derive never needs to reference a field's value, only its
1796/// name, so the type tokens (however they nest, e.g. `HashMap<String,
1797/// u64>`) are dropped once the name before the first top-level `:` is
1798/// found.
1799fn parse_derived_fields(fields_stream: TokenStream) -> Result<Vec<DerivedField>, TokenStream> {
1800    let tokens: Vec<TokenTree> = fields_stream.into_iter().collect();
1801    let mut fields = Vec::new();
1802    for segment in split_top_level_commas(&tokens) {
1803        let mut pos = 0;
1804        skip_attributes(&segment, &mut pos);
1805        let is_plain_pub = parse_field_visibility(&segment, &mut pos);
1806        let name_ident = match segment.get(pos) {
1807            Some(TokenTree::Ident(id)) => id,
1808            _ => {
1809                return Err(compile_error_at(
1810                    "StructPath: expected a field name",
1811                    segment
1812                        .first()
1813                        .map(|t| t.span())
1814                        .unwrap_or_else(Span::call_site),
1815                ))
1816            }
1817        };
1818        let name = name_ident.to_string();
1819        let name = name.strip_prefix("r#").unwrap_or(&name).to_string();
1820        fields.push(DerivedField { name, is_plain_pub });
1821    }
1822    Ok(fields)
1823}
1824
1825/// Builds a `[&'static str; N]` initializer literal from field-name
1826/// strings, which are always valid identifiers (or their camelCase/
1827/// PascalCase conversions), so no escaping beyond wrapping each one in
1828/// quotes is needed.
1829fn array_literal_tokens(values: &[String]) -> TokenStream {
1830    let joined = values
1831        .iter()
1832        .map(|v| format!("\"{}\"", v))
1833        .collect::<Vec<_>>()
1834        .join(",");
1835    format!("[{}]", joined)
1836        .parse()
1837        .expect("field names always produce a valid array literal")
1838}