const_format_proc_macros/format_args/
parsing.rs

1use super::{
2    ExpandFormatted, ExpandInto, ExpandWithFormatter, FormatArg, FormatArgs, FormatIfArgs,
3    LocalVariable, UncheckedFormatArg, UncheckedFormatArgs, WriteArgs,
4};
5
6use crate::{
7    format_str::{FmtArg, FmtStrComponent, FormatStr, WhichArg},
8    parse_utils::{LitStr, MyParse, ParseBuffer, ParseStream, TokenTreeExt},
9    shared_arg_parsing::ExprArg,
10    spanned::Spans,
11    utils::{dummy_ident, LinearResult},
12};
13
14use proc_macro2::{Ident, Span, TokenTree};
15
16////////////////////////////////////////////////
17
18impl MyParse for UncheckedFormatArg {
19    fn parse(input: ParseStream<'_>) -> Result<Self, crate::Error> {
20        // the compile wraps `:expr` in macro_rules macros in a TokenStream::Group
21        // with no delimiters.
22        input.parse_unwrap_group(|content| {
23            let mut ident = None;
24            if matches!(content.peek2(), Some(x) if x.is_punct('=')) {
25                ident = Some(content.parse_ident()?);
26                content.next();
27            }
28
29            // For some reason,
30            // the compile wraps closures in parentheses when passing them as
31            // expressions to proc macros.
32            content.parse_unwrap_paren(|content| {
33                let mut fmt_ident = None;
34
35                if matches!(content.peek(), Some(x) if x.is_punct('|'))
36                    && matches!(content.peek2(), Some(TokenTree::Ident(_)))
37                {
38                    content.next();
39                    fmt_ident = Some(content.parse_ident()?);
40                    content.parse_punct('|')?;
41                }
42
43                let (expr, spans) = if content.peek2().is_some() {
44                    content.parse_token_stream_and_span()
45                } else {
46                    content.parse_unwrap_tt(|content| Ok(content.parse_token_stream_and_span()))?
47                };
48
49                Ok(Self {
50                    spans,
51                    ident,
52                    fmt_ident,
53                    expr,
54                })
55            })
56        })
57    }
58}
59
60////////////////////////////////////////////////
61
62fn lit_str_to_fmt_lit(lit: &LitStr) -> Result<FormatStr, crate::Error> {
63    let lit_str = lit.value();
64    let format_str_span = lit.span;
65    FormatStr::parse(lit.value(), lit.rawness)
66        .map_err(|e| e.into_crate_err(format_str_span, lit_str))
67}
68
69fn parse_fmt_lit(this: &mut FormatStr, input: ParseStream<'_>) -> Result<(), crate::Error> {
70    input.parse_unwrap_tt(|input| {
71        let tt = input.next();
72
73        match tt {
74            Some(TokenTree::Literal(lit)) => {
75                let mut lit = lit_str_to_fmt_lit(&LitStr::parse_from_literal(&lit)?)?;
76
77                this.list.append(&mut lit.list);
78
79                Ok(())
80            }
81            Some(TokenTree::Ident(ident)) if ident == "concat" => {
82                input.next(); // skipping the `!`
83                let paren = input.parse_paren()?;
84                let mut input = ParseBuffer::new(paren.contents);
85
86                while !input.is_empty() {
87                    parse_fmt_lit(this, &mut input)?;
88                    input.parse_opt_punct(',')?;
89                }
90                Ok(())
91            }
92            _ => Ok(()),
93        }
94    })
95}
96
97impl MyParse for UncheckedFormatArgs {
98    fn parse(input: ParseStream<'_>) -> Result<Self, crate::Error> {
99        let mut literal = FormatStr { list: Vec::new() };
100
101        // Have to parse `concat!()` because it's not expanded before the proc macro is called.
102        {
103            let paren = input.parse_paren()?;
104            let mut input = ParseBuffer::new(paren.contents);
105
106            parse_fmt_lit(&mut literal, &mut input)?;
107        }
108
109        input.parse_opt_punct(',')?;
110
111        let mut args = Vec::new();
112
113        while !input.is_empty() {
114            args.push(UncheckedFormatArg::parse(input)?);
115
116            input.parse_opt_punct(',')?;
117        }
118
119        Ok(Self { literal, args })
120    }
121}
122
123////////////////////////////////////////////////
124
125impl MyParse for FormatArgs {
126    fn parse(input: ParseStream<'_>) -> Result<Self, crate::Error> {
127        let prefix = Ident::new("__const_fmt_local_", Span::call_site());
128        FormatArgs::parse_with(input, prefix)
129    }
130}
131
132impl FormatArgs {
133    pub fn parse_with(input: ParseStream<'_>, prefix: Ident) -> Result<FormatArgs, crate::Error> {
134        let mut res = LinearResult::ok();
135
136        let unchecked_fargs = UncheckedFormatArgs::parse(input)?;
137
138        let mut first_named_arg = unchecked_fargs.args.len();
139
140        let mut named_arg_names = Vec::<Ident>::new();
141        let mut args = Vec::<FormatArg>::with_capacity(unchecked_fargs.args.len());
142        let mut local_variables = Vec::<LocalVariable>::with_capacity(unchecked_fargs.args.len());
143
144        let arg_span_idents: Vec<(Spans, Option<Ident>)> = unchecked_fargs
145            .args
146            .iter()
147            .map(|x| (x.spans, x.ident.clone()))
148            .collect();
149
150        {
151            let mut prev_is_named_arg = false;
152            for (i, arg) in unchecked_fargs.args.into_iter().enumerate() {
153                let expr_span = arg.spans;
154
155                let make_ident = |s: String| Ident::new(&s, expr_span.start);
156
157                let is_named_arg = arg.ident.is_some();
158
159                let var_name = if let Some(ident) = arg.ident {
160                    if !prev_is_named_arg {
161                        first_named_arg = i;
162                    }
163
164                    let name = make_ident(format!("{}{}", prefix, ident));
165                    named_arg_names.push(ident);
166                    name
167                } else {
168                    if prev_is_named_arg {
169                        return Err(crate::Error::spanned(
170                            arg.spans,
171                            "expected a named argument, \
172                             named arguments cannot be followed by positional arguments.",
173                        ));
174                    }
175
176                    make_ident(format!("{}{}", prefix, i))
177                };
178
179                let format_arg = if let Some(fmt_ident) = &arg.fmt_ident {
180                    FormatArg::WithFormatter {
181                        fmt_ident: fmt_ident.clone(),
182                        expr: arg.expr.clone(),
183                    }
184                } else {
185                    local_variables.push(LocalVariable {
186                        ident: var_name.clone(),
187                        expr: arg.expr.clone(),
188                    });
189
190                    FormatArg::WithLocal(var_name)
191                };
192
193                args.push(format_arg);
194
195                prev_is_named_arg = is_named_arg;
196            }
197        }
198
199        let mut unused_args = vec![true; args.len()];
200
201        let first_named_arg = first_named_arg;
202        let named_arg_names = named_arg_names;
203        let args = args;
204
205        let positional_args = &args[..first_named_arg];
206        let named_args = &args[first_named_arg..];
207
208        let fmt_str_components = unchecked_fargs.literal.list;
209
210        let expanded_into: Vec<ExpandInto> = {
211            let mut current_pos_arg = 0;
212            let mut get_variable_name = |param: FmtArg| -> ExpandInto {
213                let FmtArg {
214                    which_arg,
215                    formatting,
216                    rawness,
217                } = param;
218
219                let arg = match which_arg {
220                    WhichArg::Ident(ident) => {
221                        if let Some(pos) = named_arg_names.iter().position(|x| *x == ident) {
222                            unused_args[pos + first_named_arg] = false;
223                            &named_args[pos]
224                        } else {
225                            // `formatcp!("{FOO}")` assumes that FOO is a constant in scope
226                            return ExpandInto::Formatted(ExpandFormatted {
227                                local_variable: Ident::new(&ident, rawness.span()),
228                                format: formatting,
229                            });
230                        }
231                    }
232                    WhichArg::Positional(opt_pos) => {
233                        let pos = opt_pos.unwrap_or_else(|| {
234                            let pos = current_pos_arg;
235                            current_pos_arg += 1;
236                            pos
237                        });
238
239                        match positional_args.get(pos) {
240                            Some(arg) => {
241                                unused_args[pos] = false;
242                                arg
243                            }
244                            None => {
245                                res.push_err(crate::Error::new(
246                                    rawness.span(),
247                                    format!(
248                                        "attempting to use nonexistent  positional argument `{}`",
249                                        pos,
250                                    ),
251                                ));
252                                return ExpandInto::Formatted(ExpandFormatted {
253                                    local_variable: dummy_ident(),
254                                    format: formatting,
255                                });
256                            }
257                        }
258                    }
259                };
260
261                match arg {
262                    FormatArg::WithFormatter { fmt_ident, expr } => {
263                        ExpandInto::WithFormatter(ExpandWithFormatter {
264                            format: formatting,
265                            fmt_ident: fmt_ident.clone(),
266                            expr: expr.clone(),
267                        })
268                    }
269                    FormatArg::WithLocal(local_variable) => {
270                        ExpandInto::Formatted(ExpandFormatted {
271                            format: formatting,
272                            local_variable: local_variable.clone(),
273                        })
274                    }
275                }
276            };
277
278            fmt_str_components
279                .into_iter()
280                .map(|fmt_str_comp| match fmt_str_comp {
281                    FmtStrComponent::Str(str, str_rawness) => ExpandInto::Str(str, str_rawness),
282                    FmtStrComponent::Arg(arg) => get_variable_name(arg),
283                })
284                .collect()
285        };
286
287        for (i, (is_it_unused, (spans, ident))) in
288            unused_args.iter().zip(&arg_span_idents).enumerate()
289        {
290            if *is_it_unused {
291                let msg = if let Some(ident) = ident {
292                    format!("the '{}' argument is unused", ident)
293                } else {
294                    format!("argument number {} is unused", i)
295                };
296                res.push_err(crate::Error::spanned(*spans, msg));
297            }
298        }
299        res.take()?;
300
301        Ok(FormatArgs {
302            condition: None,
303            local_variables,
304            expanded_into,
305        })
306    }
307}
308
309////////////////////////////////////////////////
310
311impl MyParse for FormatIfArgs {
312    fn parse(input: ParseStream) -> Result<Self, crate::Error> {
313        let condition = ExprArg::parse(input)?;
314
315        let mut inner = FormatArgs::parse(input)?;
316        inner.condition = Some(condition);
317
318        Ok(Self { inner })
319    }
320}
321
322////////////////////////////////////////////////
323
324impl MyParse for WriteArgs {
325    fn parse(input: ParseStream) -> Result<Self, crate::Error> {
326        let prefix = Ident::new("__const_fmt_local_", Span::call_site());
327
328        let paren = input.parse_paren()?;
329
330        let mut content = ParseBuffer::new(paren.contents);
331
332        let (writer_expr, spans) =
333            content.parse_unwrap_tt(|content| Ok(content.parse_token_stream_and_span()))?;
334
335        let format_args = FormatArgs::parse_with(input, prefix)?;
336
337        Ok(Self {
338            writer_expr,
339            writer_span: spans.joined(),
340            format_args,
341        })
342    }
343}