1use convert_case::{Case, Casing};
103use proc_macro::{Delimiter, Group, Ident, 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 == '~' {
585 "let _: &INNER = SELF.FIELD.iter().next().unwrap();"
586 } else {
587 "let _: &INNER = &SELF.FIELD;"
588 };
589 let self_param_tokens: TokenStream =
590 TokenStream::from(TokenTree::Ident(Ident::new("t", star_span)));
591 let check = fill(
592 check_template,
593 Span::call_site(),
594 &[
595 ("SELF", &self_param_tokens),
596 ("INNER", &inner_type_stream),
597 ("FIELD", &field_check_tokens(&field_tokens)),
598 ],
599 );
600
601 let names_path = format!(
602 "INNER::__STRUCT_PATH_{}_FIELDS{}",
603 visibility_suffix, case_suffix
604 );
605 let names_tokens = fill(&names_path, star_span, &[("INNER", &inner_type_stream)]);
606 let prefix_tokens = quoted_string_tokens(&prefix_string, star_span)?;
607 let delim_tokens = quoted_string_tokens(&delim_string, star_span)?;
608
609 Ok(fill(
619 "{
620 const _: fn(&PARENT) = |t: &PARENT| { CHECK };
621 const __STRUCT_PATH_NAMES: [&str; NAMES.len()] = NAMES;
622 const __STRUCT_PATH_LEN: usize =
623 INNER::__struct_path_joined_len(PREFIX, DELIM, &__STRUCT_PATH_NAMES);
624 const __STRUCT_PATH_BYTES: [u8; __STRUCT_PATH_LEN] =
625 INNER::__struct_path_joined_bytes::<__STRUCT_PATH_LEN>(PREFIX, DELIM, &__STRUCT_PATH_NAMES);
626 const __STRUCT_PATH_OUT: [&str; __STRUCT_PATH_NAMES.len()] =
627 INNER::__struct_path_joined(&__STRUCT_PATH_BYTES, PREFIX, DELIM, &__STRUCT_PATH_NAMES);
628 __STRUCT_PATH_OUT
629 }",
630 star_span,
631 &[
632 ("PARENT", &parent_stream),
633 ("CHECK", &check),
634 ("INNER", &inner_type_stream),
635 ("PREFIX", &prefix_tokens),
636 ("DELIM", &delim_tokens),
637 ("NAMES", &names_tokens),
638 ],
639 ))
640}
641
642fn parse_type_and_field_path(
651 tokens: &[TokenTree],
652) -> Result<(Vec<TokenTree>, Vec<TokenTree>), String> {
653 let mut struct_name_tokens: Vec<TokenTree> = Vec::new();
654 let mut field_tokens: Vec<TokenTree> = Vec::new();
655 let mut opened_struct = false;
656 let mut colons_counter = 0;
657
658 for token_tree in tokens {
659 match token_tree.clone() {
660 TokenTree::Ident(id) if struct_name_tokens.is_empty() => {
661 struct_name_tokens.push(TokenTree::Ident(id));
662 }
663 TokenTree::Punct(punct)
664 if !struct_name_tokens.is_empty()
665 && !opened_struct
666 && punct == ':'
667 && colons_counter < 2 =>
668 {
669 colons_counter += 1;
670 if colons_counter > 1 {
671 opened_struct = true;
672 }
673 }
674 TokenTree::Ident(id) if opened_struct => {
675 colons_counter = 0;
676 field_tokens.push(TokenTree::Ident(id));
677 }
678 TokenTree::Punct(punct)
679 if !struct_name_tokens.is_empty()
680 && opened_struct
681 && punct == ':'
682 && colons_counter < 2 =>
683 {
684 colons_counter += 1;
685 opened_struct = false;
686 let taken = std::mem::take(&mut field_tokens);
687 if !fold_field_into_struct_name(taken, &mut struct_name_tokens) {
688 return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string());
689 }
690 }
691 TokenTree::Punct(punct) if opened_struct && (punct == '.' || punct == '~') => {
692 if field_tokens.is_empty() {
693 return Err(NESTED_STAR_MISSING_FIELD_MESSAGE.to_string());
694 }
695 field_tokens.push(TokenTree::Punct(punct));
696 }
697 _ => return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string()),
698 }
699 }
700 if struct_name_tokens.is_empty() || !opened_struct {
701 return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string());
702 }
703 Ok((struct_name_tokens, field_tokens))
704}
705
706fn split_star_type(tokens: &[TokenTree]) -> Result<(&[TokenTree], Span, &[TokenTree]), String> {
713 let star_pos = tokens
714 .iter()
715 .position(|t| matches!(t, TokenTree::Punct(p) if *p == '*'))
716 .ok_or_else(|| EXPECTED_TYPE_PATH_MESSAGE.to_string())?;
717 if star_pos < 2
718 || !matches!(&tokens[star_pos - 1], TokenTree::Punct(p) if *p == ':')
719 || !matches!(&tokens[star_pos - 2], TokenTree::Punct(p) if *p == ':')
720 {
721 return Err("`*` must directly follow `Type::`".to_string());
722 }
723 let star_span = match &tokens[star_pos] {
724 TokenTree::Punct(p) => p.span(),
725 _ => unreachable!(),
726 };
727 Ok((&tokens[..star_pos - 2], star_span, &tokens[star_pos + 1..]))
728}
729
730fn is_self_type_path(tokens: &[TokenTree]) -> bool {
736 matches!(tokens.first(), Some(TokenTree::Ident(id)) if id.to_string() == "Self")
737}
738
739fn parse_bare_star_type(tokens: &[TokenTree]) -> Result<(Vec<TokenTree>, Span), String> {
745 if !tokens
751 .iter()
752 .any(|t| matches!(t, TokenTree::Punct(p) if *p == '*'))
753 {
754 return Err(NESTED_STAR_INNER_EXPECTED_MESSAGE.to_string());
755 }
756 let (type_tokens, star_span, rest) = split_star_type(tokens)?;
757 if type_tokens.is_empty() || !is_bare_type_path(type_tokens) {
758 return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string());
759 }
760 if is_self_type_path(type_tokens) {
761 return Err(
762 "`Self` cannot be named inside a nested `Type::*`; name the type directly, e.g. `Node::next~(Node::*)`".to_string(),
763 );
764 }
765 if !rest.is_empty() {
766 return Err(NOTHING_AFTER_NESTED_STAR_MESSAGE.to_string());
767 }
768 Ok((type_tokens.to_vec(), star_span))
769}
770
771fn quoted_string_tokens(value: &str, span: Span) -> Result<TokenStream, String> {
780 let text = format!("\"{}\"", value);
781 let tokens: TokenStream = text
782 .parse()
783 .map_err(|_| format!("Generated code failed to parse: {}", text))?;
784 Ok(tokens
785 .into_iter()
786 .map(|mut token| {
787 token.set_span(span);
788 token
789 })
790 .collect())
791}
792
793fn parse_all_fields_at(tokens: &[TokenTree]) -> Result<TokenStream, String> {
794 let (type_tokens, star_span, rest) = split_star_type(tokens)?;
795 if type_tokens.is_empty() {
796 return Err("Unexpected `*` with an empty type!".to_string());
797 }
798 if !is_bare_type_path(type_tokens) {
799 return Err(if contains_top_level_comma(type_tokens) {
806 CANNOT_COMBINE_STAR_MESSAGE.to_string()
807 } else {
808 EXPECTED_TYPE_PATH_MESSAGE.to_string()
809 });
810 }
811
812 let options = match rest {
813 [] => Vec::new(),
814 [TokenTree::Punct(p), remainder @ ..] if *p == ';' => parse_all_fields_options(remainder)?,
815 [TokenTree::Punct(p), more @ ..] if *p == ',' && !more.is_empty() => {
819 return Err(CANNOT_COMBINE_STAR_MESSAGE.to_string())
820 }
821 _ => return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string()),
822 };
823 if let Some((unknown, _)) = options
824 .iter()
825 .find(|(key, _)| !ALL_FIELDS_OPTION_KEYS.contains(&key.as_str()))
826 {
827 return Err(format!("Unknown option is specified: {}", unknown));
828 }
829
830 let (visibility_suffix, case_suffix) = names_const_suffixes(&options)?;
831
832 let const_path = format!(
833 "TYPE::__STRUCT_PATH_{}_FIELDS{}",
834 visibility_suffix, case_suffix
835 );
836 let type_stream: TokenStream = type_tokens.iter().cloned().collect();
837 Ok(fill(&const_path, star_span, &[("TYPE", &type_stream)]))
838}
839
840fn names_const_suffixes(
845 options: &[(String, String)],
846) -> Result<(&'static str, &'static str), String> {
847 let option_value = |name: &str| {
848 options
849 .iter()
850 .find(|(key, _)| key == name)
851 .map(|(_, value)| value.as_str())
852 };
853 let visibility_suffix = match option_value("visibility") {
854 None | Some("pub") => "PUB",
855 Some("all") => "ALL",
856 Some(other) => return Err(format!("Unknown visibility is specified: {}", other)),
857 };
858 let case_suffix = match option_value("case") {
859 None => "",
860 Some("camel") => "_CAMEL",
861 Some("pascal") => "_PASCAL",
862 Some(other) => return Err(format!("Unknown case is specified: {}", other)),
863 };
864 Ok((visibility_suffix, case_suffix))
865}
866
867fn contains_top_level_comma(tokens: &[TokenTree]) -> bool {
874 split_top_level_commas(tokens).len() > 1
875}
876
877fn is_bare_type_path(tokens: &[TokenTree]) -> bool {
884 let mut i = 0;
885 if matches!(tokens.first(), Some(TokenTree::Punct(p)) if *p == ':')
886 && matches!(tokens.get(1), Some(TokenTree::Punct(p)) if *p == ':')
887 {
888 i = 2;
889 }
890 loop {
891 if !matches!(tokens.get(i), Some(TokenTree::Ident(_))) {
892 return false;
893 }
894 i += 1;
895 if i == tokens.len() {
896 return true;
897 }
898 let is_double_colon = matches!(tokens.get(i), Some(TokenTree::Punct(p)) if *p == ':')
899 && matches!(tokens.get(i + 1), Some(TokenTree::Punct(p)) if *p == ':');
900 if !is_double_colon {
901 return false;
902 }
903 i += 2;
904 }
905}
906
907enum OptionParseState {
915 Key,
916 Equals,
917 Value,
918 CommaOrEnd,
919}
920
921fn parse_all_fields_options(tokens: &[TokenTree]) -> Result<Vec<(String, String)>, String> {
928 let mut options: Vec<(String, String)> = Vec::new();
929 let mut pending_key: Option<String> = None;
930 let mut state = OptionParseState::Key;
931
932 for token_tree in tokens {
933 state = match (state, token_tree) {
934 (OptionParseState::Key, TokenTree::Ident(id)) => {
935 pending_key = Some(id.to_string());
936 OptionParseState::Equals
937 }
938 (OptionParseState::Equals, TokenTree::Punct(p)) if *p == '=' => OptionParseState::Value,
939 (OptionParseState::Value, TokenTree::Literal(lit)) => {
940 let key = pending_key.take().expect("set on entering Equals/Value");
941 if options.iter().any(|(existing, _)| existing == &key) {
942 return Err(format!("Duplicate option is specified: {}", key));
943 }
944 options.push((key, unquote_literal(lit)?));
945 OptionParseState::CommaOrEnd
946 }
947 (OptionParseState::CommaOrEnd, TokenTree::Punct(p)) if *p == ',' => {
948 OptionParseState::Key
949 }
950 (state, other) => {
951 let expected = match state {
952 OptionParseState::Key => "an option name",
953 OptionParseState::Equals => "`=`",
954 OptionParseState::Value => "a quoted string (\"...\") or char ('.') literal",
955 OptionParseState::CommaOrEnd => "`,`",
956 };
957 return Err(format!(
958 "Expected {} in `Type::*` options, found `{}`",
959 expected, other
960 ));
961 }
962 };
963 }
964
965 match state {
966 OptionParseState::Key | OptionParseState::CommaOrEnd => Ok(options),
967 OptionParseState::Equals | OptionParseState::Value => Err(format!(
968 "Missing a value for option `{}`",
969 pending_key.expect("set on entering Equals/Value")
970 )),
971 }
972}
973
974#[proc_macro]
975pub fn path(struct_path_stream: TokenStream) -> TokenStream {
976 match path_impl(struct_path_stream) {
977 Ok(stream) => stream,
978 Err(message) => compile_error_for(message),
979 }
980}
981
982fn contains_star(stream: TokenStream) -> bool {
988 stream.into_iter().any(|token| match token {
989 TokenTree::Punct(p) if p == '*' => true,
990 TokenTree::Group(g) => contains_star(g.stream()),
991 _ => false,
992 })
993}
994
995fn path_impl(struct_path_stream: TokenStream) -> Result<TokenStream, String> {
996 if contains_star(struct_path_stream.clone()) {
1001 return Err(
1002 "`*` for all declared fields is only supported by paths!, not path!".to_string(),
1003 );
1004 }
1005
1006 let mut current_struct_name_tokens: Vec<TokenTree> = Vec::new();
1007
1008 let mut opened_struct = false;
1009 let mut colons_counter = 0;
1010 let mut options_opened = false;
1011
1012 let mut current_field_tokens: Vec<TokenTree> = Vec::new();
1013
1014 let mut current_option_name: Option<String> = None;
1015 let mut expect_option_value: bool = false;
1016
1017 let mut options: HashMap<String, String> = HashMap::new();
1018 let mut found_structs: FoundStructs = Vec::new();
1019
1020 for token_tree in struct_path_stream.into_iter() {
1021 match token_tree {
1022 TokenTree::Ident(id) if current_struct_name_tokens.is_empty() => {
1023 current_struct_name_tokens.push(TokenTree::Ident(id));
1024 }
1025 TokenTree::Punct(punct)
1026 if !current_struct_name_tokens.is_empty()
1027 && !opened_struct
1028 && punct == ':'
1029 && colons_counter < 2 =>
1030 {
1031 colons_counter += 1;
1032 if colons_counter > 1 {
1033 opened_struct = true;
1034 }
1035 }
1036 TokenTree::Ident(id) if opened_struct => {
1037 colons_counter = 0;
1038 current_field_tokens.push(TokenTree::Ident(id));
1039 }
1040 TokenTree::Punct(punct)
1041 if !current_struct_name_tokens.is_empty()
1042 && opened_struct
1043 && punct == ':'
1044 && colons_counter < 2 =>
1045 {
1046 colons_counter += 1;
1047 opened_struct = false;
1048 let field_tokens = std::mem::take(&mut current_field_tokens);
1049 if !fold_field_into_struct_name(field_tokens, &mut current_struct_name_tokens) {
1050 return Err(format!(
1051 "Unexpected punctuation input for struct path group parameters: {:?}",
1052 punct
1053 ));
1054 }
1055 }
1056 TokenTree::Punct(punct) if opened_struct && (punct == '.' || punct == '~') => {
1057 if current_field_tokens.is_empty() {
1058 return Err(format!(
1059 "Unexpected punctuation input for struct path group parameters: {:?}",
1060 punct
1061 ));
1062 }
1063 current_field_tokens.push(TokenTree::Punct(punct));
1064 }
1065 TokenTree::Punct(punct) if !options_opened && opened_struct && punct == ',' => {
1066 opened_struct = false;
1067 colons_counter = 0;
1068 if current_struct_name_tokens.is_empty() {
1069 return Err("Unexpected comma with empty definitions!".to_string());
1070 }
1071 if current_field_tokens.is_empty() {
1072 return Err(format!(
1073 "Unexpected comma with empty fields for {}!",
1074 render_text(¤t_struct_name_tokens)
1075 ));
1076 }
1077 found_structs.push((
1078 std::mem::take(&mut current_struct_name_tokens),
1079 vec![std::mem::take(&mut current_field_tokens)],
1080 ));
1081 }
1082 TokenTree::Punct(punct) if punct == ';' && opened_struct && !options_opened => {
1083 options_opened = true;
1084 opened_struct = false;
1085 }
1086 TokenTree::Ident(id) if options_opened && !expect_option_value => {
1087 current_option_name = Some(id.to_string())
1088 }
1089 TokenTree::Ident(id) if options_opened && expect_option_value => {
1090 expect_option_value = false;
1091 match current_option_name.take() {
1092 Some(option_name) => {
1093 options.insert(option_name, id.to_string());
1094 }
1095 _ => {
1096 return Err("Wrong options format".to_string());
1097 }
1098 }
1099 }
1100 TokenTree::Literal(lit) if options_opened && expect_option_value => {
1101 expect_option_value = false;
1102 match current_option_name.take() {
1103 Some(option_name) => {
1104 options.insert(option_name, unquote_literal(&lit)?);
1105 }
1106 _ => {
1107 return Err("Wrong options format".to_string());
1108 }
1109 }
1110 }
1111 TokenTree::Punct(punct) if options_opened && punct == '=' => {
1112 expect_option_value = true;
1113 }
1114 TokenTree::Punct(punct) if options_opened && punct == ',' => {
1115 expect_option_value = false;
1116 }
1117 others => {
1118 return Err(format!(
1119 "Unexpected input for struct path parameters: {:?}",
1120 others
1121 ));
1122 }
1123 }
1124 }
1125
1126 if !current_struct_name_tokens.is_empty() && !current_field_tokens.is_empty() {
1127 found_structs.push((current_struct_name_tokens, vec![current_field_tokens]));
1128 }
1129
1130 if found_structs.is_empty() {
1131 return Err("Unexpected empty path definition!".to_string());
1132 }
1133
1134 let all_check_functions = generate_checks_code_for(&found_structs);
1135 let full_field_path = found_structs
1140 .iter()
1141 .flat_map(|(_, fields)| fields.iter())
1142 .map(|tokens| render_text(tokens))
1143 .collect::<Vec<_>>()
1144 .join(".")
1145 .replace('~', ".");
1146 let final_field_path = apply_options(&options, full_field_path)?;
1147 let fields_str = format!("\"{}\"", final_field_path);
1148 let fields_tokens: TokenStream = fields_str
1149 .parse()
1150 .map_err(|_| format!("Generated code failed to parse: {}", fields_str))?;
1151 let mut block_contents = all_check_functions;
1152 block_contents.extend(fields_tokens);
1153 Ok(TokenStream::from(TokenTree::Group(Group::new(
1154 Delimiter::Brace,
1155 block_contents,
1156 ))))
1157}
1158
1159fn generate_checks_code_for(found_structs: &FoundStructs) -> TokenStream {
1165 let mut all_check_functions = TokenStream::new();
1166 for (struct_name_tokens, struct_fields) in found_structs {
1167 let struct_name: TokenStream = struct_name_tokens.iter().cloned().collect();
1168 for field_tokens in struct_fields {
1169 all_check_functions.extend(fill(
1170 "const _: fn(&STRUCT) = |t: &STRUCT| { let _ = &t.FIELD; };",
1171 Span::call_site(),
1172 &[
1173 ("STRUCT", &struct_name),
1174 ("FIELD", &field_check_tokens(field_tokens)),
1175 ],
1176 ));
1177 }
1178 }
1179 all_check_functions
1180}
1181
1182fn field_check_tokens(field_tokens: &[TokenTree]) -> TokenStream {
1188 field_tokens
1189 .iter()
1190 .map(|token| match token {
1191 TokenTree::Punct(punct) if *punct == '~' => {
1192 fill(".iter().next().unwrap().", punct.span(), &[])
1193 }
1194 token => token.clone().into(),
1195 })
1196 .collect()
1197}
1198
1199fn fill(template: &str, span: Span, substitutions: &[(&str, &TokenStream)]) -> TokenStream {
1203 fn walk(stream: TokenStream, span: Span, subs: &[(&str, &TokenStream)]) -> TokenStream {
1204 stream
1205 .into_iter()
1206 .map(|mut token| {
1207 match &token {
1208 TokenTree::Ident(ident) => {
1209 let name = ident.to_string();
1210 if let Some((_, tokens)) = subs.iter().find(|(n, _)| *n == name) {
1211 return (*tokens).clone();
1212 }
1213 }
1214 TokenTree::Group(group) => {
1215 let stream = walk(group.stream(), span, subs);
1216 token = TokenTree::Group(Group::new(group.delimiter(), stream));
1217 }
1218 _ => {}
1219 }
1220 token.set_span(span);
1221 token.into()
1222 })
1223 .collect()
1224 }
1225 walk(
1226 template.parse().expect("templates are valid tokens"),
1227 span,
1228 substitutions,
1229 )
1230}
1231
1232fn unquote_literal(lit: &proc_macro::Literal) -> Result<String, String> {
1239 let text = lit.to_string();
1240 let bytes = text.as_bytes();
1241 let is_quoted = bytes.len() >= 2
1242 && ((bytes[0] == b'"' && bytes[bytes.len() - 1] == b'"')
1243 || (bytes[0] == b'\'' && bytes[bytes.len() - 1] == b'\''));
1244 if is_quoted {
1245 Ok(text[1..text.len() - 1].to_string())
1246 } else {
1247 Err(format!(
1248 "Unsupported option value {}: expected a string (\"...\") or char ('.') literal",
1249 text
1250 ))
1251 }
1252}
1253
1254fn convert_case_by_name(field_name: &str, case_name: &str) -> Result<String, String> {
1259 match case_name {
1260 "camel" => Ok(field_name.from_case(Case::Snake).to_case(Case::Camel)),
1261 "pascal" => Ok(field_name.from_case(Case::Snake).to_case(Case::Pascal)),
1262 another => Err(format!("Unknown case is specified: {}", another)),
1263 }
1264}
1265
1266fn convert_all(names: &[String], case_name: &str) -> Vec<String> {
1270 names
1271 .iter()
1272 .map(|n| {
1273 convert_case_by_name(n, case_name)
1274 .unwrap_or_else(|_| panic!("{:?} is always a known case", case_name))
1275 })
1276 .collect()
1277}
1278
1279fn apply_options(options: &HashMap<String, String>, field_path: String) -> Result<String, String> {
1280 let delim = options
1281 .get("delim")
1282 .as_ref()
1283 .map(|s| s.as_str())
1284 .unwrap_or(".");
1285 let case = options.get("case");
1286 let segments = field_path
1287 .split('.')
1288 .map(|field_name| match case {
1289 Some(case_value) => convert_case_by_name(field_name, case_value),
1290 None => Ok(field_name.to_string()),
1291 })
1292 .collect::<Result<Vec<String>, String>>()?;
1293 Ok(segments.join(delim))
1294}
1295
1296#[proc_macro_derive(StructPath)]
1303pub fn derive_struct_path(input: TokenStream) -> TokenStream {
1304 match derive_struct_path_impl(input) {
1305 Ok(stream) => stream,
1306 Err(error_stream) => error_stream,
1307 }
1308}
1309
1310struct DerivedField {
1316 name: String,
1317 is_plain_pub: bool,
1318}
1319
1320fn derive_struct_path_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
1321 let tokens: Vec<TokenTree> = input.into_iter().collect();
1322 let mut pos = 0;
1323
1324 skip_attributes(&tokens, &mut pos);
1325
1326 let visibility_start = pos;
1327 let (vis_len, item_is_plain_pub) = parse_visibility_prefix(&tokens[pos..]);
1328 pos += vis_len;
1329 let visibility_tokens: TokenStream = tokens[visibility_start..pos].iter().cloned().collect();
1330
1331 let (keyword, keyword_span) = match tokens.get(pos) {
1332 Some(TokenTree::Ident(id)) => (id.to_string(), id.span()),
1333 _ => {
1334 return Err(compile_error_at(
1335 "StructPath: unable to parse the derived item",
1336 Span::call_site(),
1337 ))
1338 }
1339 };
1340 if keyword != "struct" {
1341 return Err(compile_error_at(
1342 &format!(
1343 "StructPath supports structs with named fields only, not {}s",
1344 keyword
1345 ),
1346 keyword_span,
1347 ));
1348 }
1349 pos += 1;
1350
1351 let name_ident = match tokens.get(pos) {
1352 Some(TokenTree::Ident(id)) => id.clone(),
1353 _ => {
1354 return Err(compile_error_at(
1355 "StructPath: expected a struct name",
1356 keyword_span,
1357 ))
1358 }
1359 };
1360 let name_span = name_ident.span();
1361 pos += 1;
1362
1363 let (generics_impl, generics_type) = parse_lifetime_generics(&tokens, &mut pos)?;
1364 let (where_clause, fields_group) = find_fields_group(&tokens[pos..])?;
1365 let fields = parse_derived_fields(fields_group.stream())?;
1366
1367 let pub_names: Vec<String> = fields
1368 .iter()
1369 .filter(|f| f.is_plain_pub)
1370 .map(|f| f.name.clone())
1371 .collect();
1372 let all_names: Vec<String> = fields.iter().map(|f| f.name.clone()).collect();
1373
1374 let name_tokens = TokenStream::from(TokenTree::Ident(name_ident));
1375 let pub_array = array_literal_tokens(&pub_names);
1376 let pub_camel_array = array_literal_tokens(&convert_all(&pub_names, "camel"));
1377 let pub_pascal_array = array_literal_tokens(&convert_all(&pub_names, "pascal"));
1378 let all_array = array_literal_tokens(&all_names);
1379 let all_camel_array = array_literal_tokens(&convert_all(&all_names, "camel"));
1380 let all_pascal_array = array_literal_tokens(&convert_all(&all_names, "pascal"));
1381 let n_pub: TokenStream = pub_names
1382 .len()
1383 .to_string()
1384 .parse()
1385 .expect("a field count always parses as a literal");
1386 let n_all: TokenStream = all_names
1387 .len()
1388 .to_string()
1389 .parse()
1390 .expect("a field count always parses as a literal");
1391
1392 let all_fields_visibility: TokenStream = if item_is_plain_pub {
1403 "pub(crate)".parse().expect("`pub(crate)` always parses")
1404 } else {
1405 visibility_tokens.clone()
1406 };
1407
1408 let generated = fill(
1419 "impl<GENERICS_IMPL> NAME<GENERICS_TYPE> WHERE_CLAUSE {
1420 #[doc(hidden)]
1421 VIS const __STRUCT_PATH_PUB_FIELDS: [&'static str; N_PUB] = PUB_FIELDS;
1422 #[doc(hidden)]
1423 VIS const __STRUCT_PATH_PUB_FIELDS_CAMEL: [&'static str; N_PUB] = PUB_FIELDS_CAMEL;
1424 #[doc(hidden)]
1425 VIS const __STRUCT_PATH_PUB_FIELDS_PASCAL: [&'static str; N_PUB] = PUB_FIELDS_PASCAL;
1426 #[doc(hidden)]
1427 ALL_VIS const __STRUCT_PATH_ALL_FIELDS: [&'static str; N_ALL] = ALL_FIELDS;
1428 #[doc(hidden)]
1429 ALL_VIS const __STRUCT_PATH_ALL_FIELDS_CAMEL: [&'static str; N_ALL] = ALL_FIELDS_CAMEL;
1430 #[doc(hidden)]
1431 ALL_VIS const __STRUCT_PATH_ALL_FIELDS_PASCAL: [&'static str; N_ALL] = ALL_FIELDS_PASCAL;
1432
1433 #[doc(hidden)]
1434 #[must_use]
1435 VIS const fn __struct_path_joined_len(prefix: &str, delim: &str, names: &[&str]) -> usize {
1436 let mut total = 0;
1437 let mut i = 0;
1438 while i < names.len() {
1439 total += prefix.len() + delim.len() + names[i].len();
1440 i += 1;
1441 }
1442 total
1443 }
1444
1445 #[doc(hidden)]
1446 #[must_use]
1447 VIS const fn __struct_path_joined_bytes<const TOTAL: usize>(
1448 prefix: &str,
1449 delim: &str,
1450 names: &[&str],
1451 ) -> [u8; TOTAL] {
1452 let mut buf = [0u8; TOTAL];
1453 let mut pos = 0;
1454 let mut i = 0;
1455 while i < names.len() {
1456 let parts = [prefix.as_bytes(), delim.as_bytes(), names[i].as_bytes()];
1457 let mut p = 0;
1458 while p < parts.len() {
1459 let mut b = 0;
1460 while b < parts[p].len() {
1461 buf[pos] = parts[p][b];
1462 pos += 1;
1463 b += 1;
1464 }
1465 p += 1;
1466 }
1467 i += 1;
1468 }
1469 buf
1470 }
1471
1472 #[doc(hidden)]
1473 #[must_use]
1474 VIS const fn __struct_path_joined<const N: usize, const TOTAL: usize>(
1475 bytes: &'static [u8; TOTAL],
1476 prefix: &str,
1477 delim: &str,
1478 names: &[&str; N],
1479 ) -> [&'static str; N] {
1480 let mut out = [\"\"; N];
1481 let mut pos = 0;
1482 let mut i = 0;
1483 while i < N {
1484 let len = prefix.len() + delim.len() + names[i].len();
1485 let (_, rest) = bytes.split_at(pos);
1486 let (piece, _) = rest.split_at(len);
1487 out[i] = match core::str::from_utf8(piece) {
1488 Ok(s) => s,
1489 Err(_) => panic!(\"struct-path: joined path is not valid UTF-8\"),
1490 };
1491 pos += len;
1492 i += 1;
1493 }
1494 out
1495 }
1496 }",
1497 name_span,
1498 &[
1499 ("GENERICS_IMPL", &generics_impl),
1500 ("GENERICS_TYPE", &generics_type),
1501 ("NAME", &name_tokens),
1502 ("WHERE_CLAUSE", &where_clause),
1503 ("VIS", &visibility_tokens),
1504 ("ALL_VIS", &all_fields_visibility),
1505 ("N_PUB", &n_pub),
1506 ("N_ALL", &n_all),
1507 ("PUB_FIELDS", &pub_array),
1508 ("PUB_FIELDS_CAMEL", &pub_camel_array),
1509 ("PUB_FIELDS_PASCAL", &pub_pascal_array),
1510 ("ALL_FIELDS", &all_array),
1511 ("ALL_FIELDS_CAMEL", &all_camel_array),
1512 ("ALL_FIELDS_PASCAL", &all_pascal_array),
1513 ],
1514 );
1515 Ok(generated)
1516}
1517
1518fn skip_attributes(tokens: &[TokenTree], pos: &mut usize) {
1521 while matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '#') {
1522 *pos += 1;
1523 if matches!(tokens.get(*pos), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket)
1524 {
1525 *pos += 1;
1526 }
1527 }
1528}
1529
1530fn parse_visibility_prefix(tokens: &[TokenTree]) -> (usize, bool) {
1539 if let Some(TokenTree::Group(g)) = tokens.first() {
1540 if g.delimiter() == Delimiter::None {
1541 let inner: Vec<TokenTree> = g.stream().into_iter().collect();
1542 let (_, is_plain_pub) = parse_visibility_prefix(&inner);
1543 return (1, is_plain_pub);
1544 }
1545 }
1546 if matches!(tokens.first(), Some(TokenTree::Ident(id)) if id.to_string() == "pub") {
1547 if matches!(tokens.get(1), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis)
1548 {
1549 return (2, false);
1550 }
1551 return (1, true);
1552 }
1553 (0, false)
1554}
1555
1556fn parse_field_visibility(tokens: &[TokenTree], pos: &mut usize) -> bool {
1561 let (len, is_plain_pub) = parse_visibility_prefix(&tokens[*pos..]);
1562 *pos += len;
1563 is_plain_pub
1564}
1565
1566fn split_top_level_commas(tokens: &[TokenTree]) -> Vec<Vec<TokenTree>> {
1580 let mut segments = Vec::new();
1581 let mut current = Vec::new();
1582 let mut depth: i32 = 0;
1583 let mut prev_was_arrow_dash = false;
1584 for token in tokens {
1585 match token {
1586 TokenTree::Punct(p) if *p == '<' => {
1587 depth += 1;
1588 current.push(token.clone());
1589 }
1590 TokenTree::Punct(p) if *p == '>' => {
1591 if !prev_was_arrow_dash {
1592 depth = (depth - 1).max(0);
1593 }
1594 current.push(token.clone());
1595 }
1596 TokenTree::Punct(p) if *p == ',' && depth == 0 => {
1597 segments.push(std::mem::take(&mut current));
1598 }
1599 other => current.push(other.clone()),
1600 }
1601 prev_was_arrow_dash =
1602 matches!(token, TokenTree::Punct(p) if *p == '-' && p.spacing() == Spacing::Joint);
1603 }
1604 if !current.is_empty() {
1605 segments.push(current);
1606 }
1607 segments
1608}
1609
1610fn lifetime_param_name(segment: &[TokenTree]) -> Option<Vec<TokenTree>> {
1615 if segment.len() < 2 {
1616 return None;
1617 }
1618 if !matches!(&segment[0], TokenTree::Punct(p) if *p == '\'') {
1619 return None;
1620 }
1621 if !matches!(&segment[1], TokenTree::Ident(_)) {
1622 return None;
1623 }
1624 let bare = segment[0..2].to_vec();
1625 if segment.len() == 2 {
1626 return Some(bare);
1627 }
1628 if !matches!(&segment[2], TokenTree::Punct(p) if *p == ':') {
1629 return None;
1630 }
1631 if segment.len() == 3 {
1632 return Some(bare);
1635 }
1636 let mut i = 3;
1637 loop {
1638 if i + 1 >= segment.len() {
1639 return None;
1640 }
1641 let is_lifetime = matches!(&segment[i], TokenTree::Punct(p) if *p == '\'')
1642 && matches!(&segment[i + 1], TokenTree::Ident(_));
1643 if !is_lifetime {
1644 return None;
1645 }
1646 i += 2;
1647 if i == segment.len() {
1648 break;
1649 }
1650 if !matches!(&segment[i], TokenTree::Punct(p) if *p == '+') {
1651 return None;
1652 }
1653 i += 1;
1654 }
1655 Some(bare)
1656}
1657
1658fn parse_lifetime_generics(
1663 tokens: &[TokenTree],
1664 pos: &mut usize,
1665) -> Result<(TokenStream, TokenStream), TokenStream> {
1666 if !matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '<') {
1667 return Ok((TokenStream::new(), TokenStream::new()));
1668 }
1669 let open_span = match &tokens[*pos] {
1670 TokenTree::Punct(p) => p.span(),
1671 _ => unreachable!(),
1672 };
1673 *pos += 1;
1674 let start = *pos;
1675 let mut depth = 1;
1676 while depth > 0 {
1677 match tokens.get(*pos) {
1678 Some(TokenTree::Punct(p)) if *p == '<' => depth += 1,
1679 Some(TokenTree::Punct(p)) if *p == '>' => depth -= 1,
1680 Some(_) => {}
1681 None => {
1682 return Err(compile_error_at(
1683 "StructPath: unterminated generic parameter list",
1684 open_span,
1685 ))
1686 }
1687 }
1688 *pos += 1;
1689 }
1690 let generics_tokens = &tokens[start..*pos - 1];
1691
1692 let mut bare_lifetimes: Vec<TokenTree> = Vec::new();
1693 for (i, segment) in split_top_level_commas(generics_tokens)
1694 .into_iter()
1695 .enumerate()
1696 {
1697 if i > 0 {
1698 bare_lifetimes.extend(fill(",", open_span, &[]));
1699 }
1700 match lifetime_param_name(&segment) {
1701 Some(bare) => bare_lifetimes.extend(bare),
1702 None => {
1703 let span = segment.first().map(|t| t.span()).unwrap_or(open_span);
1704 return Err(compile_error_at(
1705 "StructPath only supports lifetime generic parameters",
1706 span,
1707 ));
1708 }
1709 }
1710 }
1711
1712 let generics_impl: TokenStream = generics_tokens.iter().cloned().collect();
1713 let generics_type: TokenStream = bare_lifetimes.into_iter().collect();
1714 Ok((generics_impl, generics_type))
1715}
1716
1717fn find_fields_group(remaining: &[TokenTree]) -> Result<(TokenStream, Group), TokenStream> {
1728 match remaining.last() {
1729 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
1730 let where_clause: TokenStream =
1731 remaining[..remaining.len() - 1].iter().cloned().collect();
1732 Ok((where_clause, g.clone()))
1733 }
1734 Some(TokenTree::Punct(p)) if *p == ';' => match remaining.first() {
1735 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
1736 Err(compile_error_at(
1737 "StructPath supports structs with named fields only, not tuple structs",
1738 g.span(),
1739 ))
1740 }
1741 _ => Err(compile_error_at(
1742 "StructPath supports structs with named fields only, not unit structs",
1743 p.span(),
1744 )),
1745 },
1746 _ => Err(compile_error_at(
1747 "StructPath: expected a struct body with named fields",
1748 Span::call_site(),
1749 )),
1750 }
1751}
1752
1753fn parse_derived_fields(fields_stream: TokenStream) -> Result<Vec<DerivedField>, TokenStream> {
1760 let tokens: Vec<TokenTree> = fields_stream.into_iter().collect();
1761 let mut fields = Vec::new();
1762 for segment in split_top_level_commas(&tokens) {
1763 let mut pos = 0;
1764 skip_attributes(&segment, &mut pos);
1765 let is_plain_pub = parse_field_visibility(&segment, &mut pos);
1766 let name_ident = match segment.get(pos) {
1767 Some(TokenTree::Ident(id)) => id,
1768 _ => {
1769 return Err(compile_error_at(
1770 "StructPath: expected a field name",
1771 segment
1772 .first()
1773 .map(|t| t.span())
1774 .unwrap_or_else(Span::call_site),
1775 ))
1776 }
1777 };
1778 let name = name_ident.to_string();
1779 let name = name.strip_prefix("r#").unwrap_or(&name).to_string();
1780 fields.push(DerivedField { name, is_plain_pub });
1781 }
1782 Ok(fields)
1783}
1784
1785fn array_literal_tokens(values: &[String]) -> TokenStream {
1790 let joined = values
1791 .iter()
1792 .map(|v| format!("\"{}\"", v))
1793 .collect::<Vec<_>>()
1794 .join(",");
1795 format!("[{}]", joined)
1796 .parse()
1797 .expect("field names always produce a valid array literal")
1798}