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 {
923 GroupStart,
925 Key,
927 Equals,
928 Value,
929 CommaOrEnd,
930}
931
932fn parse_all_fields_options(tokens: &[TokenTree]) -> Result<Vec<(String, String)>, String> {
940 let mut options: Vec<(String, String)> = Vec::new();
941 let mut pending_key: Option<String> = None;
942 let mut state = OptionParseState::GroupStart;
943
944 for token_tree in tokens {
945 state = match (state, token_tree) {
946 (OptionParseState::GroupStart | OptionParseState::Key, TokenTree::Ident(id)) => {
947 pending_key = Some(id.to_string());
948 OptionParseState::Equals
949 }
950 (OptionParseState::Equals, TokenTree::Punct(p)) if *p == '=' => OptionParseState::Value,
951 (OptionParseState::Value, TokenTree::Literal(lit)) => {
952 let key = pending_key.take().expect("set on entering Equals/Value");
953 if options.iter().any(|(existing, _)| existing == &key) {
954 return Err(format!("Duplicate option is specified: {}", key));
955 }
956 options.push((key, unquote_literal(lit)?));
957 OptionParseState::CommaOrEnd
958 }
959 (OptionParseState::CommaOrEnd, TokenTree::Punct(p)) if *p == ',' => {
960 OptionParseState::Key
961 }
962 (OptionParseState::CommaOrEnd | OptionParseState::GroupStart, TokenTree::Punct(p))
967 if *p == ';' =>
968 {
969 OptionParseState::GroupStart
970 }
971 (state, other) => {
972 let expected = match state {
973 OptionParseState::GroupStart | OptionParseState::Key => "an option name",
974 OptionParseState::Equals => "`=`",
975 OptionParseState::Value => "a quoted string (\"...\") or char ('.') literal",
976 OptionParseState::CommaOrEnd => "`,` or `;`",
977 };
978 return Err(format!(
979 "Expected {} in `Type::*` options, found `{}`",
980 expected, other
981 ));
982 }
983 };
984 }
985
986 match state {
987 OptionParseState::GroupStart | OptionParseState::Key | OptionParseState::CommaOrEnd => {
988 Ok(options)
989 }
990 OptionParseState::Equals | OptionParseState::Value => Err(format!(
991 "Missing a value for option `{}`",
992 pending_key.expect("set on entering Equals/Value")
993 )),
994 }
995}
996
997#[proc_macro]
998pub fn path(struct_path_stream: TokenStream) -> TokenStream {
999 match path_impl(struct_path_stream) {
1000 Ok(stream) => stream,
1001 Err(message) => compile_error_for(message),
1002 }
1003}
1004
1005fn contains_star(stream: TokenStream) -> bool {
1011 stream.into_iter().any(|token| match token {
1012 TokenTree::Punct(p) if p == '*' => true,
1013 TokenTree::Group(g) => contains_star(g.stream()),
1014 _ => false,
1015 })
1016}
1017
1018fn path_impl(struct_path_stream: TokenStream) -> Result<TokenStream, String> {
1019 if contains_star(struct_path_stream.clone()) {
1024 return Err(
1025 "`*` for all declared fields is only supported by paths!, not path!".to_string(),
1026 );
1027 }
1028
1029 let mut current_struct_name_tokens: Vec<TokenTree> = Vec::new();
1030
1031 let mut opened_struct = false;
1032 let mut colons_counter = 0;
1033 let mut options_opened = false;
1034
1035 let mut current_field_tokens: Vec<TokenTree> = Vec::new();
1036
1037 let mut current_option_name: Option<String> = None;
1038 let mut expect_option_value: bool = false;
1039
1040 let mut options: HashMap<String, String> = HashMap::new();
1041 let mut found_structs: FoundStructs = Vec::new();
1042
1043 for token_tree in struct_path_stream.into_iter() {
1044 match token_tree {
1045 TokenTree::Ident(id) if current_struct_name_tokens.is_empty() => {
1046 current_struct_name_tokens.push(TokenTree::Ident(id));
1047 }
1048 TokenTree::Punct(punct)
1049 if !current_struct_name_tokens.is_empty()
1050 && !opened_struct
1051 && punct == ':'
1052 && colons_counter < 2 =>
1053 {
1054 colons_counter += 1;
1055 if colons_counter > 1 {
1056 opened_struct = true;
1057 }
1058 }
1059 TokenTree::Ident(id) if opened_struct => {
1060 colons_counter = 0;
1061 current_field_tokens.push(TokenTree::Ident(id));
1062 }
1063 TokenTree::Punct(punct)
1064 if !current_struct_name_tokens.is_empty()
1065 && opened_struct
1066 && punct == ':'
1067 && colons_counter < 2 =>
1068 {
1069 colons_counter += 1;
1070 opened_struct = false;
1071 let field_tokens = std::mem::take(&mut current_field_tokens);
1072 if !fold_field_into_struct_name(field_tokens, &mut current_struct_name_tokens) {
1073 return Err(format!(
1074 "Unexpected punctuation input for struct path group parameters: {:?}",
1075 punct
1076 ));
1077 }
1078 }
1079 TokenTree::Punct(punct) if opened_struct && (punct == '.' || punct == '~') => {
1080 if current_field_tokens.is_empty() {
1081 return Err(format!(
1082 "Unexpected punctuation input for struct path group parameters: {:?}",
1083 punct
1084 ));
1085 }
1086 current_field_tokens.push(TokenTree::Punct(punct));
1087 }
1088 TokenTree::Punct(punct) if !options_opened && opened_struct && punct == ',' => {
1089 opened_struct = false;
1090 colons_counter = 0;
1091 if current_struct_name_tokens.is_empty() {
1092 return Err("Unexpected comma with empty definitions!".to_string());
1093 }
1094 if current_field_tokens.is_empty() {
1095 return Err(format!(
1096 "Unexpected comma with empty fields for {}!",
1097 render_text(¤t_struct_name_tokens)
1098 ));
1099 }
1100 found_structs.push((
1101 std::mem::take(&mut current_struct_name_tokens),
1102 vec![std::mem::take(&mut current_field_tokens)],
1103 ));
1104 }
1105 TokenTree::Punct(punct) if punct == ';' && opened_struct && !options_opened => {
1106 options_opened = true;
1107 opened_struct = false;
1108 }
1109 TokenTree::Ident(id) if options_opened && !expect_option_value => {
1110 current_option_name = Some(id.to_string())
1111 }
1112 TokenTree::Ident(id) if options_opened && expect_option_value => {
1113 expect_option_value = false;
1114 match current_option_name.take() {
1115 Some(option_name) => {
1116 options.insert(option_name, id.to_string());
1117 }
1118 _ => {
1119 return Err("Wrong options format".to_string());
1120 }
1121 }
1122 }
1123 TokenTree::Literal(lit) if options_opened && expect_option_value => {
1124 expect_option_value = false;
1125 match current_option_name.take() {
1126 Some(option_name) => {
1127 options.insert(option_name, unquote_literal(&lit)?);
1128 }
1129 _ => {
1130 return Err("Wrong options format".to_string());
1131 }
1132 }
1133 }
1134 TokenTree::Punct(punct) if options_opened && punct == '=' => {
1135 expect_option_value = true;
1136 }
1137 TokenTree::Punct(punct) if options_opened && punct == ',' => {
1138 expect_option_value = false;
1139 }
1140 TokenTree::Punct(punct) if options_opened && punct == ';' => {
1144 expect_option_value = false;
1145 }
1146 others => {
1147 return Err(format!(
1148 "Unexpected input for struct path parameters: {:?}",
1149 others
1150 ));
1151 }
1152 }
1153 }
1154
1155 if !current_struct_name_tokens.is_empty() && !current_field_tokens.is_empty() {
1156 found_structs.push((current_struct_name_tokens, vec![current_field_tokens]));
1157 }
1158
1159 if found_structs.is_empty() {
1160 return Err("Unexpected empty path definition!".to_string());
1161 }
1162
1163 let all_check_functions = generate_checks_code_for(&found_structs);
1164 let full_field_path = found_structs
1169 .iter()
1170 .flat_map(|(_, fields)| fields.iter())
1171 .map(|tokens| render_text(tokens))
1172 .collect::<Vec<_>>()
1173 .join(".")
1174 .replace('~', ".");
1175 let final_field_path = apply_options(&options, full_field_path)?;
1176 let fields_str = format!("\"{}\"", final_field_path);
1177 let fields_tokens: TokenStream = fields_str
1178 .parse()
1179 .map_err(|_| format!("Generated code failed to parse: {}", fields_str))?;
1180 let mut block_contents = all_check_functions;
1181 block_contents.extend(fields_tokens);
1182 Ok(TokenStream::from(TokenTree::Group(Group::new(
1183 Delimiter::Brace,
1184 block_contents,
1185 ))))
1186}
1187
1188fn generate_checks_code_for(found_structs: &FoundStructs) -> TokenStream {
1194 let mut all_check_functions = TokenStream::new();
1195 for (struct_name_tokens, struct_fields) in found_structs {
1196 let struct_name: TokenStream = struct_name_tokens.iter().cloned().collect();
1197 for field_tokens in struct_fields {
1198 all_check_functions.extend(fill(
1199 "const _: fn(&STRUCT) = |t: &STRUCT| { let _ = &t.FIELD; };",
1200 Span::call_site(),
1201 &[
1202 ("STRUCT", &struct_name),
1203 ("FIELD", &field_check_tokens(field_tokens)),
1204 ],
1205 ));
1206 }
1207 }
1208 all_check_functions
1209}
1210
1211fn field_check_tokens(field_tokens: &[TokenTree]) -> TokenStream {
1217 field_tokens
1218 .iter()
1219 .map(|token| match token {
1220 TokenTree::Punct(punct) if *punct == '~' => {
1221 fill(".iter().next().unwrap().", punct.span(), &[])
1222 }
1223 token => token.clone().into(),
1224 })
1225 .collect()
1226}
1227
1228fn fill(template: &str, span: Span, substitutions: &[(&str, &TokenStream)]) -> TokenStream {
1232 fn walk(stream: TokenStream, span: Span, subs: &[(&str, &TokenStream)]) -> TokenStream {
1233 stream
1234 .into_iter()
1235 .map(|mut token| {
1236 match &token {
1237 TokenTree::Ident(ident) => {
1238 let name = ident.to_string();
1239 if let Some((_, tokens)) = subs.iter().find(|(n, _)| *n == name) {
1240 return (*tokens).clone();
1241 }
1242 }
1243 TokenTree::Group(group) => {
1244 let stream = walk(group.stream(), span, subs);
1245 token = TokenTree::Group(Group::new(group.delimiter(), stream));
1246 }
1247 _ => {}
1248 }
1249 token.set_span(span);
1250 token.into()
1251 })
1252 .collect()
1253 }
1254 walk(
1255 template.parse().expect("templates are valid tokens"),
1256 span,
1257 substitutions,
1258 )
1259}
1260
1261fn unquote_literal(lit: &proc_macro::Literal) -> Result<String, String> {
1268 let text = lit.to_string();
1269 let bytes = text.as_bytes();
1270 let is_quoted = bytes.len() >= 2
1271 && ((bytes[0] == b'"' && bytes[bytes.len() - 1] == b'"')
1272 || (bytes[0] == b'\'' && bytes[bytes.len() - 1] == b'\''));
1273 if is_quoted {
1274 Ok(text[1..text.len() - 1].to_string())
1275 } else {
1276 Err(format!(
1277 "Unsupported option value {}: expected a string (\"...\") or char ('.') literal",
1278 text
1279 ))
1280 }
1281}
1282
1283fn convert_case_by_name(field_name: &str, case_name: &str) -> Result<String, String> {
1288 match case_name {
1289 "camel" => Ok(field_name.from_case(Case::Snake).to_case(Case::Camel)),
1290 "pascal" => Ok(field_name.from_case(Case::Snake).to_case(Case::Pascal)),
1291 another => Err(format!("Unknown case is specified: {}", another)),
1292 }
1293}
1294
1295fn convert_all(names: &[String], case_name: &str) -> Vec<String> {
1299 names
1300 .iter()
1301 .map(|n| {
1302 convert_case_by_name(n, case_name)
1303 .unwrap_or_else(|_| panic!("{:?} is always a known case", case_name))
1304 })
1305 .collect()
1306}
1307
1308fn apply_options(options: &HashMap<String, String>, field_path: String) -> Result<String, String> {
1309 let delim = options
1310 .get("delim")
1311 .as_ref()
1312 .map(|s| s.as_str())
1313 .unwrap_or(".");
1314 let case = options.get("case");
1315 let segments = field_path
1316 .split('.')
1317 .map(|field_name| match case {
1318 Some(case_value) => convert_case_by_name(field_name, case_value),
1319 None => Ok(field_name.to_string()),
1320 })
1321 .collect::<Result<Vec<String>, String>>()?;
1322 Ok(segments.join(delim))
1323}
1324
1325#[proc_macro_derive(StructPath)]
1332pub fn derive_struct_path(input: TokenStream) -> TokenStream {
1333 match derive_struct_path_impl(input) {
1334 Ok(stream) => stream,
1335 Err(error_stream) => error_stream,
1336 }
1337}
1338
1339struct DerivedField {
1345 name: String,
1346 is_plain_pub: bool,
1347}
1348
1349fn derive_struct_path_impl(input: TokenStream) -> Result<TokenStream, TokenStream> {
1350 let tokens: Vec<TokenTree> = input.into_iter().collect();
1351 let mut pos = 0;
1352
1353 skip_attributes(&tokens, &mut pos);
1354
1355 let visibility_start = pos;
1356 let (vis_len, item_is_plain_pub) = parse_visibility_prefix(&tokens[pos..]);
1357 pos += vis_len;
1358 let visibility_tokens: TokenStream = tokens[visibility_start..pos].iter().cloned().collect();
1359
1360 let (keyword, keyword_span) = match tokens.get(pos) {
1361 Some(TokenTree::Ident(id)) => (id.to_string(), id.span()),
1362 _ => {
1363 return Err(compile_error_at(
1364 "StructPath: unable to parse the derived item",
1365 Span::call_site(),
1366 ))
1367 }
1368 };
1369 if keyword != "struct" {
1370 return Err(compile_error_at(
1371 &format!(
1372 "StructPath supports structs with named fields only, not {}s",
1373 keyword
1374 ),
1375 keyword_span,
1376 ));
1377 }
1378 pos += 1;
1379
1380 let name_ident = match tokens.get(pos) {
1381 Some(TokenTree::Ident(id)) => id.clone(),
1382 _ => {
1383 return Err(compile_error_at(
1384 "StructPath: expected a struct name",
1385 keyword_span,
1386 ))
1387 }
1388 };
1389 let name_span = name_ident.span();
1390 pos += 1;
1391
1392 let (generics_impl, generics_type) = parse_lifetime_generics(&tokens, &mut pos)?;
1393 let (where_clause, fields_group) = find_fields_group(&tokens[pos..])?;
1394 let fields = parse_derived_fields(fields_group.stream())?;
1395
1396 let pub_names: Vec<String> = fields
1397 .iter()
1398 .filter(|f| f.is_plain_pub)
1399 .map(|f| f.name.clone())
1400 .collect();
1401 let all_names: Vec<String> = fields.iter().map(|f| f.name.clone()).collect();
1402
1403 let name_tokens = TokenStream::from(TokenTree::Ident(name_ident));
1404 let pub_array = array_literal_tokens(&pub_names);
1405 let pub_camel_array = array_literal_tokens(&convert_all(&pub_names, "camel"));
1406 let pub_pascal_array = array_literal_tokens(&convert_all(&pub_names, "pascal"));
1407 let all_array = array_literal_tokens(&all_names);
1408 let all_camel_array = array_literal_tokens(&convert_all(&all_names, "camel"));
1409 let all_pascal_array = array_literal_tokens(&convert_all(&all_names, "pascal"));
1410 let n_pub: TokenStream = pub_names
1411 .len()
1412 .to_string()
1413 .parse()
1414 .expect("a field count always parses as a literal");
1415 let n_all: TokenStream = all_names
1416 .len()
1417 .to_string()
1418 .parse()
1419 .expect("a field count always parses as a literal");
1420
1421 let all_fields_visibility: TokenStream = if item_is_plain_pub {
1432 "pub(crate)".parse().expect("`pub(crate)` always parses")
1433 } else {
1434 visibility_tokens.clone()
1435 };
1436
1437 let generated = fill(
1448 "impl<GENERICS_IMPL> NAME<GENERICS_TYPE> WHERE_CLAUSE {
1449 #[doc(hidden)]
1450 VIS const __STRUCT_PATH_PUB_FIELDS: [&'static str; N_PUB] = PUB_FIELDS;
1451 #[doc(hidden)]
1452 VIS const __STRUCT_PATH_PUB_FIELDS_CAMEL: [&'static str; N_PUB] = PUB_FIELDS_CAMEL;
1453 #[doc(hidden)]
1454 VIS const __STRUCT_PATH_PUB_FIELDS_PASCAL: [&'static str; N_PUB] = PUB_FIELDS_PASCAL;
1455 #[doc(hidden)]
1456 ALL_VIS const __STRUCT_PATH_ALL_FIELDS: [&'static str; N_ALL] = ALL_FIELDS;
1457 #[doc(hidden)]
1458 ALL_VIS const __STRUCT_PATH_ALL_FIELDS_CAMEL: [&'static str; N_ALL] = ALL_FIELDS_CAMEL;
1459 #[doc(hidden)]
1460 ALL_VIS const __STRUCT_PATH_ALL_FIELDS_PASCAL: [&'static str; N_ALL] = ALL_FIELDS_PASCAL;
1461
1462 #[doc(hidden)]
1463 #[must_use]
1464 VIS const fn __struct_path_joined_len(prefix: &str, delim: &str, names: &[&str]) -> usize {
1465 let mut total = 0;
1466 let mut i = 0;
1467 while i < names.len() {
1468 total += prefix.len() + delim.len() + names[i].len();
1469 i += 1;
1470 }
1471 total
1472 }
1473
1474 #[doc(hidden)]
1475 #[must_use]
1476 VIS const fn __struct_path_joined_bytes<const TOTAL: usize>(
1477 prefix: &str,
1478 delim: &str,
1479 names: &[&str],
1480 ) -> [u8; TOTAL] {
1481 let mut buf = [0u8; TOTAL];
1482 let mut pos = 0;
1483 let mut i = 0;
1484 while i < names.len() {
1485 let parts = [prefix.as_bytes(), delim.as_bytes(), names[i].as_bytes()];
1486 let mut p = 0;
1487 while p < parts.len() {
1488 let mut b = 0;
1489 while b < parts[p].len() {
1490 buf[pos] = parts[p][b];
1491 pos += 1;
1492 b += 1;
1493 }
1494 p += 1;
1495 }
1496 i += 1;
1497 }
1498 buf
1499 }
1500
1501 #[doc(hidden)]
1502 #[must_use]
1503 VIS const fn __struct_path_joined<const N: usize, const TOTAL: usize>(
1504 bytes: &'static [u8; TOTAL],
1505 prefix: &str,
1506 delim: &str,
1507 names: &[&str; N],
1508 ) -> [&'static str; N] {
1509 let mut out = [\"\"; N];
1510 let mut pos = 0;
1511 let mut i = 0;
1512 while i < N {
1513 let len = prefix.len() + delim.len() + names[i].len();
1514 let (_, rest) = bytes.split_at(pos);
1515 let (piece, _) = rest.split_at(len);
1516 out[i] = match core::str::from_utf8(piece) {
1517 Ok(s) => s,
1518 Err(_) => panic!(\"struct-path: joined path is not valid UTF-8\"),
1519 };
1520 pos += len;
1521 i += 1;
1522 }
1523 out
1524 }
1525 }",
1526 name_span,
1527 &[
1528 ("GENERICS_IMPL", &generics_impl),
1529 ("GENERICS_TYPE", &generics_type),
1530 ("NAME", &name_tokens),
1531 ("WHERE_CLAUSE", &where_clause),
1532 ("VIS", &visibility_tokens),
1533 ("ALL_VIS", &all_fields_visibility),
1534 ("N_PUB", &n_pub),
1535 ("N_ALL", &n_all),
1536 ("PUB_FIELDS", &pub_array),
1537 ("PUB_FIELDS_CAMEL", &pub_camel_array),
1538 ("PUB_FIELDS_PASCAL", &pub_pascal_array),
1539 ("ALL_FIELDS", &all_array),
1540 ("ALL_FIELDS_CAMEL", &all_camel_array),
1541 ("ALL_FIELDS_PASCAL", &all_pascal_array),
1542 ],
1543 );
1544 Ok(generated)
1545}
1546
1547fn skip_attributes(tokens: &[TokenTree], pos: &mut usize) {
1550 while matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '#') {
1551 *pos += 1;
1552 if matches!(tokens.get(*pos), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Bracket)
1553 {
1554 *pos += 1;
1555 }
1556 }
1557}
1558
1559fn parse_visibility_prefix(tokens: &[TokenTree]) -> (usize, bool) {
1568 if let Some(TokenTree::Group(g)) = tokens.first() {
1569 if g.delimiter() == Delimiter::None {
1570 let inner: Vec<TokenTree> = g.stream().into_iter().collect();
1571 let (_, is_plain_pub) = parse_visibility_prefix(&inner);
1572 return (1, is_plain_pub);
1573 }
1574 }
1575 if matches!(tokens.first(), Some(TokenTree::Ident(id)) if id.to_string() == "pub") {
1576 if matches!(tokens.get(1), Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis)
1577 {
1578 return (2, false);
1579 }
1580 return (1, true);
1581 }
1582 (0, false)
1583}
1584
1585fn parse_field_visibility(tokens: &[TokenTree], pos: &mut usize) -> bool {
1590 let (len, is_plain_pub) = parse_visibility_prefix(&tokens[*pos..]);
1591 *pos += len;
1592 is_plain_pub
1593}
1594
1595fn split_top_level_commas(tokens: &[TokenTree]) -> Vec<Vec<TokenTree>> {
1609 let mut segments = Vec::new();
1610 let mut current = Vec::new();
1611 let mut depth: i32 = 0;
1612 let mut prev_was_arrow_dash = false;
1613 for token in tokens {
1614 match token {
1615 TokenTree::Punct(p) if *p == '<' => {
1616 depth += 1;
1617 current.push(token.clone());
1618 }
1619 TokenTree::Punct(p) if *p == '>' => {
1620 if !prev_was_arrow_dash {
1621 depth = (depth - 1).max(0);
1622 }
1623 current.push(token.clone());
1624 }
1625 TokenTree::Punct(p) if *p == ',' && depth == 0 => {
1626 segments.push(std::mem::take(&mut current));
1627 }
1628 other => current.push(other.clone()),
1629 }
1630 prev_was_arrow_dash =
1631 matches!(token, TokenTree::Punct(p) if *p == '-' && p.spacing() == Spacing::Joint);
1632 }
1633 if !current.is_empty() {
1634 segments.push(current);
1635 }
1636 segments
1637}
1638
1639fn lifetime_param_name(segment: &[TokenTree]) -> Option<Vec<TokenTree>> {
1644 if segment.len() < 2 {
1645 return None;
1646 }
1647 if !matches!(&segment[0], TokenTree::Punct(p) if *p == '\'') {
1648 return None;
1649 }
1650 if !matches!(&segment[1], TokenTree::Ident(_)) {
1651 return None;
1652 }
1653 let bare = segment[0..2].to_vec();
1654 if segment.len() == 2 {
1655 return Some(bare);
1656 }
1657 if !matches!(&segment[2], TokenTree::Punct(p) if *p == ':') {
1658 return None;
1659 }
1660 if segment.len() == 3 {
1661 return Some(bare);
1664 }
1665 let mut i = 3;
1666 loop {
1667 if i + 1 >= segment.len() {
1668 return None;
1669 }
1670 let is_lifetime = matches!(&segment[i], TokenTree::Punct(p) if *p == '\'')
1671 && matches!(&segment[i + 1], TokenTree::Ident(_));
1672 if !is_lifetime {
1673 return None;
1674 }
1675 i += 2;
1676 if i == segment.len() {
1677 break;
1678 }
1679 if !matches!(&segment[i], TokenTree::Punct(p) if *p == '+') {
1680 return None;
1681 }
1682 i += 1;
1683 }
1684 Some(bare)
1685}
1686
1687fn parse_lifetime_generics(
1692 tokens: &[TokenTree],
1693 pos: &mut usize,
1694) -> Result<(TokenStream, TokenStream), TokenStream> {
1695 if !matches!(tokens.get(*pos), Some(TokenTree::Punct(p)) if *p == '<') {
1696 return Ok((TokenStream::new(), TokenStream::new()));
1697 }
1698 let open_span = match &tokens[*pos] {
1699 TokenTree::Punct(p) => p.span(),
1700 _ => unreachable!(),
1701 };
1702 *pos += 1;
1703 let start = *pos;
1704 let mut depth = 1;
1705 while depth > 0 {
1706 match tokens.get(*pos) {
1707 Some(TokenTree::Punct(p)) if *p == '<' => depth += 1,
1708 Some(TokenTree::Punct(p)) if *p == '>' => depth -= 1,
1709 Some(_) => {}
1710 None => {
1711 return Err(compile_error_at(
1712 "StructPath: unterminated generic parameter list",
1713 open_span,
1714 ))
1715 }
1716 }
1717 *pos += 1;
1718 }
1719 let generics_tokens = &tokens[start..*pos - 1];
1720
1721 let mut bare_lifetimes: Vec<TokenTree> = Vec::new();
1722 for (i, segment) in split_top_level_commas(generics_tokens)
1723 .into_iter()
1724 .enumerate()
1725 {
1726 if i > 0 {
1727 bare_lifetimes.extend(fill(",", open_span, &[]));
1728 }
1729 match lifetime_param_name(&segment) {
1730 Some(bare) => bare_lifetimes.extend(bare),
1731 None => {
1732 let span = segment.first().map(|t| t.span()).unwrap_or(open_span);
1733 return Err(compile_error_at(
1734 "StructPath only supports lifetime generic parameters",
1735 span,
1736 ));
1737 }
1738 }
1739 }
1740
1741 let generics_impl: TokenStream = generics_tokens.iter().cloned().collect();
1742 let generics_type: TokenStream = bare_lifetimes.into_iter().collect();
1743 Ok((generics_impl, generics_type))
1744}
1745
1746fn find_fields_group(remaining: &[TokenTree]) -> Result<(TokenStream, Group), TokenStream> {
1757 match remaining.last() {
1758 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Brace => {
1759 let where_clause: TokenStream =
1760 remaining[..remaining.len() - 1].iter().cloned().collect();
1761 Ok((where_clause, g.clone()))
1762 }
1763 Some(TokenTree::Punct(p)) if *p == ';' => match remaining.first() {
1764 Some(TokenTree::Group(g)) if g.delimiter() == Delimiter::Parenthesis => {
1765 Err(compile_error_at(
1766 "StructPath supports structs with named fields only, not tuple structs",
1767 g.span(),
1768 ))
1769 }
1770 _ => Err(compile_error_at(
1771 "StructPath supports structs with named fields only, not unit structs",
1772 p.span(),
1773 )),
1774 },
1775 _ => Err(compile_error_at(
1776 "StructPath: expected a struct body with named fields",
1777 Span::call_site(),
1778 )),
1779 }
1780}
1781
1782fn parse_derived_fields(fields_stream: TokenStream) -> Result<Vec<DerivedField>, TokenStream> {
1789 let tokens: Vec<TokenTree> = fields_stream.into_iter().collect();
1790 let mut fields = Vec::new();
1791 for segment in split_top_level_commas(&tokens) {
1792 let mut pos = 0;
1793 skip_attributes(&segment, &mut pos);
1794 let is_plain_pub = parse_field_visibility(&segment, &mut pos);
1795 let name_ident = match segment.get(pos) {
1796 Some(TokenTree::Ident(id)) => id,
1797 _ => {
1798 return Err(compile_error_at(
1799 "StructPath: expected a field name",
1800 segment
1801 .first()
1802 .map(|t| t.span())
1803 .unwrap_or_else(Span::call_site),
1804 ))
1805 }
1806 };
1807 let name = name_ident.to_string();
1808 let name = name.strip_prefix("r#").unwrap_or(&name).to_string();
1809 fields.push(DerivedField { name, is_plain_pub });
1810 }
1811 Ok(fields)
1812}
1813
1814fn array_literal_tokens(values: &[String]) -> TokenStream {
1819 let joined = values
1820 .iter()
1821 .map(|v| format!("\"{}\"", v))
1822 .collect::<Vec<_>>()
1823 .join(",");
1824 format!("[{}]", joined)
1825 .parse()
1826 .expect("field names always produce a valid array literal")
1827}