thiserror_impl/
fmt.rs

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