1use convert_case::{Case, Casing};
103use proc_macro::{Delimiter, Group, Spacing, Span, TokenStream, TokenTree};
104use std::collections::HashMap;
105
106fn 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
115fn 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
130type 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(¤t_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(¤t_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
321fn 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
339fn 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
387fn 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
405const ALL_FIELDS_OPTION_KEYS: &[&str] = &["visibility", "case"];
409
410const CANNOT_COMBINE_STAR_MESSAGE: &str = "`Type::*` cannot be combined with another struct or field group: its length is unknown to the macro";
414
415const EXPECTED_TYPE_PATH_MESSAGE: &str =
420 "expected a type path such as `Type::*` or `crate::module::Type::*`";
421
422const NOTHING_AFTER_NESTED_STAR_MESSAGE: &str = "nothing may follow a nested `Type::*` inside the parentheses; put options after the closing `)`";
425
426const 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
431const NESTED_STAR_MISSING_FIELD_MESSAGE: &str = "expected a field name before `.(` / `~(`";
437
438const NESTED_STAR_INNER_EXPECTED_MESSAGE: &str = "inside the parens, expected `Type::*`";
442
443fn 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 if contains_top_level_comma(prefix_region) {
485 return Err(CANNOT_COMBINE_NESTED_STAR_MESSAGE.to_string());
486 }
487 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 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 [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 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 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 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 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
634fn 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
698fn 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
722fn is_self_type_path(tokens: &[TokenTree]) -> bool {
728 matches!(tokens.first(), Some(TokenTree::Ident(id)) if id.to_string() == "Self")
729}
730
731fn parse_bare_star_type(tokens: &[TokenTree]) -> Result<(Vec<TokenTree>, Span), String> {
737 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
763fn 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 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 [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
832fn 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
859fn contains_top_level_comma(tokens: &[TokenTree]) -> bool {
866 split_top_level_commas(tokens).len() > 1
867}
868
869fn 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
899enum OptionParseState {
907 Key,
908 Equals,
909 Value,
910 CommaOrEnd,
911}
912
913fn 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
974fn 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 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(¤t_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 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
1151fn 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
1174fn 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
1191fn 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
1224fn 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
1246fn 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
1258fn 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#[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
1302struct 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 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 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
1510fn 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
1522fn 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
1548fn 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
1558fn 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
1602fn 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 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
1650fn 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
1709fn 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
1745fn 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
1777fn 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}