1use proc_macro2::TokenTree;
2use std::collections::{BTreeSet as Set, HashMap as Map};
3
4use quote::{format_ident, quote_spanned};
5use syn::ext::IdentExt;
6use syn::parse::{ParseStream, Parser};
7use syn::{Ident, Index, LitStr, Member, Result, Token};
8
9use crate::ast::Field;
10use crate::attr::{Display, Trait};
11
12impl Display<'_> {
13 pub fn expand_shorthand(&mut self, fields: &[Field]) {
15 let raw_args = self.args.clone();
16 let mut named_args = explicit_named_args.parse2(raw_args).unwrap();
17 let mut member_index = Map::new();
18 for (i, field) in fields.iter().enumerate() {
19 member_index.insert(&field.member, i);
20 }
21
22 let span = self.fmt.span();
23 let fmt = self.fmt.value();
24 let mut read = fmt.as_str();
25 let mut out = String::new();
26 let mut args = self.args.clone();
27 let mut has_bonus_display = false;
28 let mut implied_bounds = Set::new();
29
30 let mut has_trailing_comma = false;
31 if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() {
32 if punct.as_char() == ',' {
33 has_trailing_comma = true;
34 }
35 }
36
37 self.requires_fmt_machinery = self.requires_fmt_machinery || fmt.contains('}');
38
39 while let Some(brace) = read.find('{') {
40 self.requires_fmt_machinery = true;
41 out += &read[..brace + 1];
42 read = &read[brace + 1..];
43 if read.starts_with('{') {
44 out.push('{');
45 read = &read[1..];
46 continue;
47 }
48 let next = match read.chars().next() {
49 Some(next) => next,
50 None => return,
51 };
52 let member = match next {
53 '0'..='9' => {
54 let int = take_int(&mut read);
55 let member = match int.parse::<u32>() {
56 Ok(index) => Member::Unnamed(Index { index, span }),
57 Err(_) => return,
58 };
59 if !member_index.contains_key(&member) {
60 out += ∫
61 continue;
62 }
63 member
64 }
65 'a'..='z' | 'A'..='Z' | '_' => {
66 let mut ident = take_ident(&mut read);
67 ident.set_span(span);
68 Member::Named(ident)
69 }
70 _ => continue,
71 };
72 if let Some(&field) = member_index.get(&member) {
73 let end_spec = match read.find('}') {
74 Some(end_spec) => end_spec,
75 None => return,
76 };
77 let bound = match read[..end_spec].chars().next_back() {
78 Some('?') => Trait::Debug,
79 Some('o') => Trait::Octal,
80 Some('x') => Trait::LowerHex,
81 Some('X') => Trait::UpperHex,
82 Some('p') => Trait::Pointer,
83 Some('b') => Trait::Binary,
84 Some('e') => Trait::LowerExp,
85 Some('E') => Trait::UpperExp,
86 Some(_) | None => Trait::Display,
87 };
88 implied_bounds.insert((field, bound));
89 }
90 let local = match &member {
91 Member::Unnamed(index) => format_ident!("_{}", index),
92 Member::Named(ident) => ident.clone(),
93 };
94 let mut formatvar = local.clone();
95 if formatvar.to_string().starts_with("r#") {
96 formatvar = format_ident!("r_{}", formatvar);
97 }
98 if formatvar.to_string().starts_with('_') {
99 formatvar = format_ident!("field_{}", formatvar);
102 }
103 out += &formatvar.to_string();
104 if !named_args.insert(formatvar.clone()) {
105 continue;
107 }
108 if !has_trailing_comma {
109 args.extend(quote_spanned!(span=> ,));
110 }
111 args.extend(quote_spanned!(span=> #formatvar = #local));
112 if read.starts_with('}') && member_index.contains_key(&member) {
113 has_bonus_display = true;
114 args.extend(quote_spanned!(span=> .as_display()));
115 }
116 has_trailing_comma = false;
117 }
118
119 out += read;
120 self.fmt = LitStr::new(&out, self.fmt.span());
121 self.args = args;
122 self.has_bonus_display = has_bonus_display;
123 self.implied_bounds = implied_bounds;
124 }
125}
126
127fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
128 let mut named_args = Set::new();
129
130 while !input.is_empty() {
131 if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
132 input.parse::<Token![,]>()?;
133 let ident = input.call(Ident::parse_any)?;
134 input.parse::<Token![=]>()?;
135 named_args.insert(ident);
136 } else {
137 input.parse::<TokenTree>()?;
138 }
139 }
140
141 Ok(named_args)
142}
143
144fn take_int(read: &mut &str) -> String {
145 let mut int = String::new();
146 for (i, ch) in read.char_indices() {
147 match ch {
148 '0'..='9' => int.push(ch),
149 _ => {
150 *read = &read[i..];
151 break;
152 }
153 }
154 }
155 int
156}
157
158fn take_ident(read: &mut &str) -> Ident {
159 let mut ident = String::new();
160 let raw = read.starts_with("r#");
161 if raw {
162 ident.push_str("r#");
163 *read = &read[2..];
164 }
165 for (i, ch) in read.char_indices() {
166 match ch {
167 'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
168 _ => {
169 *read = &read[i..];
170 break;
171 }
172 }
173 }
174 Ident::parse_any.parse_str(&ident).unwrap()
175}