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 `;` straight after a `,`,
921/// and no key repeated, whether within one group or across groups.
922enum OptionParseState {
923    /// At the start of a group, where a `;` closes it as an empty group.
924    GroupStart,
925    /// After a `,`, where only an option name may follow.
926    Key,
927    Equals,
928    Value,
929    CommaOrEnd,
930}
931
932/// Parses the `key = "value", ...; key = "value", ...` option grammar
933/// accepted after `Type::*;` (and, for the nested `Type::*` form, after the
934/// closing `)`). `tokens` starts after the `;` that opens the first group.
935/// Keeps source order so the caller can report the first unknown key as
936/// written rather than in a hash map's arbitrary order, and rejects a key
937/// repeated later in the options, in the same group or a later one, rather
938/// than silently keeping whichever value a lookup finds first.
939fn parse_all_fields_options(tokens: &[TokenTree]) -> Result<Vec<(String, String)>, String> {
940    let mut options: Vec<(String, String)> = Vec::new();
941    let mut pending_key: Option<String> = None;
942    let mut state = OptionParseState::GroupStart;
943
944    for token_tree in tokens {
945        state = match (state, token_tree) {
946            (OptionParseState::GroupStart | OptionParseState::Key, TokenTree::Ident(id)) => {
947                pending_key = Some(id.to_string());
948                OptionParseState::Equals
949            }
950            (OptionParseState::Equals, TokenTree::Punct(p)) if *p == '=' => OptionParseState::Value,
951            (OptionParseState::Value, TokenTree::Literal(lit)) => {
952                let key = pending_key.take().expect("set on entering Equals/Value");
953                if options.iter().any(|(existing, _)| existing == &key) {
954                    return Err(format!("Duplicate option is specified: {}", key));
955                }
956                options.push((key, unquote_literal(lit)?));
957                OptionParseState::CommaOrEnd
958            }
959            (OptionParseState::CommaOrEnd, TokenTree::Punct(p)) if *p == ',' => {
960                OptionParseState::Key
961            }
962            // Later groups exist so a wrapper macro can append its own
963            // options after the caller's. They share one key space with the
964            // first group: a key repeated in a later group is still a
965            // duplicate, not an override.
966            (OptionParseState::CommaOrEnd | OptionParseState::GroupStart, TokenTree::Punct(p))
967                if *p == ';' =>
968            {
969                OptionParseState::GroupStart
970            }
971            (state, other) => {
972                let expected = match state {
973                    OptionParseState::GroupStart | OptionParseState::Key => "an option name",
974                    OptionParseState::Equals => "`=`",
975                    OptionParseState::Value => "a quoted string (\"...\") or char ('.') literal",
976                    OptionParseState::CommaOrEnd => "`,` or `;`",
977                };
978                return Err(format!(
979                    "Expected {} in `Type::*` options, found `{}`",
980                    expected, other
981                ));
982            }
983        };
984    }
985
986    match state {
987        OptionParseState::GroupStart | OptionParseState::Key | OptionParseState::CommaOrEnd => {
988            Ok(options)
989        }
990        OptionParseState::Equals | OptionParseState::Value => Err(format!(
991            "Missing a value for option `{}`",
992            pending_key.expect("set on entering Equals/Value")
993        )),
994    }
995}
996
997#[proc_macro]
998pub fn path(struct_path_stream: TokenStream) -> TokenStream {
999    match path_impl(struct_path_stream) {
1000        Ok(stream) => stream,
1001        Err(message) => compile_error_for(message),
1002    }
1003}
1004
1005/// Whether `stream` contains a `*` at any depth, including inside a
1006/// parenthesized nested-`Type::*` group -- `path!` rejects both shapes with
1007/// the same message, so it needs the deeper scan `paths!`'s own two `*`
1008/// parsers do not, since each of those looks for a specific placement
1009/// rather than asking whether one exists anywhere at all.
1010fn contains_star(stream: TokenStream) -> bool {
1011    stream.into_iter().any(|token| match token {
1012        TokenTree::Punct(p) if p == '*' => true,
1013        TokenTree::Group(g) => contains_star(g.stream()),
1014        _ => false,
1015    })
1016}
1017
1018fn path_impl(struct_path_stream: TokenStream) -> Result<TokenStream, String> {
1019    // `Type::*` (all declared fields) has an array result, so only `paths!`
1020    // can return it; `path!` always returns a single string. The bare form
1021    // sits at the top level, but the nested form's `*` is inside a
1022    // parenthesized group, so this checks every depth, not just the top one.
1023    if contains_star(struct_path_stream.clone()) {
1024        return Err(
1025            "`*` for all declared fields is only supported by paths!, not path!".to_string(),
1026        );
1027    }
1028
1029    let mut current_struct_name_tokens: Vec<TokenTree> = Vec::new();
1030
1031    let mut opened_struct = false;
1032    let mut colons_counter = 0;
1033    let mut options_opened = false;
1034
1035    let mut current_field_tokens: Vec<TokenTree> = Vec::new();
1036
1037    let mut current_option_name: Option<String> = None;
1038    let mut expect_option_value: bool = false;
1039
1040    let mut options: HashMap<String, String> = HashMap::new();
1041    let mut found_structs: FoundStructs = Vec::new();
1042
1043    for token_tree in struct_path_stream.into_iter() {
1044        match token_tree {
1045            TokenTree::Ident(id) if current_struct_name_tokens.is_empty() => {
1046                current_struct_name_tokens.push(TokenTree::Ident(id));
1047            }
1048            TokenTree::Punct(punct)
1049                if !current_struct_name_tokens.is_empty()
1050                    && !opened_struct
1051                    && punct == ':'
1052                    && colons_counter < 2 =>
1053            {
1054                colons_counter += 1;
1055                if colons_counter > 1 {
1056                    opened_struct = true;
1057                }
1058            }
1059            TokenTree::Ident(id) if opened_struct => {
1060                colons_counter = 0;
1061                current_field_tokens.push(TokenTree::Ident(id));
1062            }
1063            TokenTree::Punct(punct)
1064                if !current_struct_name_tokens.is_empty()
1065                    && opened_struct
1066                    && punct == ':'
1067                    && colons_counter < 2 =>
1068            {
1069                colons_counter += 1;
1070                opened_struct = false;
1071                let field_tokens = std::mem::take(&mut current_field_tokens);
1072                if !fold_field_into_struct_name(field_tokens, &mut current_struct_name_tokens) {
1073                    return Err(format!(
1074                        "Unexpected punctuation input for struct path group parameters: {:?}",
1075                        punct
1076                    ));
1077                }
1078            }
1079            TokenTree::Punct(punct) if opened_struct && (punct == '.' || punct == '~') => {
1080                if current_field_tokens.is_empty() {
1081                    return Err(format!(
1082                        "Unexpected punctuation input for struct path group parameters: {:?}",
1083                        punct
1084                    ));
1085                }
1086                current_field_tokens.push(TokenTree::Punct(punct));
1087            }
1088            TokenTree::Punct(punct) if !options_opened && opened_struct && punct == ',' => {
1089                opened_struct = false;
1090                colons_counter = 0;
1091                if current_struct_name_tokens.is_empty() {
1092                    return Err("Unexpected comma with empty definitions!".to_string());
1093                }
1094                if current_field_tokens.is_empty() {
1095                    return Err(format!(
1096                        "Unexpected comma with empty fields for {}!",
1097                        render_text(&current_struct_name_tokens)
1098                    ));
1099                }
1100                found_structs.push((
1101                    std::mem::take(&mut current_struct_name_tokens),
1102                    vec![std::mem::take(&mut current_field_tokens)],
1103                ));
1104            }
1105            TokenTree::Punct(punct) if punct == ';' && opened_struct && !options_opened => {
1106                options_opened = true;
1107                opened_struct = false;
1108            }
1109            TokenTree::Ident(id) if options_opened && !expect_option_value => {
1110                current_option_name = Some(id.to_string())
1111            }
1112            TokenTree::Ident(id) if options_opened && expect_option_value => {
1113                expect_option_value = false;
1114                match current_option_name.take() {
1115                    Some(option_name) => {
1116                        options.insert(option_name, id.to_string());
1117                    }
1118                    _ => {
1119                        return Err("Wrong options format".to_string());
1120                    }
1121                }
1122            }
1123            TokenTree::Literal(lit) 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, unquote_literal(&lit)?);
1128                    }
1129                    _ => {
1130                        return Err("Wrong options format".to_string());
1131                    }
1132                }
1133            }
1134            TokenTree::Punct(punct) if options_opened && punct == '=' => {
1135                expect_option_value = true;
1136            }
1137            TokenTree::Punct(punct) if options_opened && punct == ',' => {
1138                expect_option_value = false;
1139            }
1140            // A second (or later) `;` starts another options group, so a
1141            // wrapper macro can append its own options after the caller's.
1142            // Groups share one key space; a repeated key takes its last value.
1143            TokenTree::Punct(punct) if options_opened && punct == ';' => {
1144                expect_option_value = false;
1145            }
1146            others => {
1147                return Err(format!(
1148                    "Unexpected input for struct path parameters: {:?}",
1149                    others
1150                ));
1151            }
1152        }
1153    }
1154
1155    if !current_struct_name_tokens.is_empty() && !current_field_tokens.is_empty() {
1156        found_structs.push((current_struct_name_tokens, vec![current_field_tokens]));
1157    }
1158
1159    if found_structs.is_empty() {
1160        return Err("Unexpected empty path definition!".to_string());
1161    }
1162
1163    let all_check_functions = generate_checks_code_for(&found_structs);
1164    // `~` marks a collection step and must become the path separator before
1165    // options are applied, or a segment joined by `~` is treated as one
1166    // field name instead of two and never gets the chosen delim or
1167    // per-segment case conversion.
1168    let full_field_path = found_structs
1169        .iter()
1170        .flat_map(|(_, fields)| fields.iter())
1171        .map(|tokens| render_text(tokens))
1172        .collect::<Vec<_>>()
1173        .join(".")
1174        .replace('~', ".");
1175    let final_field_path = apply_options(&options, full_field_path)?;
1176    let fields_str = format!("\"{}\"", final_field_path);
1177    let fields_tokens: TokenStream = fields_str
1178        .parse()
1179        .map_err(|_| format!("Generated code failed to parse: {}", fields_str))?;
1180    let mut block_contents = all_check_functions;
1181    block_contents.extend(fields_tokens);
1182    Ok(TokenStream::from(TokenTree::Group(Group::new(
1183        Delimiter::Brace,
1184        block_contents,
1185    ))))
1186}
1187
1188/// Builds the `const _: fn(&Type) = |t: &Type| { let _ = &t.field; };`
1189/// check for every discovered `Type::field` pair. The user's type and field
1190/// tokens keep their spans, so a type error is reported on the exact token
1191/// that is wrong; each `~` becomes `.iter().next().unwrap().` spanned at that
1192/// `~`, so a stepped-into type without `iter()` is blamed on the `~`.
1193fn generate_checks_code_for(found_structs: &FoundStructs) -> TokenStream {
1194    let mut all_check_functions = TokenStream::new();
1195    for (struct_name_tokens, struct_fields) in found_structs {
1196        let struct_name: TokenStream = struct_name_tokens.iter().cloned().collect();
1197        for field_tokens in struct_fields {
1198            all_check_functions.extend(fill(
1199                "const _: fn(&STRUCT) = |t: &STRUCT| { let _ = &t.FIELD; };",
1200                Span::call_site(),
1201                &[
1202                    ("STRUCT", &struct_name),
1203                    ("FIELD", &field_check_tokens(field_tokens)),
1204                ],
1205            ));
1206        }
1207    }
1208    all_check_functions
1209}
1210
1211/// Maps a field path's tokens to themselves, except a `~` step, which
1212/// becomes `.iter().next().unwrap().` spanned at that `~` so a stepped-into
1213/// type without `iter()` is blamed on the `~` rather than on the check as a
1214/// whole. Shared by every check this macro emits, flat or nested, so they
1215/// can never disagree on what `~` means.
1216fn field_check_tokens(field_tokens: &[TokenTree]) -> TokenStream {
1217    field_tokens
1218        .iter()
1219        .map(|token| match token {
1220            TokenTree::Punct(punct) if *punct == '~' => {
1221                fill(".iter().next().unwrap().", punct.span(), &[])
1222            }
1223            token => token.clone().into(),
1224        })
1225        .collect()
1226}
1227
1228/// Parses `template` into tokens spanned at `span`, replacing each ident
1229/// named in `substitutions` with the given tokens, which keep their own
1230/// spans. `template` must be valid Rust tokens.
1231fn fill(template: &str, span: Span, substitutions: &[(&str, &TokenStream)]) -> TokenStream {
1232    fn walk(stream: TokenStream, span: Span, subs: &[(&str, &TokenStream)]) -> TokenStream {
1233        stream
1234            .into_iter()
1235            .map(|mut token| {
1236                match &token {
1237                    TokenTree::Ident(ident) => {
1238                        let name = ident.to_string();
1239                        if let Some((_, tokens)) = subs.iter().find(|(n, _)| *n == name) {
1240                            return (*tokens).clone();
1241                        }
1242                    }
1243                    TokenTree::Group(group) => {
1244                        let stream = walk(group.stream(), span, subs);
1245                        token = TokenTree::Group(Group::new(group.delimiter(), stream));
1246                    }
1247                    _ => {}
1248                }
1249                token.set_span(span);
1250                token.into()
1251            })
1252            .collect()
1253    }
1254    walk(
1255        template.parse().expect("templates are valid tokens"),
1256        span,
1257        substitutions,
1258    )
1259}
1260
1261/// Strips the surrounding quotes from a plain string (`"..."`) or char
1262/// (`'.'`) literal. Escapes keep their source spelling because the value is
1263/// pasted back into a string literal in the generated code; decoding them
1264/// would change the output. Any other literal kind (numeric, byte string,
1265/// byte char, raw string) is rejected, since its text has no such quotes to
1266/// strip and would produce invalid generated code.
1267fn unquote_literal(lit: &proc_macro::Literal) -> Result<String, String> {
1268    let text = lit.to_string();
1269    let bytes = text.as_bytes();
1270    let is_quoted = bytes.len() >= 2
1271        && ((bytes[0] == b'"' && bytes[bytes.len() - 1] == b'"')
1272            || (bytes[0] == b'\'' && bytes[bytes.len() - 1] == b'\''));
1273    if is_quoted {
1274        Ok(text[1..text.len() - 1].to_string())
1275    } else {
1276        Err(format!(
1277            "Unsupported option value {}: expected a string (\"...\") or char ('.') literal",
1278            text
1279        ))
1280    }
1281}
1282
1283/// The case conversion shared by `path!`/`paths!`'s `case = "..."` option
1284/// and `#[derive(StructPath)]`'s `_CAMEL`/`_PASCAL` field-name constants, so
1285/// the two can never disagree on what "camel" or "pascal" means for a
1286/// snake_case field name.
1287fn convert_case_by_name(field_name: &str, case_name: &str) -> Result<String, String> {
1288    match case_name {
1289        "camel" => Ok(field_name.from_case(Case::Snake).to_case(Case::Camel)),
1290        "pascal" => Ok(field_name.from_case(Case::Snake).to_case(Case::Pascal)),
1291        another => Err(format!("Unknown case is specified: {}", another)),
1292    }
1293}
1294
1295/// Converts every name in `names` with `convert_case_by_name`, for the two
1296/// known case names the derive itself always passes; a case name it does
1297/// not recognize is a bug in the derive, not a user input to report.
1298fn convert_all(names: &[String], case_name: &str) -> Vec<String> {
1299    names
1300        .iter()
1301        .map(|n| {
1302            convert_case_by_name(n, case_name)
1303                .unwrap_or_else(|_| panic!("{:?} is always a known case", case_name))
1304        })
1305        .collect()
1306}
1307
1308fn apply_options(options: &HashMap<String, String>, field_path: String) -> Result<String, String> {
1309    let delim = options
1310        .get("delim")
1311        .as_ref()
1312        .map(|s| s.as_str())
1313        .unwrap_or(".");
1314    let case = options.get("case");
1315    let segments = field_path
1316        .split('.')
1317        .map(|field_name| match case {
1318            Some(case_value) => convert_case_by_name(field_name, case_value),
1319            None => Ok(field_name.to_string()),
1320        })
1321        .collect::<Result<Vec<String>, String>>()?;
1322    Ok(segments.join(delim))
1323}
1324
1325/// `#[derive(StructPath)]` records a struct's declared field names as
1326/// `#[doc(hidden)]` associated consts, so `paths!(Type::*)` can return them
1327/// without the caller listing every field by hand. Supports only a struct
1328/// with named fields and, at most, lifetime generic parameters; anything
1329/// else is a `compile_error!` on the offending item, spanned at the
1330/// specific token that disqualifies it.
1331#[proc_macro_derive(StructPath)]
1332pub fn derive_struct_path(input: TokenStream) -> TokenStream {
1333    match derive_struct_path_impl(input) {
1334        Ok(stream) => stream,
1335        Err(error_stream) => error_stream,
1336    }
1337}
1338
1339/// One field found in a derived struct's body: its declared name, with any
1340/// `r#` prefix stripped since the generated string is a JSON-path-style
1341/// segment rather than a Rust identifier, and whether it was declared with
1342/// plain `pub` — `pub(crate)`, `pub(super)`, `pub(in ..)` and private
1343/// fields are all "not plain pub".
1344struct DerivedField {
1345    name: String,
1346    is_plain_pub: bool,
1347}
1348
1349fn derive_struct_path_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
1350    let tokens: Vec<TokenTree> = input.into_iter().collect();
1351    let mut pos = 0;
1352
1353    skip_attributes(&tokens, &mut pos);
1354
1355    let visibility_start = pos;
1356    let (vis_len, item_is_plain_pub) = parse_visibility_prefix(&tokens[pos..]);
1357    pos += vis_len;
1358    let visibility_tokens: TokenStream = tokens[visibility_start..pos].iter().cloned().collect();
1359
1360    let (keyword, keyword_span) = match tokens.get(pos) {
1361        Some(TokenTree::Ident(id)) => (id.to_string(), id.span()),
1362        _ => {
1363            return Err(compile_error_at(
1364                "StructPath: unable to parse the derived item",
1365                Span::call_site(),
1366            ))
1367        }
1368    };
1369    if keyword != "struct" {
1370        return Err(compile_error_at(
1371            &format!(
1372                "StructPath supports structs with named fields only, not {}s",
1373                keyword
1374            ),
1375            keyword_span,
1376        ));
1377    }
1378    pos += 1;
1379
1380    let name_ident = match tokens.get(pos) {
1381        Some(TokenTree::Ident(id)) => id.clone(),
1382        _ => {
1383            return Err(compile_error_at(
1384                "StructPath: expected a struct name",
1385                keyword_span,
1386            ))
1387        }
1388    };
1389    let name_span = name_ident.span();
1390    pos += 1;
1391
1392    let (generics_impl, generics_type) = parse_lifetime_generics(&tokens, &mut pos)?;
1393    let (where_clause, fields_group) = find_fields_group(&tokens[pos..])?;
1394    let fields = parse_derived_fields(fields_group.stream())?;
1395
1396    let pub_names: Vec<String> = fields
1397        .iter()
1398        .filter(|f| f.is_plain_pub)
1399        .map(|f| f.name.clone())
1400        .collect();
1401    let all_names: Vec<String> = fields.iter().map(|f| f.name.clone()).collect();
1402
1403    let name_tokens = TokenStream::from(TokenTree::Ident(name_ident));
1404    let pub_array = array_literal_tokens(&pub_names);
1405    let pub_camel_array = array_literal_tokens(&convert_all(&pub_names, "camel"));
1406    let pub_pascal_array = array_literal_tokens(&convert_all(&pub_names, "pascal"));
1407    let all_array = array_literal_tokens(&all_names);
1408    let all_camel_array = array_literal_tokens(&convert_all(&all_names, "camel"));
1409    let all_pascal_array = array_literal_tokens(&convert_all(&all_names, "pascal"));
1410    let n_pub: TokenStream = pub_names
1411        .len()
1412        .to_string()
1413        .parse()
1414        .expect("a field count always parses as a literal");
1415    let n_all: TokenStream = all_names
1416        .len()
1417        .to_string()
1418        .parse()
1419        .expect("a field count always parses as a literal");
1420
1421    // `__STRUCT_PATH_ALL_FIELDS*` exposes every declared field's name,
1422    // private ones included. A plain `pub` struct's consts are capped to
1423    // `pub(crate)`, so a downstream crate cannot read private field names
1424    // through them and adding a private field is not a breaking change to a
1425    // `pub` array type. Any other struct visibility -- private, `pub(crate)`,
1426    // `pub(super)`, `pub(in ..)` -- reuses the struct's own visibility tokens
1427    // instead, so these consts are never wider than the struct they describe.
1428    // The `__STRUCT_PATH_PUB_FIELDS*` consts only ever list fields already
1429    // visible outside the crate, so they keep the struct's own visibility
1430    // unconditionally.
1431    let all_fields_visibility: TokenStream = if item_is_plain_pub {
1432        "pub(crate)".parse().expect("`pub(crate)` always parses")
1433    } else {
1434        visibility_tokens.clone()
1435    };
1436
1437    // The three helpers below take a names array as a runtime-shaped
1438    // parameter rather than reading `Self::__STRUCT_PATH_*_FIELDS`
1439    // themselves, so paths!'s nested `Type::*` form can pass either the PUB
1440    // or the ALL array through the same three functions. Their own
1441    // visibility therefore stays at the struct's own tokens, the same as
1442    // `__STRUCT_PATH_PUB_FIELDS*` above, rather than the `pub(crate)` cap
1443    // applied to `__STRUCT_PATH_ALL_FIELDS*`: every `paths!` call that uses
1444    // the PUB array, including from a downstream crate, calls these
1445    // helpers too, so capping them the same way as the ALL array would
1446    // break that ordinary case, not just a downstream crate's use of ALL.
1447    let generated = fill(
1448        "impl<GENERICS_IMPL> NAME<GENERICS_TYPE> WHERE_CLAUSE {
1449            #[doc(hidden)]
1450            VIS const __STRUCT_PATH_PUB_FIELDS: [&'static str; N_PUB] = PUB_FIELDS;
1451            #[doc(hidden)]
1452            VIS const __STRUCT_PATH_PUB_FIELDS_CAMEL: [&'static str; N_PUB] = PUB_FIELDS_CAMEL;
1453            #[doc(hidden)]
1454            VIS const __STRUCT_PATH_PUB_FIELDS_PASCAL: [&'static str; N_PUB] = PUB_FIELDS_PASCAL;
1455            #[doc(hidden)]
1456            ALL_VIS const __STRUCT_PATH_ALL_FIELDS: [&'static str; N_ALL] = ALL_FIELDS;
1457            #[doc(hidden)]
1458            ALL_VIS const __STRUCT_PATH_ALL_FIELDS_CAMEL: [&'static str; N_ALL] = ALL_FIELDS_CAMEL;
1459            #[doc(hidden)]
1460            ALL_VIS const __STRUCT_PATH_ALL_FIELDS_PASCAL: [&'static str; N_ALL] = ALL_FIELDS_PASCAL;
1461
1462            #[doc(hidden)]
1463            #[must_use]
1464            VIS const fn __struct_path_joined_len(prefix: &str, delim: &str, names: &[&str]) -> usize {
1465                let mut total = 0;
1466                let mut i = 0;
1467                while i < names.len() {
1468                    total += prefix.len() + delim.len() + names[i].len();
1469                    i += 1;
1470                }
1471                total
1472            }
1473
1474            #[doc(hidden)]
1475            #[must_use]
1476            VIS const fn __struct_path_joined_bytes<const TOTAL: usize>(
1477                prefix: &str,
1478                delim: &str,
1479                names: &[&str],
1480            ) -> [u8; TOTAL] {
1481                let mut buf = [0u8; TOTAL];
1482                let mut pos = 0;
1483                let mut i = 0;
1484                while i < names.len() {
1485                    let parts = [prefix.as_bytes(), delim.as_bytes(), names[i].as_bytes()];
1486                    let mut p = 0;
1487                    while p < parts.len() {
1488                        let mut b = 0;
1489                        while b < parts[p].len() {
1490                            buf[pos] = parts[p][b];
1491                            pos += 1;
1492                            b += 1;
1493                        }
1494                        p += 1;
1495                    }
1496                    i += 1;
1497                }
1498                buf
1499            }
1500
1501            #[doc(hidden)]
1502            #[must_use]
1503            VIS const fn __struct_path_joined<const N: usize, const TOTAL: usize>(
1504                bytes: &'static [u8; TOTAL],
1505                prefix: &str,
1506                delim: &str,
1507                names: &[&str; N],
1508            ) -> [&'static str; N] {
1509                let mut out = [\"\"; N];
1510                let mut pos = 0;
1511                let mut i = 0;
1512                while i < N {
1513                    let len = prefix.len() + delim.len() + names[i].len();
1514                    let (_, rest) = bytes.split_at(pos);
1515                    let (piece, _) = rest.split_at(len);
1516                    out[i] = match core::str::from_utf8(piece) {
1517                        Ok(s) => s,
1518                        Err(_) => panic!(\"struct-path: joined path is not valid UTF-8\"),
1519                    };
1520                    pos += len;
1521                    i += 1;
1522                }
1523                out
1524            }
1525        }",
1526        name_span,
1527        &[
1528            ("GENERICS_IMPL", &generics_impl),
1529            ("GENERICS_TYPE", &generics_type),
1530            ("NAME", &name_tokens),
1531            ("WHERE_CLAUSE", &where_clause),
1532            ("VIS", &visibility_tokens),
1533            ("ALL_VIS", &all_fields_visibility),
1534            ("N_PUB", &n_pub),
1535            ("N_ALL", &n_all),
1536            ("PUB_FIELDS", &pub_array),
1537            ("PUB_FIELDS_CAMEL", &pub_camel_array),
1538            ("PUB_FIELDS_PASCAL", &pub_pascal_array),
1539            ("ALL_FIELDS", &all_array),
1540            ("ALL_FIELDS_CAMEL", &all_camel_array),
1541            ("ALL_FIELDS_PASCAL", &all_pascal_array),
1542        ],
1543    );
1544    Ok(generated)
1545}
1546
1547/// Skips a run of `#[...]` attributes (including `#[doc = "..."]` doc
1548/// comments), advancing `pos` past each one.
1549fn skip_attributes(tokens: &[TokenTree], pos: &mut usize) {
1550    while matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '#') {
1551        *pos += 1;
1552        if matches!(tokens.get(*pos), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket)
1553        {
1554            *pos += 1;
1555        }
1556    }
1557}
1558
1559/// Reports how many raw tokens at the front of `tokens` are a `pub`/
1560/// `pub(...)` visibility prefix -- 0 for private, 1 for plain `pub`, 2 for
1561/// `pub(crate)`/`pub(super)`/`pub(in ..)` -- and whether that prefix is
1562/// plain `pub`. A `$v:vis`/`$fv:vis` macro_rules fragment reaches the derive
1563/// re-emitted as a `Group(Delimiter::None)`, empty when the fragment matched
1564/// private and containing the literal `pub`/`pub(...)` tokens otherwise;
1565/// this unwraps exactly one such group before classifying, and reports its
1566/// prefix length as the single outer token regardless of what was inside.
1567fn parse_visibility_prefix(tokens: &[TokenTree]) -> (usize, bool) {
1568    if let Some(TokenTree::Group(g)) = tokens.first() {
1569        if g.delimiter() == Delimiter::None {
1570            let inner: Vec<TokenTree> = g.stream().into_iter().collect();
1571            let (_, is_plain_pub) = parse_visibility_prefix(&inner);
1572            return (1, is_plain_pub);
1573        }
1574    }
1575    if matches!(tokens.first(), Some(TokenTree::Ident(id)) if id.to_string() == "pub") {
1576        if matches!(tokens.get(1), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis)
1577        {
1578            return (2, false);
1579        }
1580        return (1, true);
1581    }
1582    (0, false)
1583}
1584
1585/// Advances `pos` past a field's visibility and reports whether it was
1586/// plain `pub`: `pub(crate)`, `pub(super)`, `pub(in ..)` and private fields
1587/// all return `false`, since none of them are visible outside the crate
1588/// the way plain `pub` is.
1589fn parse_field_visibility(tokens: &[TokenTree], pos: &mut usize) -> bool {
1590    let (len, is_plain_pub) = parse_visibility_prefix(&tokens[*pos..]);
1591    *pos += len;
1592    is_plain_pub
1593}
1594
1595/// Splits `tokens` on commas that are not inside `<...>`, so a field's
1596/// type, e.g. `HashMap<String, u64>`, keeps its own comma out of the split.
1597/// Angle brackets are plain `Punct` tokens here, not a delimited `Group`, so
1598/// depth has to be tracked explicitly. A trailing comma produces no empty
1599/// trailing segment. Shared by generic-parameter and struct-field splitting.
1600///
1601/// The `>` of a `->` return-type arrow is not a closing angle bracket, so it
1602/// must not decrement `depth`; it is told apart from a real one by the `-`
1603/// immediately before it being tokenized `Joint` (no space before this `>`),
1604/// which is otherwise only true of operators Rust's grammar never places
1605/// right before `>`. Depth is also floored at 0, so a field list with no
1606/// generics at all (where an arrow's `>` would otherwise be the only `<`/`>`
1607/// seen) cannot be pushed negative and starve every comma split after it.
1608fn split_top_level_commas(tokens: &[TokenTree]) -> Vec<Vec<TokenTree>> {
1609    let mut segments = Vec::new();
1610    let mut current = Vec::new();
1611    let mut depth: i32 = 0;
1612    let mut prev_was_arrow_dash = false;
1613    for token in tokens {
1614        match token {
1615            TokenTree::Punct(p) if *p == '<' => {
1616                depth += 1;
1617                current.push(token.clone());
1618            }
1619            TokenTree::Punct(p) if *p == '>' => {
1620                if !prev_was_arrow_dash {
1621                    depth = (depth - 1).max(0);
1622                }
1623                current.push(token.clone());
1624            }
1625            TokenTree::Punct(p) if *p == ',' && depth == 0 => {
1626                segments.push(std::mem::take(&mut current));
1627            }
1628            other => current.push(other.clone()),
1629        }
1630        prev_was_arrow_dash =
1631            matches!(token, TokenTree::Punct(p) if *p == '-' && p.spacing() == Spacing::Joint);
1632    }
1633    if !current.is_empty() {
1634        segments.push(current);
1635    }
1636    segments
1637}
1638
1639/// Recognizes a single generic parameter as a lifetime, optionally with
1640/// lifetime bounds (`'a`, `'a:`, or `'a: 'b + 'c`), and returns just the
1641/// bare `'a` tokens for use in the derived impl's `Name<'a>`. Returns `None`
1642/// for a type or const parameter, which `StructPath` does not support.
1643fn lifetime_param_name(segment: &[TokenTree]) -> Option<Vec<TokenTree>> {
1644    if segment.len() < 2 {
1645        return None;
1646    }
1647    if !matches!(&segment[0], TokenTree::Punct(p) if *p == '\'') {
1648        return None;
1649    }
1650    if !matches!(&segment[1], TokenTree::Ident(_)) {
1651        return None;
1652    }
1653    let bare = segment[0..2].to_vec();
1654    if segment.len() == 2 {
1655        return Some(bare);
1656    }
1657    if !matches!(&segment[2], TokenTree::Punct(p) if *p == ':') {
1658        return None;
1659    }
1660    if segment.len() == 3 {
1661        // `'a:` with no bound after the colon -- valid Rust, equivalent to
1662        // no colon at all.
1663        return Some(bare);
1664    }
1665    let mut i = 3;
1666    loop {
1667        if i + 1 >= segment.len() {
1668            return None;
1669        }
1670        let is_lifetime = matches!(&segment[i], TokenTree::Punct(p) if *p == '\'')
1671            && matches!(&segment[i + 1], TokenTree::Ident(_));
1672        if !is_lifetime {
1673            return None;
1674        }
1675        i += 2;
1676        if i == segment.len() {
1677            break;
1678        }
1679        if !matches!(&segment[i], TokenTree::Punct(p) if *p == '+') {
1680            return None;
1681        }
1682        i += 1;
1683    }
1684    Some(bare)
1685}
1686
1687/// Parses the optional `<...>` generic parameter list starting at `*pos`,
1688/// advancing `pos` past it, and returns the tokens for `impl<GENERICS_IMPL>`
1689/// and the bare lifetime names for `Name<GENERICS_TYPE>`. Both are empty
1690/// when the struct has no generics.
1691fn parse_lifetime_generics(
1692    tokens: &[TokenTree],
1693    pos: &mut usize,
1694) -> Result<(TokenStream, TokenStream), TokenStream> {
1695    if !matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '<') {
1696        return Ok((TokenStream::new(), TokenStream::new()));
1697    }
1698    let open_span = match &tokens[*pos] {
1699        TokenTree::Punct(p) => p.span(),
1700        _ => unreachable!(),
1701    };
1702    *pos += 1;
1703    let start = *pos;
1704    let mut depth = 1;
1705    while depth > 0 {
1706        match tokens.get(*pos) {
1707            Some(TokenTree::Punct(p)) if *p == '<' => depth += 1,
1708            Some(TokenTree::Punct(p)) if *p == '>' => depth -= 1,
1709            Some(_) => {}
1710            None => {
1711                return Err(compile_error_at(
1712                    "StructPath: unterminated generic parameter list",
1713                    open_span,
1714                ))
1715            }
1716        }
1717        *pos += 1;
1718    }
1719    let generics_tokens = &tokens[start..*pos - 1];
1720
1721    let mut bare_lifetimes: Vec<TokenTree> = Vec::new();
1722    for (i, segment) in split_top_level_commas(generics_tokens)
1723        .into_iter()
1724        .enumerate()
1725    {
1726        if i > 0 {
1727            bare_lifetimes.extend(fill(",", open_span, &[]));
1728        }
1729        match lifetime_param_name(&segment) {
1730            Some(bare) => bare_lifetimes.extend(bare),
1731            None => {
1732                let span = segment.first().map(|t| t.span()).unwrap_or(open_span);
1733                return Err(compile_error_at(
1734                    "StructPath only supports lifetime generic parameters",
1735                    span,
1736                ));
1737            }
1738        }
1739    }
1740
1741    let generics_impl: TokenStream = generics_tokens.iter().cloned().collect();
1742    let generics_type: TokenStream = bare_lifetimes.into_iter().collect();
1743    Ok((generics_impl, generics_type))
1744}
1745
1746/// Finds the struct's field body in `remaining` (everything after its name
1747/// and generics) and returns the `where` clause tokens in front of it, if
1748/// any, alongside it. The body's shape is read off the item's *last* token:
1749/// a `{ ... }` group for named fields (returned with any `where` clause
1750/// before it), or a trailing `;` for a tuple or unit struct (both rejected,
1751/// since only named fields have names to record). Deciding this from the
1752/// end, rather than scanning for the first `(...)` or `{...}` seen, is what
1753/// keeps a `(...)` written inside the `where` clause itself (e.g.
1754/// `where (): Sized`) from being mistaken for a tuple-struct body: nothing
1755/// before the item's own last token can be that body.
1756fn find_fields_group(remaining: &[TokenTree]) -> Result<(TokenStream, Group), TokenStream> {
1757    match remaining.last() {
1758        Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
1759            let where_clause: TokenStream =
1760                remaining[..remaining.len() - 1].iter().cloned().collect();
1761            Ok((where_clause, g.clone()))
1762        }
1763        Some(TokenTree::Punct(p)) if *p == ';' => match remaining.first() {
1764            Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
1765                Err(compile_error_at(
1766                    "StructPath supports structs with named fields only, not tuple structs",
1767                    g.span(),
1768                ))
1769            }
1770            _ => Err(compile_error_at(
1771                "StructPath supports structs with named fields only, not unit structs",
1772                p.span(),
1773            )),
1774        },
1775        _ => Err(compile_error_at(
1776            "StructPath: expected a struct body with named fields",
1777            Span::call_site(),
1778        )),
1779    }
1780}
1781
1782/// Parses a named-field struct body into its field names and plain-`pub`
1783/// flags, in declaration order. A field's type is skipped rather than
1784/// parsed: the derive never needs to reference a field's value, only its
1785/// name, so the type tokens (however they nest, e.g. `HashMap<String,
1786/// u64>`) are dropped once the name before the first top-level `:` is
1787/// found.
1788fn parse_derived_fields(fields_stream: TokenStream) -> Result<Vec<DerivedField>, TokenStream> {
1789    let tokens: Vec<TokenTree> = fields_stream.into_iter().collect();
1790    let mut fields = Vec::new();
1791    for segment in split_top_level_commas(&tokens) {
1792        let mut pos = 0;
1793        skip_attributes(&segment, &mut pos);
1794        let is_plain_pub = parse_field_visibility(&segment, &mut pos);
1795        let name_ident = match segment.get(pos) {
1796            Some(TokenTree::Ident(id)) => id,
1797            _ => {
1798                return Err(compile_error_at(
1799                    "StructPath: expected a field name",
1800                    segment
1801                        .first()
1802                        .map(|t| t.span())
1803                        .unwrap_or_else(Span::call_site),
1804                ))
1805            }
1806        };
1807        let name = name_ident.to_string();
1808        let name = name.strip_prefix("r#").unwrap_or(&name).to_string();
1809        fields.push(DerivedField { name, is_plain_pub });
1810    }
1811    Ok(fields)
1812}
1813
1814/// Builds a `[&'static str; N]` initializer literal from field-name
1815/// strings, which are always valid identifiers (or their camelCase/
1816/// PascalCase conversions), so no escaping beyond wrapping each one in
1817/// quotes is needed.
1818fn array_literal_tokens(values: &[String]) -> TokenStream {
1819    let joined = values
1820        .iter()
1821        .map(|v| format!("\"{}\"", v))
1822        .collect::<Vec<_>>()
1823        .join(",");
1824    format!("[{}]", joined)
1825        .parse()
1826        .expect("field names always produce a valid array literal")
1827}