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