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