miette_derive/
fmt.rs

1// NOTE: Most code in this file is taken straight from `thiserror`.
2use std::collections::HashSet as Set;
3use std::iter::FromIterator;
4
5use proc_macro2::{Delimiter, Group, TokenStream, TokenTree};
6use quote::{format_ident, quote, quote_spanned, ToTokens};
7use syn::ext::IdentExt;
8use syn::parse::{ParseStream, Parser};
9use syn::{braced, bracketed, parenthesized, Ident, Index, LitStr, Member, Result, Token};
10
11#[derive(Clone)]
12pub struct Display {
13    pub fmt: LitStr,
14    pub args: TokenStream,
15    pub has_bonus_display: bool,
16}
17
18impl ToTokens for Display {
19    fn to_tokens(&self, tokens: &mut TokenStream) {
20        let fmt = &self.fmt;
21        let args = &self.args;
22        tokens.extend(quote! {
23            write!(__formatter, #fmt #args)
24        });
25    }
26}
27
28impl Display {
29    // Transform `"error {var}"` to `"error {}", var`.
30    pub fn expand_shorthand(&mut self, members: &Set<Member>) {
31        let raw_args = self.args.clone();
32        let mut named_args = explicit_named_args.parse2(raw_args).unwrap();
33
34        let span = self.fmt.span();
35        let fmt = self.fmt.value();
36        let mut read = fmt.as_str();
37        let mut out = String::new();
38        let mut args = self.args.clone();
39        let mut has_bonus_display = false;
40
41        let mut has_trailing_comma = false;
42        if let Some(TokenTree::Punct(punct)) = args.clone().into_iter().last() {
43            if punct.as_char() == ',' {
44                has_trailing_comma = true;
45            }
46        }
47
48        while let Some(brace) = read.find('{') {
49            out += &read[..brace + 1];
50            read = &read[brace + 1..];
51            if read.starts_with('{') {
52                out.push('{');
53                read = &read[1..];
54                continue;
55            }
56            let next = match read.chars().next() {
57                Some(next) => next,
58                None => return,
59            };
60            let member = match next {
61                '0'..='9' => {
62                    let int = take_int(&mut read);
63                    let member = match int.parse::<u32>() {
64                        Ok(index) => Member::Unnamed(Index { index, span }),
65                        Err(_) => return,
66                    };
67                    if !members.contains(&member) {
68                        out += &int;
69                        continue;
70                    }
71                    member
72                }
73                'a'..='z' | 'A'..='Z' | '_' => {
74                    let mut ident = take_ident(&mut read);
75                    ident.set_span(span);
76                    Member::Named(ident)
77                }
78                _ => continue,
79            };
80            let local = match &member {
81                Member::Unnamed(index) => format_ident!("_{}", index),
82                Member::Named(ident) => ident.clone(),
83            };
84            let mut formatvar = local.clone();
85            if formatvar.to_string().starts_with("r#") {
86                formatvar = format_ident!("r_{}", formatvar);
87            }
88            if formatvar.to_string().starts_with('_') {
89                // Work around leading underscore being rejected by 1.40 and
90                // older compilers. https://github.com/rust-lang/rust/pull/66847
91                formatvar = format_ident!("field_{}", formatvar);
92            }
93            out += &formatvar.to_string();
94            if !named_args.insert(formatvar.clone()) {
95                // Already specified in the format argument list.
96                continue;
97            }
98            if !has_trailing_comma {
99                args.extend(quote_spanned!(span=> ,));
100            }
101            args.extend(quote_spanned!(span=> #formatvar = #local));
102            if read.starts_with('}') && members.contains(&member) {
103                has_bonus_display = true;
104                // args.extend(quote_spanned!(span=> .as_display()));
105            }
106            has_trailing_comma = false;
107        }
108
109        out += read;
110        self.fmt = LitStr::new(&out, self.fmt.span());
111        self.args = args;
112        self.has_bonus_display = has_bonus_display;
113    }
114}
115
116fn explicit_named_args(input: ParseStream) -> Result<Set<Ident>> {
117    let mut named_args = Set::new();
118
119    while !input.is_empty() {
120        if input.peek(Token![,]) && input.peek2(Ident::peek_any) && input.peek3(Token![=]) {
121            input.parse::<Token![,]>()?;
122            let ident = input.call(Ident::parse_any)?;
123            input.parse::<Token![=]>()?;
124            named_args.insert(ident);
125        } else {
126            input.parse::<TokenTree>()?;
127        }
128    }
129
130    Ok(named_args)
131}
132
133fn take_int(read: &mut &str) -> String {
134    let mut int = String::new();
135    for (i, ch) in read.char_indices() {
136        match ch {
137            '0'..='9' => int.push(ch),
138            _ => {
139                *read = &read[i..];
140                break;
141            }
142        }
143    }
144    int
145}
146
147fn take_ident(read: &mut &str) -> Ident {
148    let mut ident = String::new();
149    let raw = read.starts_with("r#");
150    if raw {
151        ident.push_str("r#");
152        *read = &read[2..];
153    }
154    for (i, ch) in read.char_indices() {
155        match ch {
156            'a'..='z' | 'A'..='Z' | '0'..='9' | '_' => ident.push(ch),
157            _ => {
158                *read = &read[i..];
159                break;
160            }
161        }
162    }
163    Ident::parse_any.parse_str(&ident).unwrap()
164}
165
166pub fn parse_token_expr(input: ParseStream, mut begin_expr: bool) -> Result<TokenStream> {
167    let mut tokens = Vec::new();
168    while !input.is_empty() {
169        if begin_expr && input.peek(Token![.]) {
170            if input.peek2(Ident) {
171                input.parse::<Token![.]>()?;
172                begin_expr = false;
173                continue;
174            }
175            if input.peek2(syn::LitInt) {
176                input.parse::<Token![.]>()?;
177                let int: Index = input.parse()?;
178                let ident = format_ident!("_{}", int.index, span = int.span);
179                tokens.push(TokenTree::Ident(ident));
180                begin_expr = false;
181                continue;
182            }
183        }
184
185        begin_expr = input.peek(Token![break])
186            || input.peek(Token![continue])
187            || input.peek(Token![if])
188            || input.peek(Token![in])
189            || input.peek(Token![match])
190            || input.peek(Token![mut])
191            || input.peek(Token![return])
192            || input.peek(Token![while])
193            || input.peek(Token![+])
194            || input.peek(Token![&])
195            || input.peek(Token![!])
196            || input.peek(Token![^])
197            || input.peek(Token![,])
198            || input.peek(Token![/])
199            || input.peek(Token![=])
200            || input.peek(Token![>])
201            || input.peek(Token![<])
202            || input.peek(Token![|])
203            || input.peek(Token![%])
204            || input.peek(Token![;])
205            || input.peek(Token![*])
206            || input.peek(Token![-]);
207
208        let token: TokenTree = if input.peek(syn::token::Paren) {
209            let content;
210            let delimiter = parenthesized!(content in input);
211            let nested = parse_token_expr(&content, true)?;
212            let mut group = Group::new(Delimiter::Parenthesis, nested);
213            group.set_span(delimiter.span.join());
214            TokenTree::Group(group)
215        } else if input.peek(syn::token::Brace) {
216            let content;
217            let delimiter = braced!(content in input);
218            let nested = parse_token_expr(&content, true)?;
219            let mut group = Group::new(Delimiter::Brace, nested);
220            group.set_span(delimiter.span.join());
221            TokenTree::Group(group)
222        } else if input.peek(syn::token::Bracket) {
223            let content;
224            let delimiter = bracketed!(content in input);
225            let nested = parse_token_expr(&content, true)?;
226            let mut group = Group::new(Delimiter::Bracket, nested);
227            group.set_span(delimiter.span.join());
228            TokenTree::Group(group)
229        } else {
230            input.parse()?
231        };
232        tokens.push(token);
233    }
234    Ok(TokenStream::from_iter(tokens))
235}