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 TokenTree::Punct(punct) if options_opened && punct == ';' => {
276 expect_option_value = false;
277 }
278 others => {
279 return Err(format!(
280 "Unexpected input for struct path parameters: {:?}",
281 others
282 ));
283 }
284 }
285 }
286
287 if !current_field_tokens.is_empty() {
288 current_struct_fields.push(std::mem::take(&mut current_field_tokens));
289 }
290 if current_struct_name_tokens.is_empty() {
291 return Err("Unexpected comma with empty definitions!".to_string());
292 }
293 if current_struct_fields.is_empty() {
294 return Err(format!(
295 "Unexpected comma with empty fields for {}!",
296 render_text(¤t_struct_name_tokens)
297 ));
298 }
299 found_structs.push((current_struct_name_tokens, current_struct_fields));
300
301 let all_check_functions = generate_checks_code_for(&found_structs);
302
303 let mut all_final_fields: Vec<String> = Vec::with_capacity(16);
304
305 for (_, fields) in &found_structs {
306 for field_tokens in fields {
307 let mut final_field_path = render_text(field_tokens).replace('~', ".");
308 if !options.is_empty() {
309 final_field_path = apply_options(&options, final_field_path)?;
310 }
311 all_final_fields.push(format!("\"{}\"", final_field_path))
312 }
313 }
314
315 let fields_str = format!("[{}]", all_final_fields.join(","));
316 let fields_tokens: TokenStream = fields_str
317 .parse()
318 .map_err(|_| format!("Generated code failed to parse: {}", fields_str))?;
319 let mut block_contents = all_check_functions;
320 block_contents.extend(fields_tokens);
321 Ok(TokenStream::from(TokenTree::Group(Group::new(
322 Delimiter::Brace,
323 block_contents,
324 ))))
325}
326
327fn fold_field_into_struct_name(
334 field_tokens: Vec<TokenTree>,
335 struct_name_tokens: &mut Vec<TokenTree>,
336) -> bool {
337 let is_single_ident = matches!(field_tokens.as_slice(), [TokenTree::Ident(_)]);
338 if is_single_ident {
339 struct_name_tokens.extend("::".parse::<TokenStream>().expect("`::` always parses"));
340 struct_name_tokens.extend(field_tokens);
341 }
342 is_single_ident
343}
344
345fn render_text(tokens: &[TokenTree]) -> String {
348 tokens.iter().map(|token| token.to_string()).collect()
349}
350
351fn parse_multiple_fields(
352 group_stream: TokenStream,
353 found_struct_fields: &mut Vec<Vec<TokenTree>>,
354) -> Result<(), String> {
355 let mut current_field_tokens: Vec<TokenTree> = Vec::new();
356
357 for token_tree in group_stream.into_iter() {
358 match token_tree {
359 TokenTree::Ident(id) => current_field_tokens.push(TokenTree::Ident(id)),
360 TokenTree::Punct(punct) if punct == ',' => {
361 if current_field_tokens.is_empty() {
362 return Err(format!(
363 "Unexpected punctuation input for struct path group parameters: {:?}",
364 punct
365 ));
366 }
367 found_struct_fields.push(std::mem::take(&mut current_field_tokens));
368 }
369 TokenTree::Punct(punct) if punct == '.' => {
370 if current_field_tokens.is_empty() {
371 return Err(format!(
372 "Unexpected punctuation input for struct path group parameters: {:?}",
373 punct
374 ));
375 }
376 current_field_tokens.push(TokenTree::Punct(punct));
377 }
378 others => {
379 return Err(format!(
380 "Unexpected input for struct path group parameters: {:?}",
381 others
382 ));
383 }
384 }
385 }
386
387 if !current_field_tokens.is_empty() {
388 found_struct_fields.push(current_field_tokens);
389 }
390 Ok(())
391}
392
393fn try_parse_all_fields(struct_path_stream: &TokenStream) -> Option<Result<TokenStream, String>> {
401 let tokens: Vec<TokenTree> = struct_path_stream.clone().into_iter().collect();
402 if !tokens
403 .iter()
404 .any(|t| matches!(t, TokenTree::Punct(p) if *p == '*'))
405 {
406 return None;
407 }
408 Some(parse_all_fields_at(&tokens))
409}
410
411const ALL_FIELDS_OPTION_KEYS: &[&str] = &["visibility", "case"];
415
416const CANNOT_COMBINE_STAR_MESSAGE: &str = "`Type::*` cannot be combined with another struct or field group: its length is unknown to the macro";
420
421const EXPECTED_TYPE_PATH_MESSAGE: &str =
426 "expected a type path such as `Type::*` or `crate::module::Type::*`";
427
428const NOTHING_AFTER_NESTED_STAR_MESSAGE: &str = "nothing may follow a nested `Type::*` inside the parentheses; put options after the closing `)`";
431
432const CANNOT_COMBINE_NESTED_STAR_MESSAGE: &str = "a nested `Type::*` cannot be combined with another struct or field group: its length is unknown to the macro";
436
437const NESTED_STAR_MISSING_FIELD_MESSAGE: &str = "expected a field name before `.(` / `~(`";
443
444const NESTED_STAR_INNER_EXPECTED_MESSAGE: &str = "inside the parens, expected `Type::*`";
448
449fn try_parse_nested_star(tokens: &[TokenTree]) -> Option<Result<TokenStream, String>> {
462 let paren_idx = tokens.iter().enumerate().find_map(|(i, t)| {
463 let group = match t {
464 TokenTree::Group(g) if g.delimiter() == Delimiter::Parenthesis => g,
465 _ => return None,
466 };
467 let follows_connector = i
468 .checked_sub(1)
469 .is_some_and(|j| matches!(&tokens[j], TokenTree::Punct(p) if *p == '.' || *p == '~'));
470 let contains_star = group
471 .stream()
472 .into_iter()
473 .any(|t| matches!(t, TokenTree::Punct(p) if p == '*'));
474 (follows_connector || contains_star).then_some(i)
475 })?;
476 Some(parse_nested_star_at(tokens, paren_idx))
477}
478
479fn parse_nested_star_at(tokens: &[TokenTree], paren_idx: usize) -> Result<TokenStream, String> {
480 let prefix_region = &tokens[..paren_idx];
481 if contains_top_level_comma(prefix_region) {
491 return Err(CANNOT_COMBINE_NESTED_STAR_MESSAGE.to_string());
492 }
493 if prefix_region
496 .iter()
497 .any(|t| matches!(t, TokenTree::Group(g) if g.delimiter() == Delimiter::Brace))
498 {
499 return Err(
500 "a `{ ... }` field group cannot appear before a nested `Type::*`; use a plain field path such as `Parent::child.(Child::*)`".to_string(),
501 );
502 }
503
504 let connector = match paren_idx.checked_sub(1).map(|i| &tokens[i]) {
505 Some(TokenTree::Punct(p)) if *p == '.' || *p == '~' => p.clone(),
506 _ => return Err(NESTED_STAR_MISSING_FIELD_MESSAGE.to_string()),
507 };
508 let head = &prefix_region[..prefix_region.len() - 1];
509 let (parent_tokens, field_tokens) = parse_type_and_field_path(head)?;
510 if field_tokens.is_empty() {
511 return Err(
512 "expected a field before the nested `Type::*`, e.g. `Parent::child.(Child::*)`"
513 .to_string(),
514 );
515 }
516 if !matches!(field_tokens.last(), Some(TokenTree::Ident(_))) {
522 return Err(
523 "expected a field name immediately before the nested `Type::*`'s connector, e.g. `Parent::child.(Child::*)`".to_string(),
524 );
525 }
526
527 let paren_group = match &tokens[paren_idx] {
528 TokenTree::Group(g) => g,
529 _ => unreachable!(),
530 };
531 let inner_tokens: Vec<TokenTree> = paren_group.stream().into_iter().collect();
532 if inner_tokens
533 .iter()
534 .any(|t| matches!(t, TokenTree::Group(_)))
535 {
536 return Err("nesting a parenthesized group inside `Type::*` is not supported".to_string());
537 }
538 let (inner_type_tokens, star_span) = parse_bare_star_type(&inner_tokens)?;
539
540 let rest = &tokens[paren_idx + 1..];
541 let options = match rest {
542 [] => Vec::new(),
543 [TokenTree::Punct(p), remainder @ ..] if *p == ';' => parse_all_fields_options(remainder)?,
544 [TokenTree::Punct(p)] if *p == ',' => return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string()),
549 _ => return Err(CANNOT_COMBINE_NESTED_STAR_MESSAGE.to_string()),
550 };
551 const NESTED_STAR_OPTION_KEYS: &[&str] = &["visibility", "case", "delim"];
556 if let Some((unknown, _)) = options
557 .iter()
558 .find(|(key, _)| !NESTED_STAR_OPTION_KEYS.contains(&key.as_str()))
559 {
560 return Err(format!("Unknown option is specified: {}", unknown));
561 }
562 let (visibility_suffix, case_suffix) = names_const_suffixes(&options)?;
563 let options_map: HashMap<String, String> = options.into_iter().collect();
564
565 let raw_prefix = render_text(&field_tokens).replace('~', ".");
570 let prefix_string = apply_options(&options_map, raw_prefix)?;
571 let delim_string = options_map
572 .get("delim")
573 .cloned()
574 .unwrap_or_else(|| ".".to_string());
575
576 let inner_type_stream: TokenStream = inner_type_tokens.iter().cloned().collect();
577 let parent_stream: TokenStream = parent_tokens.iter().cloned().collect();
578
579 let check_template = if connector == '~' {
591 "let _: &INNER = SELF.FIELD.iter().next().unwrap();"
592 } else {
593 "let _: &INNER = &SELF.FIELD;"
594 };
595 let self_param_tokens: TokenStream =
596 TokenStream::from(TokenTree::Ident(Ident::new("t", star_span)));
597 let check = fill(
598 check_template,
599 Span::call_site(),
600 &[
601 ("SELF", &self_param_tokens),
602 ("INNER", &inner_type_stream),
603 ("FIELD", &field_check_tokens(&field_tokens)),
604 ],
605 );
606
607 let names_path = format!(
608 "INNER::__STRUCT_PATH_{}_FIELDS{}",
609 visibility_suffix, case_suffix
610 );
611 let names_tokens = fill(&names_path, star_span, &[("INNER", &inner_type_stream)]);
612 let prefix_tokens = quoted_string_tokens(&prefix_string, star_span)?;
613 let delim_tokens = quoted_string_tokens(&delim_string, star_span)?;
614
615 Ok(fill(
625 "{
626 const _: fn(&PARENT) = |t: &PARENT| { CHECK };
627 const __STRUCT_PATH_NAMES: [&str; NAMES.len()] = NAMES;
628 const __STRUCT_PATH_LEN: usize =
629 INNER::__struct_path_joined_len(PREFIX, DELIM, &__STRUCT_PATH_NAMES);
630 const __STRUCT_PATH_BYTES: [u8; __STRUCT_PATH_LEN] =
631 INNER::__struct_path_joined_bytes::<__STRUCT_PATH_LEN>(PREFIX, DELIM, &__STRUCT_PATH_NAMES);
632 const __STRUCT_PATH_OUT: [&str; __STRUCT_PATH_NAMES.len()] =
633 INNER::__struct_path_joined(&__STRUCT_PATH_BYTES, PREFIX, DELIM, &__STRUCT_PATH_NAMES);
634 __STRUCT_PATH_OUT
635 }",
636 star_span,
637 &[
638 ("PARENT", &parent_stream),
639 ("CHECK", &check),
640 ("INNER", &inner_type_stream),
641 ("PREFIX", &prefix_tokens),
642 ("DELIM", &delim_tokens),
643 ("NAMES", &names_tokens),
644 ],
645 ))
646}
647
648fn parse_type_and_field_path(
657 tokens: &[TokenTree],
658) -> Result<(Vec<TokenTree>, Vec<TokenTree>), String> {
659 let mut struct_name_tokens: Vec<TokenTree> = Vec::new();
660 let mut field_tokens: Vec<TokenTree> = Vec::new();
661 let mut opened_struct = false;
662 let mut colons_counter = 0;
663
664 for token_tree in tokens {
665 match token_tree.clone() {
666 TokenTree::Ident(id) if struct_name_tokens.is_empty() => {
667 struct_name_tokens.push(TokenTree::Ident(id));
668 }
669 TokenTree::Punct(punct)
670 if !struct_name_tokens.is_empty()
671 && !opened_struct
672 && punct == ':'
673 && colons_counter < 2 =>
674 {
675 colons_counter += 1;
676 if colons_counter > 1 {
677 opened_struct = true;
678 }
679 }
680 TokenTree::Ident(id) if opened_struct => {
681 colons_counter = 0;
682 field_tokens.push(TokenTree::Ident(id));
683 }
684 TokenTree::Punct(punct)
685 if !struct_name_tokens.is_empty()
686 && opened_struct
687 && punct == ':'
688 && colons_counter < 2 =>
689 {
690 colons_counter += 1;
691 opened_struct = false;
692 let taken = std::mem::take(&mut field_tokens);
693 if !fold_field_into_struct_name(taken, &mut struct_name_tokens) {
694 return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string());
695 }
696 }
697 TokenTree::Punct(punct) if opened_struct && (punct == '.' || punct == '~') => {
698 if field_tokens.is_empty() {
699 return Err(NESTED_STAR_MISSING_FIELD_MESSAGE.to_string());
700 }
701 field_tokens.push(TokenTree::Punct(punct));
702 }
703 _ => return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string()),
704 }
705 }
706 if struct_name_tokens.is_empty() || !opened_struct {
707 return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string());
708 }
709 Ok((struct_name_tokens, field_tokens))
710}
711
712fn split_star_type(tokens: &[TokenTree]) -> Result<(&[TokenTree], Span, &[TokenTree]), String> {
719 let star_pos = tokens
720 .iter()
721 .position(|t| matches!(t, TokenTree::Punct(p) if *p == '*'))
722 .ok_or_else(|| EXPECTED_TYPE_PATH_MESSAGE.to_string())?;
723 if star_pos < 2
724 || !matches!(&tokens[star_pos - 1], TokenTree::Punct(p) if *p == ':')
725 || !matches!(&tokens[star_pos - 2], TokenTree::Punct(p) if *p == ':')
726 {
727 return Err("`*` must directly follow `Type::`".to_string());
728 }
729 let star_span = match &tokens[star_pos] {
730 TokenTree::Punct(p) => p.span(),
731 _ => unreachable!(),
732 };
733 Ok((&tokens[..star_pos - 2], star_span, &tokens[star_pos + 1..]))
734}
735
736fn is_self_type_path(tokens: &[TokenTree]) -> bool {
742 matches!(tokens.first(), Some(TokenTree::Ident(id)) if id.to_string() == "Self")
743}
744
745fn parse_bare_star_type(tokens: &[TokenTree]) -> Result<(Vec<TokenTree>, Span), String> {
751 if !tokens
757 .iter()
758 .any(|t| matches!(t, TokenTree::Punct(p) if *p == '*'))
759 {
760 return Err(NESTED_STAR_INNER_EXPECTED_MESSAGE.to_string());
761 }
762 let (type_tokens, star_span, rest) = split_star_type(tokens)?;
763 if type_tokens.is_empty() || !is_bare_type_path(type_tokens) {
764 return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string());
765 }
766 if is_self_type_path(type_tokens) {
767 return Err(
768 "`Self` cannot be named inside a nested `Type::*`; name the type directly, e.g. `Node::next~(Node::*)`".to_string(),
769 );
770 }
771 if !rest.is_empty() {
772 return Err(NOTHING_AFTER_NESTED_STAR_MESSAGE.to_string());
773 }
774 Ok((type_tokens.to_vec(), star_span))
775}
776
777fn quoted_string_tokens(value: &str, span: Span) -> Result<TokenStream, String> {
786 let text = format!("\"{}\"", value);
787 let tokens: TokenStream = text
788 .parse()
789 .map_err(|_| format!("Generated code failed to parse: {}", text))?;
790 Ok(tokens
791 .into_iter()
792 .map(|mut token| {
793 token.set_span(span);
794 token
795 })
796 .collect())
797}
798
799fn parse_all_fields_at(tokens: &[TokenTree]) -> Result<TokenStream, String> {
800 let (type_tokens, star_span, rest) = split_star_type(tokens)?;
801 if type_tokens.is_empty() {
802 return Err("Unexpected `*` with an empty type!".to_string());
803 }
804 if !is_bare_type_path(type_tokens) {
805 return Err(if contains_top_level_comma(type_tokens) {
812 CANNOT_COMBINE_STAR_MESSAGE.to_string()
813 } else {
814 EXPECTED_TYPE_PATH_MESSAGE.to_string()
815 });
816 }
817
818 let options = match rest {
819 [] => Vec::new(),
820 [TokenTree::Punct(p), remainder @ ..] if *p == ';' => parse_all_fields_options(remainder)?,
821 [TokenTree::Punct(p), more @ ..] if *p == ',' && !more.is_empty() => {
825 return Err(CANNOT_COMBINE_STAR_MESSAGE.to_string())
826 }
827 _ => return Err(EXPECTED_TYPE_PATH_MESSAGE.to_string()),
828 };
829 if let Some((unknown, _)) = options
830 .iter()
831 .find(|(key, _)| !ALL_FIELDS_OPTION_KEYS.contains(&key.as_str()))
832 {
833 return Err(format!("Unknown option is specified: {}", unknown));
834 }
835
836 let (visibility_suffix, case_suffix) = names_const_suffixes(&options)?;
837
838 let const_path = format!(
839 "TYPE::__STRUCT_PATH_{}_FIELDS{}",
840 visibility_suffix, case_suffix
841 );
842 let type_stream: TokenStream = type_tokens.iter().cloned().collect();
843 Ok(fill(&const_path, star_span, &[("TYPE", &type_stream)]))
844}
845
846fn names_const_suffixes(
851 options: &[(String, String)],
852) -> Result<(&'static str, &'static str), String> {
853 let option_value = |name: &str| {
854 options
855 .iter()
856 .find(|(key, _)| key == name)
857 .map(|(_, value)| value.as_str())
858 };
859 let visibility_suffix = match option_value("visibility") {
860 None | Some("pub") => "PUB",
861 Some("all") => "ALL",
862 Some(other) => return Err(format!("Unknown visibility is specified: {}", other)),
863 };
864 let case_suffix = match option_value("case") {
865 None => "",
866 Some("camel") => "_CAMEL",
867 Some("pascal") => "_PASCAL",
868 Some(other) => return Err(format!("Unknown case is specified: {}", other)),
869 };
870 Ok((visibility_suffix, case_suffix))
871}
872
873fn contains_top_level_comma(tokens: &[TokenTree]) -> bool {
880 split_top_level_commas(tokens).len() > 1
881}
882
883fn is_bare_type_path(tokens: &[TokenTree]) -> bool {
890 let mut i = 0;
891 if matches!(tokens.first(), Some(TokenTree::Punct(p)) if *p == ':')
892 && matches!(tokens.get(1), Some(TokenTree::Punct(p)) if *p == ':')
893 {
894 i = 2;
895 }
896 loop {
897 if !matches!(tokens.get(i), Some(TokenTree::Ident(_))) {
898 return false;
899 }
900 i += 1;
901 if i == tokens.len() {
902 return true;
903 }
904 let is_double_colon = matches!(tokens.get(i), Some(TokenTree::Punct(p)) if *p == ':')
905 && matches!(tokens.get(i + 1), Some(TokenTree::Punct(p)) if *p == ':');
906 if !is_double_colon {
907 return false;
908 }
909 i += 2;
910 }
911}
912
913enum OptionParseState {
929 GroupStart,
931 Key,
934 Equals,
935 Value,
936 CommaOrEnd,
937}
938
939fn parse_all_fields_options(tokens: &[TokenTree]) -> Result<Vec<(String, String)>, String> {
950 let mut options: Vec<(String, String)> = Vec::new();
951 let mut pending_key: Option<String> = None;
952 let mut state = OptionParseState::GroupStart;
953
954 for token_tree in tokens {
955 state = match (state, token_tree) {
956 (OptionParseState::GroupStart | OptionParseState::Key, TokenTree::Ident(id)) => {
957 pending_key = Some(id.to_string());
958 OptionParseState::Equals
959 }
960 (OptionParseState::Equals, TokenTree::Punct(p)) if *p == '=' => OptionParseState::Value,
961 (OptionParseState::Value, TokenTree::Literal(lit)) => {
962 let key = pending_key.take().expect("set on entering Equals/Value");
963 let value = unquote_literal(lit)?;
964 match options.iter().find(|(existing, _)| existing == &key) {
965 Some((_, existing_value)) if *existing_value == value => {}
966 Some(_) => return Err(format!("Duplicate option is specified: {}", key)),
967 None => options.push((key, value)),
968 }
969 OptionParseState::CommaOrEnd
970 }
971 (OptionParseState::CommaOrEnd, TokenTree::Punct(p)) if *p == ',' => {
972 OptionParseState::Key
973 }
974 (
979 OptionParseState::CommaOrEnd | OptionParseState::GroupStart | OptionParseState::Key,
980 TokenTree::Punct(p),
981 ) if *p == ';' => OptionParseState::GroupStart,
982 (state, other) => {
983 let expected = match state {
984 OptionParseState::GroupStart | OptionParseState::Key => "an option name or `;`",
985 OptionParseState::Equals => "`=`",
986 OptionParseState::Value => "a quoted string (\"...\") or char ('.') literal",
987 OptionParseState::CommaOrEnd => "`,` or `;`",
988 };
989 return Err(format!(
990 "Expected {} in `Type::*` options, found `{}`",
991 expected, other
992 ));
993 }
994 };
995 }
996
997 match state {
998 OptionParseState::GroupStart | OptionParseState::Key | OptionParseState::CommaOrEnd => {
999 Ok(options)
1000 }
1001 OptionParseState::Equals | OptionParseState::Value => Err(format!(
1002 "Missing a value for option `{}`",
1003 pending_key.expect("set on entering Equals/Value")
1004 )),
1005 }
1006}
1007
1008#[proc_macro]
1009pub fn path(struct_path_stream: TokenStream) -> TokenStream {
1010 match path_impl(struct_path_stream) {
1011 Ok(stream) => stream,
1012 Err(message) => compile_error_for(message),
1013 }
1014}
1015
1016fn contains_star(stream: TokenStream) -> bool {
1022 stream.into_iter().any(|token| match token {
1023 TokenTree::Punct(p) if p == '*' => true,
1024 TokenTree::Group(g) => contains_star(g.stream()),
1025 _ => false,
1026 })
1027}
1028
1029fn path_impl(struct_path_stream: TokenStream) -> Result<TokenStream, String> {
1030 if contains_star(struct_path_stream.clone()) {
1035 return Err(
1036 "`*` for all declared fields is only supported by paths!, not path!".to_string(),
1037 );
1038 }
1039
1040 let mut current_struct_name_tokens: Vec<TokenTree> = Vec::new();
1041
1042 let mut opened_struct = false;
1043 let mut colons_counter = 0;
1044 let mut options_opened = false;
1045
1046 let mut current_field_tokens: Vec<TokenTree> = Vec::new();
1047
1048 let mut current_option_name: Option<String> = None;
1049 let mut expect_option_value: bool = false;
1050
1051 let mut options: HashMap<String, String> = HashMap::new();
1052 let mut found_structs: FoundStructs = Vec::new();
1053
1054 for token_tree in struct_path_stream.into_iter() {
1055 match token_tree {
1056 TokenTree::Ident(id) if current_struct_name_tokens.is_empty() => {
1057 current_struct_name_tokens.push(TokenTree::Ident(id));
1058 }
1059 TokenTree::Punct(punct)
1060 if !current_struct_name_tokens.is_empty()
1061 && !opened_struct
1062 && punct == ':'
1063 && colons_counter < 2 =>
1064 {
1065 colons_counter += 1;
1066 if colons_counter > 1 {
1067 opened_struct = true;
1068 }
1069 }
1070 TokenTree::Ident(id) if opened_struct => {
1071 colons_counter = 0;
1072 current_field_tokens.push(TokenTree::Ident(id));
1073 }
1074 TokenTree::Punct(punct)
1075 if !current_struct_name_tokens.is_empty()
1076 && opened_struct
1077 && punct == ':'
1078 && colons_counter < 2 =>
1079 {
1080 colons_counter += 1;
1081 opened_struct = false;
1082 let field_tokens = std::mem::take(&mut current_field_tokens);
1083 if !fold_field_into_struct_name(field_tokens, &mut current_struct_name_tokens) {
1084 return Err(format!(
1085 "Unexpected punctuation input for struct path group parameters: {:?}",
1086 punct
1087 ));
1088 }
1089 }
1090 TokenTree::Punct(punct) if opened_struct && (punct == '.' || punct == '~') => {
1091 if current_field_tokens.is_empty() {
1092 return Err(format!(
1093 "Unexpected punctuation input for struct path group parameters: {:?}",
1094 punct
1095 ));
1096 }
1097 current_field_tokens.push(TokenTree::Punct(punct));
1098 }
1099 TokenTree::Punct(punct) if !options_opened && opened_struct && punct == ',' => {
1100 opened_struct = false;
1101 colons_counter = 0;
1102 if current_struct_name_tokens.is_empty() {
1103 return Err("Unexpected comma with empty definitions!".to_string());
1104 }
1105 if current_field_tokens.is_empty() {
1106 return Err(format!(
1107 "Unexpected comma with empty fields for {}!",
1108 render_text(¤t_struct_name_tokens)
1109 ));
1110 }
1111 found_structs.push((
1112 std::mem::take(&mut current_struct_name_tokens),
1113 vec![std::mem::take(&mut current_field_tokens)],
1114 ));
1115 }
1116 TokenTree::Punct(punct) if punct == ';' && opened_struct && !options_opened => {
1117 options_opened = true;
1118 opened_struct = false;
1119 }
1120 TokenTree::Ident(id) if options_opened && !expect_option_value => {
1121 current_option_name = Some(id.to_string())
1122 }
1123 TokenTree::Ident(id) if options_opened && expect_option_value => {
1124 expect_option_value = false;
1125 match current_option_name.take() {
1126 Some(option_name) => {
1127 options.insert(option_name, id.to_string());
1128 }
1129 _ => {
1130 return Err("Wrong options format".to_string());
1131 }
1132 }
1133 }
1134 TokenTree::Literal(lit) if options_opened && expect_option_value => {
1135 expect_option_value = false;
1136 match current_option_name.take() {
1137 Some(option_name) => {
1138 options.insert(option_name, unquote_literal(&lit)?);
1139 }
1140 _ => {
1141 return Err("Wrong options format".to_string());
1142 }
1143 }
1144 }
1145 TokenTree::Punct(punct) if options_opened && punct == '=' => {
1146 expect_option_value = true;
1147 }
1148 TokenTree::Punct(punct) if options_opened && punct == ',' => {
1149 expect_option_value = false;
1150 }
1151 TokenTree::Punct(punct) if options_opened && punct == ';' => {
1155 expect_option_value = false;
1156 }
1157 others => {
1158 return Err(format!(
1159 "Unexpected input for struct path parameters: {:?}",
1160 others
1161 ));
1162 }
1163 }
1164 }
1165
1166 if !current_struct_name_tokens.is_empty() && !current_field_tokens.is_empty() {
1167 found_structs.push((current_struct_name_tokens, vec![current_field_tokens]));
1168 }
1169
1170 if found_structs.is_empty() {
1171 return Err("Unexpected empty path definition!".to_string());
1172 }
1173
1174 let all_check_functions = generate_checks_code_for(&found_structs);
1175 let full_field_path = found_structs
1180 .iter()
1181 .flat_map(|(_, fields)| fields.iter())
1182 .map(|tokens| render_text(tokens))
1183 .collect::<Vec<_>>()
1184 .join(".")
1185 .replace('~', ".");
1186 let final_field_path = apply_options(&options, full_field_path)?;
1187 let fields_str = format!("\"{}\"", final_field_path);
1188 let fields_tokens: TokenStream = fields_str
1189 .parse()
1190 .map_err(|_| format!("Generated code failed to parse: {}", fields_str))?;
1191 let mut block_contents = all_check_functions;
1192 block_contents.extend(fields_tokens);
1193 Ok(TokenStream::from(TokenTree::Group(Group::new(
1194 Delimiter::Brace,
1195 block_contents,
1196 ))))
1197}
1198
1199fn generate_checks_code_for(found_structs: &FoundStructs) -> TokenStream {
1205 let mut all_check_functions = TokenStream::new();
1206 for (struct_name_tokens, struct_fields) in found_structs {
1207 let struct_name: TokenStream = struct_name_tokens.iter().cloned().collect();
1208 for field_tokens in struct_fields {
1209 all_check_functions.extend(fill(
1210 "const _: fn(&STRUCT) = |t: &STRUCT| { let _ = &t.FIELD; };",
1211 Span::call_site(),
1212 &[
1213 ("STRUCT", &struct_name),
1214 ("FIELD", &field_check_tokens(field_tokens)),
1215 ],
1216 ));
1217 }
1218 }
1219 all_check_functions
1220}
1221
1222fn field_check_tokens(field_tokens: &[TokenTree]) -> TokenStream {
1228 field_tokens
1229 .iter()
1230 .map(|token| match token {
1231 TokenTree::Punct(punct) if *punct == '~' => {
1232 fill(".iter().next().unwrap().", punct.span(), &[])
1233 }
1234 token => token.clone().into(),
1235 })
1236 .collect()
1237}
1238
1239fn fill(template: &str, span: Span, substitutions: &[(&str, &TokenStream)]) -> TokenStream {
1243 fn walk(stream: TokenStream, span: Span, subs: &[(&str, &TokenStream)]) -> TokenStream {
1244 stream
1245 .into_iter()
1246 .map(|mut token| {
1247 match &token {
1248 TokenTree::Ident(ident) => {
1249 let name = ident.to_string();
1250 if let Some((_, tokens)) = subs.iter().find(|(n, _)| *n == name) {
1251 return (*tokens).clone();
1252 }
1253 }
1254 TokenTree::Group(group) => {
1255 let stream = walk(group.stream(), span, subs);
1256 token = TokenTree::Group(Group::new(group.delimiter(), stream));
1257 }
1258 _ => {}
1259 }
1260 token.set_span(span);
1261 token.into()
1262 })
1263 .collect()
1264 }
1265 walk(
1266 template.parse().expect("templates are valid tokens"),
1267 span,
1268 substitutions,
1269 )
1270}
1271
1272fn unquote_literal(lit: &proc_macro::Literal) -> Result<String, String> {
1279 let text = lit.to_string();
1280 let bytes = text.as_bytes();
1281 let is_quoted = bytes.len() >= 2
1282 && ((bytes[0] == b'"' && bytes[bytes.len() - 1] == b'"')
1283 || (bytes[0] == b'\'' && bytes[bytes.len() - 1] == b'\''));
1284 if is_quoted {
1285 Ok(text[1..text.len() - 1].to_string())
1286 } else {
1287 Err(format!(
1288 "Unsupported option value {}: expected a string (\"...\") or char ('.') literal",
1289 text
1290 ))
1291 }
1292}
1293
1294fn convert_case_by_name(field_name: &str, case_name: &str) -> Result<String, String> {
1299 match case_name {
1300 "camel" => Ok(field_name.from_case(Case::Snake).to_case(Case::Camel)),
1301 "pascal" => Ok(field_name.from_case(Case::Snake).to_case(Case::Pascal)),
1302 another => Err(format!("Unknown case is specified: {}", another)),
1303 }
1304}
1305
1306fn convert_all(names: &[String], case_name: &str) -> Vec<String> {
1310 names
1311 .iter()
1312 .map(|n| {
1313 convert_case_by_name(n, case_name)
1314 .unwrap_or_else(|_| panic!("{:?} is always a known case", case_name))
1315 })
1316 .collect()
1317}
1318
1319fn apply_options(options: &HashMap<String, String>, field_path: String) -> Result<String, String> {
1320 let delim = options
1321 .get("delim")
1322 .as_ref()
1323 .map(|s| s.as_str())
1324 .unwrap_or(".");
1325 let case = options.get("case");
1326 let segments = field_path
1327 .split('.')
1328 .map(|field_name| match case {
1329 Some(case_value) => convert_case_by_name(field_name, case_value),
1330 None => Ok(field_name.to_string()),
1331 })
1332 .collect::<Result<Vec<String>, String>>()?;
1333 Ok(segments.join(delim))
1334}
1335
1336#[proc_macro_derive(StructPath)]
1343pub fn derive_struct_path(input: TokenStream) -> TokenStream {
1344 match derive_struct_path_impl(input) {
1345 Ok(stream) => stream,
1346 Err(error_stream) => error_stream,
1347 }
1348}
1349
1350struct DerivedField {
1356 name: String,
1357 is_plain_pub: bool,
1358}
1359
1360fn derive_struct_path_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
1361 let tokens: Vec<TokenTree> = input.into_iter().collect();
1362 let mut pos = 0;
1363
1364 skip_attributes(&tokens, &mut pos);
1365
1366 let visibility_start = pos;
1367 let (vis_len, item_is_plain_pub) = parse_visibility_prefix(&tokens[pos..]);
1368 pos += vis_len;
1369 let visibility_tokens: TokenStream = tokens[visibility_start..pos].iter().cloned().collect();
1370
1371 let (keyword, keyword_span) = match tokens.get(pos) {
1372 Some(TokenTree::Ident(id)) => (id.to_string(), id.span()),
1373 _ => {
1374 return Err(compile_error_at(
1375 "StructPath: unable to parse the derived item",
1376 Span::call_site(),
1377 ))
1378 }
1379 };
1380 if keyword != "struct" {
1381 return Err(compile_error_at(
1382 &format!(
1383 "StructPath supports structs with named fields only, not {}s",
1384 keyword
1385 ),
1386 keyword_span,
1387 ));
1388 }
1389 pos += 1;
1390
1391 let name_ident = match tokens.get(pos) {
1392 Some(TokenTree::Ident(id)) => id.clone(),
1393 _ => {
1394 return Err(compile_error_at(
1395 "StructPath: expected a struct name",
1396 keyword_span,
1397 ))
1398 }
1399 };
1400 let name_span = name_ident.span();
1401 pos += 1;
1402
1403 let (generics_impl, generics_type) = parse_lifetime_generics(&tokens, &mut pos)?;
1404 let (where_clause, fields_group) = find_fields_group(&tokens[pos..])?;
1405 let fields = parse_derived_fields(fields_group.stream())?;
1406
1407 let pub_names: Vec<String> = fields
1408 .iter()
1409 .filter(|f| f.is_plain_pub)
1410 .map(|f| f.name.clone())
1411 .collect();
1412 let all_names: Vec<String> = fields.iter().map(|f| f.name.clone()).collect();
1413
1414 let name_tokens = TokenStream::from(TokenTree::Ident(name_ident));
1415 let pub_array = array_literal_tokens(&pub_names);
1416 let pub_camel_array = array_literal_tokens(&convert_all(&pub_names, "camel"));
1417 let pub_pascal_array = array_literal_tokens(&convert_all(&pub_names, "pascal"));
1418 let all_array = array_literal_tokens(&all_names);
1419 let all_camel_array = array_literal_tokens(&convert_all(&all_names, "camel"));
1420 let all_pascal_array = array_literal_tokens(&convert_all(&all_names, "pascal"));
1421 let n_pub: TokenStream = pub_names
1422 .len()
1423 .to_string()
1424 .parse()
1425 .expect("a field count always parses as a literal");
1426 let n_all: TokenStream = all_names
1427 .len()
1428 .to_string()
1429 .parse()
1430 .expect("a field count always parses as a literal");
1431
1432 let all_fields_visibility: TokenStream = if item_is_plain_pub {
1443 "pub(crate)".parse().expect("`pub(crate)` always parses")
1444 } else {
1445 visibility_tokens.clone()
1446 };
1447
1448 let generated = fill(
1459 "impl<GENERICS_IMPL> NAME<GENERICS_TYPE> WHERE_CLAUSE {
1460 #[doc(hidden)]
1461 VIS const __STRUCT_PATH_PUB_FIELDS: [&'static str; N_PUB] = PUB_FIELDS;
1462 #[doc(hidden)]
1463 VIS const __STRUCT_PATH_PUB_FIELDS_CAMEL: [&'static str; N_PUB] = PUB_FIELDS_CAMEL;
1464 #[doc(hidden)]
1465 VIS const __STRUCT_PATH_PUB_FIELDS_PASCAL: [&'static str; N_PUB] = PUB_FIELDS_PASCAL;
1466 #[doc(hidden)]
1467 ALL_VIS const __STRUCT_PATH_ALL_FIELDS: [&'static str; N_ALL] = ALL_FIELDS;
1468 #[doc(hidden)]
1469 ALL_VIS const __STRUCT_PATH_ALL_FIELDS_CAMEL: [&'static str; N_ALL] = ALL_FIELDS_CAMEL;
1470 #[doc(hidden)]
1471 ALL_VIS const __STRUCT_PATH_ALL_FIELDS_PASCAL: [&'static str; N_ALL] = ALL_FIELDS_PASCAL;
1472
1473 #[doc(hidden)]
1474 #[must_use]
1475 VIS const fn __struct_path_joined_len(prefix: &str, delim: &str, names: &[&str]) -> usize {
1476 let mut total = 0;
1477 let mut i = 0;
1478 while i < names.len() {
1479 total += prefix.len() + delim.len() + names[i].len();
1480 i += 1;
1481 }
1482 total
1483 }
1484
1485 #[doc(hidden)]
1486 #[must_use]
1487 VIS const fn __struct_path_joined_bytes<const TOTAL: usize>(
1488 prefix: &str,
1489 delim: &str,
1490 names: &[&str],
1491 ) -> [u8; TOTAL] {
1492 let mut buf = [0u8; TOTAL];
1493 let mut pos = 0;
1494 let mut i = 0;
1495 while i < names.len() {
1496 let parts = [prefix.as_bytes(), delim.as_bytes(), names[i].as_bytes()];
1497 let mut p = 0;
1498 while p < parts.len() {
1499 let mut b = 0;
1500 while b < parts[p].len() {
1501 buf[pos] = parts[p][b];
1502 pos += 1;
1503 b += 1;
1504 }
1505 p += 1;
1506 }
1507 i += 1;
1508 }
1509 buf
1510 }
1511
1512 #[doc(hidden)]
1513 #[must_use]
1514 VIS const fn __struct_path_joined<const N: usize, const TOTAL: usize>(
1515 bytes: &'static [u8; TOTAL],
1516 prefix: &str,
1517 delim: &str,
1518 names: &[&str; N],
1519 ) -> [&'static str; N] {
1520 let mut out = [\"\"; N];
1521 let mut pos = 0;
1522 let mut i = 0;
1523 while i < N {
1524 let len = prefix.len() + delim.len() + names[i].len();
1525 let (_, rest) = bytes.split_at(pos);
1526 let (piece, _) = rest.split_at(len);
1527 out[i] = match core::str::from_utf8(piece) {
1528 Ok(s) => s,
1529 Err(_) => panic!(\"struct-path: joined path is not valid UTF-8\"),
1530 };
1531 pos += len;
1532 i += 1;
1533 }
1534 out
1535 }
1536 }",
1537 name_span,
1538 &[
1539 ("GENERICS_IMPL", &generics_impl),
1540 ("GENERICS_TYPE", &generics_type),
1541 ("NAME", &name_tokens),
1542 ("WHERE_CLAUSE", &where_clause),
1543 ("VIS", &visibility_tokens),
1544 ("ALL_VIS", &all_fields_visibility),
1545 ("N_PUB", &n_pub),
1546 ("N_ALL", &n_all),
1547 ("PUB_FIELDS", &pub_array),
1548 ("PUB_FIELDS_CAMEL", &pub_camel_array),
1549 ("PUB_FIELDS_PASCAL", &pub_pascal_array),
1550 ("ALL_FIELDS", &all_array),
1551 ("ALL_FIELDS_CAMEL", &all_camel_array),
1552 ("ALL_FIELDS_PASCAL", &all_pascal_array),
1553 ],
1554 );
1555 Ok(generated)
1556}
1557
1558fn skip_attributes(tokens: &[TokenTree], pos: &mut usize) {
1561 while matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '#') {
1562 *pos += 1;
1563 if matches!(tokens.get(*pos), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket)
1564 {
1565 *pos += 1;
1566 }
1567 }
1568}
1569
1570fn parse_visibility_prefix(tokens: &[TokenTree]) -> (usize, bool) {
1579 if let Some(TokenTree::Group(g)) = tokens.first() {
1580 if g.delimiter() == Delimiter::None {
1581 let inner: Vec<TokenTree> = g.stream().into_iter().collect();
1582 let (_, is_plain_pub) = parse_visibility_prefix(&inner);
1583 return (1, is_plain_pub);
1584 }
1585 }
1586 if matches!(tokens.first(), Some(TokenTree::Ident(id)) if id.to_string() == "pub") {
1587 if matches!(tokens.get(1), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis)
1588 {
1589 return (2, false);
1590 }
1591 return (1, true);
1592 }
1593 (0, false)
1594}
1595
1596fn parse_field_visibility(tokens: &[TokenTree], pos: &mut usize) -> bool {
1601 let (len, is_plain_pub) = parse_visibility_prefix(&tokens[*pos..]);
1602 *pos += len;
1603 is_plain_pub
1604}
1605
1606fn split_top_level_commas(tokens: &[TokenTree]) -> Vec<Vec<TokenTree>> {
1620 let mut segments = Vec::new();
1621 let mut current = Vec::new();
1622 let mut depth: i32 = 0;
1623 let mut prev_was_arrow_dash = false;
1624 for token in tokens {
1625 match token {
1626 TokenTree::Punct(p) if *p == '<' => {
1627 depth += 1;
1628 current.push(token.clone());
1629 }
1630 TokenTree::Punct(p) if *p == '>' => {
1631 if !prev_was_arrow_dash {
1632 depth = (depth - 1).max(0);
1633 }
1634 current.push(token.clone());
1635 }
1636 TokenTree::Punct(p) if *p == ',' && depth == 0 => {
1637 segments.push(std::mem::take(&mut current));
1638 }
1639 other => current.push(other.clone()),
1640 }
1641 prev_was_arrow_dash =
1642 matches!(token, TokenTree::Punct(p) if *p == '-' && p.spacing() == Spacing::Joint);
1643 }
1644 if !current.is_empty() {
1645 segments.push(current);
1646 }
1647 segments
1648}
1649
1650fn lifetime_param_name(segment: &[TokenTree]) -> Option<Vec<TokenTree>> {
1655 if segment.len() < 2 {
1656 return None;
1657 }
1658 if !matches!(&segment[0], TokenTree::Punct(p) if *p == '\'') {
1659 return None;
1660 }
1661 if !matches!(&segment[1], TokenTree::Ident(_)) {
1662 return None;
1663 }
1664 let bare = segment[0..2].to_vec();
1665 if segment.len() == 2 {
1666 return Some(bare);
1667 }
1668 if !matches!(&segment[2], TokenTree::Punct(p) if *p == ':') {
1669 return None;
1670 }
1671 if segment.len() == 3 {
1672 return Some(bare);
1675 }
1676 let mut i = 3;
1677 loop {
1678 if i + 1 >= segment.len() {
1679 return None;
1680 }
1681 let is_lifetime = matches!(&segment[i], TokenTree::Punct(p) if *p == '\'')
1682 && matches!(&segment[i + 1], TokenTree::Ident(_));
1683 if !is_lifetime {
1684 return None;
1685 }
1686 i += 2;
1687 if i == segment.len() {
1688 break;
1689 }
1690 if !matches!(&segment[i], TokenTree::Punct(p) if *p == '+') {
1691 return None;
1692 }
1693 i += 1;
1694 }
1695 Some(bare)
1696}
1697
1698fn parse_lifetime_generics(
1703 tokens: &[TokenTree],
1704 pos: &mut usize,
1705) -> Result<(TokenStream, TokenStream), TokenStream> {
1706 if !matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '<') {
1707 return Ok((TokenStream::new(), TokenStream::new()));
1708 }
1709 let open_span = match &tokens[*pos] {
1710 TokenTree::Punct(p) => p.span(),
1711 _ => unreachable!(),
1712 };
1713 *pos += 1;
1714 let start = *pos;
1715 let mut depth = 1;
1716 while depth > 0 {
1717 match tokens.get(*pos) {
1718 Some(TokenTree::Punct(p)) if *p == '<' => depth += 1,
1719 Some(TokenTree::Punct(p)) if *p == '>' => depth -= 1,
1720 Some(_) => {}
1721 None => {
1722 return Err(compile_error_at(
1723 "StructPath: unterminated generic parameter list",
1724 open_span,
1725 ))
1726 }
1727 }
1728 *pos += 1;
1729 }
1730 let generics_tokens = &tokens[start..*pos - 1];
1731
1732 let mut bare_lifetimes: Vec<TokenTree> = Vec::new();
1733 for (i, segment) in split_top_level_commas(generics_tokens)
1734 .into_iter()
1735 .enumerate()
1736 {
1737 if i > 0 {
1738 bare_lifetimes.extend(fill(",", open_span, &[]));
1739 }
1740 match lifetime_param_name(&segment) {
1741 Some(bare) => bare_lifetimes.extend(bare),
1742 None => {
1743 let span = segment.first().map(|t| t.span()).unwrap_or(open_span);
1744 return Err(compile_error_at(
1745 "StructPath only supports lifetime generic parameters",
1746 span,
1747 ));
1748 }
1749 }
1750 }
1751
1752 let generics_impl: TokenStream = generics_tokens.iter().cloned().collect();
1753 let generics_type: TokenStream = bare_lifetimes.into_iter().collect();
1754 Ok((generics_impl, generics_type))
1755}
1756
1757fn find_fields_group(remaining: &[TokenTree]) -> Result<(TokenStream, Group), TokenStream> {
1768 match remaining.last() {
1769 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
1770 let where_clause: TokenStream =
1771 remaining[..remaining.len() - 1].iter().cloned().collect();
1772 Ok((where_clause, g.clone()))
1773 }
1774 Some(TokenTree::Punct(p)) if *p == ';' => match remaining.first() {
1775 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
1776 Err(compile_error_at(
1777 "StructPath supports structs with named fields only, not tuple structs",
1778 g.span(),
1779 ))
1780 }
1781 _ => Err(compile_error_at(
1782 "StructPath supports structs with named fields only, not unit structs",
1783 p.span(),
1784 )),
1785 },
1786 _ => Err(compile_error_at(
1787 "StructPath: expected a struct body with named fields",
1788 Span::call_site(),
1789 )),
1790 }
1791}
1792
1793fn parse_derived_fields(fields_stream: TokenStream) -> Result<Vec<DerivedField>, TokenStream> {
1800 let tokens: Vec<TokenTree> = fields_stream.into_iter().collect();
1801 let mut fields = Vec::new();
1802 for segment in split_top_level_commas(&tokens) {
1803 let mut pos = 0;
1804 skip_attributes(&segment, &mut pos);
1805 let is_plain_pub = parse_field_visibility(&segment, &mut pos);
1806 let name_ident = match segment.get(pos) {
1807 Some(TokenTree::Ident(id)) => id,
1808 _ => {
1809 return Err(compile_error_at(
1810 "StructPath: expected a field name",
1811 segment
1812 .first()
1813 .map(|t| t.span())
1814 .unwrap_or_else(Span::call_site),
1815 ))
1816 }
1817 };
1818 let name = name_ident.to_string();
1819 let name = name.strip_prefix("r#").unwrap_or(&name).to_string();
1820 fields.push(DerivedField { name, is_plain_pub });
1821 }
1822 Ok(fields)
1823}
1824
1825fn array_literal_tokens(values: &[String]) -> TokenStream {
1830 let joined = values
1831 .iter()
1832 .map(|v| format!("\"{}\"", v))
1833 .collect::<Vec<_>>()
1834 .join(",");
1835 format!("[{}]", joined)
1836 .parse()
1837 .expect("field names always produce a valid array literal")
1838}