Skip to main content

hir_def/hir/
format_args.rs

1//! Parses `format_args` input.
2
3use either::Either;
4use hir_expand::name::Name;
5use intern::Symbol;
6use rustc_parse_format as parse;
7use span::SyntaxContext;
8use stdx::TupleExt;
9use syntax::{
10    AstPtr, TextRange,
11    ast::{self, IsString},
12};
13
14use crate::hir::ExprId;
15
16#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct FormatArgs {
18    pub template: Box<[FormatArgsPiece]>,
19    pub arguments: FormatArguments,
20    pub orphans: Vec<ExprId>,
21}
22
23#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct FormatArguments {
25    pub arguments: Box<[FormatArgument]>,
26    pub num_unnamed_args: usize,
27    pub num_explicit_args: usize,
28    pub names: Box<[(Name, usize)]>,
29}
30
31#[derive(Debug, Clone, PartialEq, Eq)]
32pub enum FormatArgsPiece {
33    Literal(Symbol),
34    Placeholder(FormatPlaceholder),
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
38pub struct FormatPlaceholder {
39    /// Index into [`FormatArgs::arguments`].
40    pub argument: FormatArgPosition,
41    /// The span inside the format string for the full `{…}` placeholder.
42    pub span: Option<TextRange>,
43    /// `{}`, `{:?}`, or `{:x}`, etc.
44    pub format_trait: FormatTrait,
45    /// `{}` or `{:.5}` or `{:-^20}`, etc.
46    pub format_options: FormatOptions,
47}
48
49#[derive(Debug, Clone, PartialEq, Eq)]
50pub struct FormatArgPosition {
51    /// Which argument this position refers to (Ok),
52    /// or would've referred to if it existed (Err).
53    pub index: Result<usize, Either<usize, Name>>,
54    /// What kind of position this is. See [`FormatArgPositionKind`].
55    pub kind: FormatArgPositionKind,
56    /// The span of the name or number.
57    pub span: Option<TextRange>,
58}
59
60#[derive(Copy, Debug, Clone, PartialEq, Eq)]
61pub enum FormatArgPositionKind {
62    /// `{}` or `{:.*}`
63    Implicit,
64    /// `{1}` or `{:1$}` or `{:.1$}`
65    Number,
66    /// `{a}` or `{:a$}` or `{:.a$}`
67    Named,
68}
69
70#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq)]
71pub enum FormatTrait {
72    /// `{}`
73    Display,
74    /// `{:?}`
75    Debug,
76    /// `{:e}`
77    LowerExp,
78    /// `{:E}`
79    UpperExp,
80    /// `{:o}`
81    Octal,
82    /// `{:p}`
83    Pointer,
84    /// `{:b}`
85    Binary,
86    /// `{:x}`
87    LowerHex,
88    /// `{:X}`
89    UpperHex,
90}
91
92#[derive(Clone, Default, Debug, PartialEq, Eq)]
93pub struct FormatOptions {
94    /// The width. E.g. `{:5}` or `{:width$}`.
95    pub width: Option<FormatCount>,
96    /// The precision. E.g. `{:.5}` or `{:.precision$}`.
97    pub precision: Option<FormatCount>,
98    /// The alignment. E.g. `{:>}` or `{:<}` or `{:^}`.
99    pub alignment: Option<FormatAlignment>,
100    /// The fill character. E.g. the `.` in `{:.>10}`.
101    pub fill: Option<char>,
102    /// The `+` or `-` flag.
103    pub sign: Option<FormatSign>,
104    /// The `#` flag.
105    pub alternate: bool,
106    /// The `0` flag. E.g. the `0` in `{:02x}`.
107    pub zero_pad: bool,
108    /// The `x` or `X` flag (for `Debug` only). E.g. the `x` in `{:x?}`.
109    pub debug_hex: Option<FormatDebugHex>,
110}
111#[derive(Copy, Clone, Debug, PartialEq, Eq)]
112pub enum FormatSign {
113    /// The `+` flag.
114    Plus,
115    /// The `-` flag.
116    Minus,
117}
118
119#[derive(Copy, Clone, Debug, PartialEq, Eq)]
120pub enum FormatDebugHex {
121    /// The `x` flag in `{:x?}`.
122    Lower,
123    /// The `X` flag in `{:X?}`.
124    Upper,
125}
126
127#[derive(Copy, Clone, Debug, PartialEq, Eq)]
128pub enum FormatAlignment {
129    /// `{:<}`
130    Left,
131    /// `{:>}`
132    Right,
133    /// `{:^}`
134    Center,
135}
136
137#[derive(Clone, Debug, PartialEq, Eq)]
138pub enum FormatCount {
139    /// `{:5}` or `{:.5}`
140    Literal(u16),
141    /// `{:.*}`, `{:.5$}`, or `{:a$}`, etc.
142    Argument(FormatArgPosition),
143}
144
145#[derive(Debug, Clone, PartialEq, Eq)]
146pub struct FormatArgument {
147    pub kind: FormatArgumentKind,
148    pub expr: ExprId,
149    pub syntax: Option<AstPtr<ast::Expr>>,
150}
151
152#[derive(Clone, PartialEq, Eq, Debug)]
153pub enum FormatArgumentKind {
154    /// `format_args(…, arg)`
155    Normal,
156    /// `format_args(…, arg = 1)`
157    Named(Name),
158    /// `format_args("… {arg} …")`
159    Captured(Name),
160}
161
162// Only used in parse_args and report_invalid_references,
163// to indicate how a referred argument was used.
164#[derive(Clone, Copy, Debug, PartialEq, Eq)]
165enum PositionUsedAs {
166    Placeholder(Option<TextRange>),
167    Precision,
168    Width,
169}
170use PositionUsedAs::*;
171
172pub(crate) fn parse(
173    s: &ast::String,
174    string_ptr: AstPtr<ast::Expr>,
175    fmt_snippet: Option<String>,
176    mut args: FormatArgumentsCollector,
177    is_direct_literal: bool,
178    mut synth: impl FnMut(Name, Option<TextRange>) -> ExprId,
179    mut record_usage: impl FnMut(Name, Option<TextRange>),
180    call_ctx: SyntaxContext,
181) -> FormatArgs {
182    let Ok(text) = s.value() else {
183        return FormatArgs {
184            template: Default::default(),
185            arguments: args.finish(),
186            orphans: vec![],
187        };
188    };
189    let str_style = match s.quote_offsets() {
190        Some(offsets) => {
191            let raw = usize::from(offsets.quotes.0.len()) - 1;
192            // subtract 1 for the `r` prefix
193            (raw != 0).then(|| raw - 1)
194        }
195        None => None,
196    };
197    let mut parser =
198        parse::Parser::new(&text, str_style, fmt_snippet, false, parse::ParseMode::Format);
199
200    let mut pieces = Vec::new();
201    while let Some(piece) = parser.next() {
202        if !parser.errors.is_empty() {
203            break;
204        } else {
205            pieces.push(piece);
206        }
207    }
208    let is_source_literal = parser.is_source_literal;
209    if !parser.errors.is_empty() {
210        // FIXME: Diagnose
211        return FormatArgs {
212            template: Default::default(),
213            arguments: args.finish(),
214            orphans: vec![],
215        };
216    }
217
218    let to_span = |inner_span: std::ops::Range<usize>| {
219        is_source_literal.then(|| {
220            TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap())
221        })
222    };
223
224    let mut used = vec![false; args.explicit_args().len()];
225    let mut invalid_refs = Vec::new();
226    let mut numeric_references_to_named_arg = Vec::new();
227
228    enum ArgRef<'a> {
229        Index(usize),
230        Name(&'a str, Option<TextRange>),
231    }
232    let mut lookup_arg = |arg: ArgRef<'_>,
233                          span: Option<TextRange>,
234                          used_as: PositionUsedAs,
235                          kind: FormatArgPositionKind|
236     -> FormatArgPosition {
237        let index = match arg {
238            ArgRef::Index(index) => {
239                if let Some(arg) = args.by_index(index) {
240                    used[index] = true;
241                    if arg.kind.ident().is_some() {
242                        // This was a named argument, but it was used as a positional argument.
243                        numeric_references_to_named_arg.push((index, span, used_as));
244                    }
245                    Ok(index)
246                } else {
247                    // Doesn't exist as an explicit argument.
248                    invalid_refs.push((Either::Left(index), span, used_as, kind));
249                    Err(Either::Left(index))
250                }
251            }
252            ArgRef::Name(name, span) => {
253                let name = Name::new(name, call_ctx);
254                if let Some((index, _)) = args.by_name(&name) {
255                    record_usage(name, span);
256                    // Name found in `args`, so we resolve it to its index.
257                    if index < args.explicit_args().len() {
258                        // Mark it as used, if it was an explicit argument.
259                        used[index] = true;
260                    }
261                    Ok(index)
262                } else {
263                    // Name not found in `args`, so we add it as an implicitly captured argument.
264                    if !is_direct_literal {
265                        // For the moment capturing variables from format strings expanded from macros is
266                        // disabled (see RFC #2795)
267                        // FIXME: Diagnose
268                        invalid_refs.push((Either::Right(name.clone()), span, used_as, kind));
269                        Err(Either::Right(name))
270                    } else {
271                        record_usage(name.clone(), span);
272                        Ok(args.add(FormatArgument {
273                            kind: FormatArgumentKind::Captured(name.clone()),
274                            // FIXME: This is problematic, we might want to synthesize a dummy
275                            // expression proper and/or desugar these.
276                            expr: synth(name, span),
277                            // FIXME: This will lead us to show failed trait bounds (e.g. `T: Display`)
278                            // on the whole template string for implicit arguments instead of just their name.
279                            // Fixing this is hard since we don't have an `AstPtr` for the arg, and it's
280                            // only part of an expression.
281                            syntax: Some(string_ptr),
282                        }))
283                    }
284                }
285            }
286        };
287        FormatArgPosition { index, kind, span }
288    };
289
290    let mut template = Vec::new();
291    let mut unfinished_literal = String::new();
292    let mut placeholder_index = 0;
293
294    for piece in pieces {
295        match piece {
296            parse::Piece::Lit(s) => {
297                unfinished_literal.push_str(s);
298            }
299            parse::Piece::NextArgument(arg) => {
300                let parse::Argument { position, position_span, format } = *arg;
301                if !unfinished_literal.is_empty() {
302                    template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
303                    unfinished_literal.clear();
304                }
305
306                let span =
307                    parser.arg_places.get(placeholder_index).and_then(|s| to_span(s.clone()));
308                placeholder_index += 1;
309
310                let position_span = to_span(position_span);
311                let argument = match position {
312                    parse::ArgumentImplicitlyIs(i) => lookup_arg(
313                        ArgRef::Index(i),
314                        position_span,
315                        Placeholder(span),
316                        FormatArgPositionKind::Implicit,
317                    ),
318                    parse::ArgumentIs(i) => lookup_arg(
319                        ArgRef::Index(i),
320                        position_span,
321                        Placeholder(span),
322                        FormatArgPositionKind::Number,
323                    ),
324                    parse::ArgumentNamed(name) => lookup_arg(
325                        ArgRef::Name(name, position_span),
326                        position_span,
327                        Placeholder(span),
328                        FormatArgPositionKind::Named,
329                    ),
330                };
331
332                let alignment = match format.align {
333                    parse::AlignUnknown => None,
334                    parse::AlignLeft => Some(FormatAlignment::Left),
335                    parse::AlignRight => Some(FormatAlignment::Right),
336                    parse::AlignCenter => Some(FormatAlignment::Center),
337                };
338
339                let format_trait = match format.ty {
340                    "" => FormatTrait::Display,
341                    "?" => FormatTrait::Debug,
342                    "e" => FormatTrait::LowerExp,
343                    "E" => FormatTrait::UpperExp,
344                    "o" => FormatTrait::Octal,
345                    "p" => FormatTrait::Pointer,
346                    "b" => FormatTrait::Binary,
347                    "x" => FormatTrait::LowerHex,
348                    "X" => FormatTrait::UpperHex,
349                    _ => {
350                        // FIXME: Diagnose
351                        FormatTrait::Display
352                    }
353                };
354
355                let precision_span = format.precision_span.and_then(to_span);
356                let precision = match format.precision {
357                    parse::CountIs(n) => Some(FormatCount::Literal(n)),
358                    parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
359                        ArgRef::Name(name, to_span(name_span)),
360                        precision_span,
361                        Precision,
362                        FormatArgPositionKind::Named,
363                    ))),
364                    parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
365                        ArgRef::Index(i),
366                        precision_span,
367                        Precision,
368                        FormatArgPositionKind::Number,
369                    ))),
370                    parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
371                        ArgRef::Index(i),
372                        precision_span,
373                        Precision,
374                        FormatArgPositionKind::Implicit,
375                    ))),
376                    parse::CountImplied => None,
377                };
378
379                let width_span = format.width_span.and_then(to_span);
380                let width = match format.width {
381                    parse::CountIs(n) => Some(FormatCount::Literal(n)),
382                    parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
383                        ArgRef::Name(name, to_span(name_span)),
384                        width_span,
385                        Width,
386                        FormatArgPositionKind::Named,
387                    ))),
388                    parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
389                        ArgRef::Index(i),
390                        width_span,
391                        Width,
392                        FormatArgPositionKind::Number,
393                    ))),
394                    parse::CountIsStar(_) => unreachable!(),
395                    parse::CountImplied => None,
396                };
397
398                template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
399                    argument,
400                    span,
401                    format_trait,
402                    format_options: FormatOptions {
403                        fill: format.fill,
404                        alignment,
405                        sign: format.sign.map(|s| match s {
406                            parse::Sign::Plus => FormatSign::Plus,
407                            parse::Sign::Minus => FormatSign::Minus,
408                        }),
409                        alternate: format.alternate,
410                        zero_pad: format.zero_pad,
411                        debug_hex: format.debug_hex.map(|s| match s {
412                            parse::DebugHex::Lower => FormatDebugHex::Lower,
413                            parse::DebugHex::Upper => FormatDebugHex::Upper,
414                        }),
415                        precision,
416                        width,
417                    },
418                }));
419            }
420        }
421    }
422
423    if !unfinished_literal.is_empty() {
424        template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
425    }
426
427    if !invalid_refs.is_empty() {
428        // FIXME: Diagnose
429    }
430
431    let unused = used
432        .iter()
433        .enumerate()
434        .filter(|&(_, used)| !used)
435        .map(|(i, _)| {
436            let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
437            (args.explicit_args()[i].expr, named)
438        })
439        .collect::<Vec<_>>();
440
441    if !unused.is_empty() {
442        // FIXME: Diagnose
443    }
444
445    FormatArgs {
446        template: template.into_boxed_slice(),
447        arguments: args.finish(),
448        orphans: unused.into_iter().map(TupleExt::head).collect(),
449    }
450}
451
452#[derive(Clone, Debug, Default, Eq, PartialEq)]
453pub struct FormatArgumentsCollector {
454    arguments: Vec<FormatArgument>,
455    num_unnamed_args: usize,
456    num_explicit_args: usize,
457    names: Vec<(Name, usize)>,
458}
459
460impl FormatArgumentsCollector {
461    pub(crate) fn finish(self) -> FormatArguments {
462        FormatArguments {
463            arguments: self.arguments.into_boxed_slice(),
464            num_unnamed_args: self.num_unnamed_args,
465            num_explicit_args: self.num_explicit_args,
466            names: self.names.into_boxed_slice(),
467        }
468    }
469
470    pub fn add(&mut self, arg: FormatArgument) -> usize {
471        let index = self.arguments.len();
472        if let Some(name) = arg.kind.ident() {
473            self.names.push((name.clone(), index));
474        } else if self.names.is_empty() {
475            // Only count the unnamed args before the first named arg.
476            // (Any later ones are errors.)
477            self.num_unnamed_args += 1;
478        }
479        if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
480            // This is an explicit argument.
481            // Make sure that all arguments so far are explicit.
482            assert_eq!(
483                self.num_explicit_args,
484                self.arguments.len(),
485                "captured arguments must be added last"
486            );
487            self.num_explicit_args += 1;
488        }
489        self.arguments.push(arg);
490        index
491    }
492
493    pub fn by_name(&self, name: &Name) -> Option<(usize, &FormatArgument)> {
494        let &(_, i) = self.names.iter().find(|(n, _)| n == name)?;
495        Some((i, &self.arguments[i]))
496    }
497
498    pub fn by_index(&self, i: usize) -> Option<&FormatArgument> {
499        (i < self.num_explicit_args).then(|| &self.arguments[i])
500    }
501
502    pub fn unnamed_args(&self) -> &[FormatArgument] {
503        &self.arguments[..self.num_unnamed_args]
504    }
505
506    pub fn named_args(&self) -> &[FormatArgument] {
507        &self.arguments[self.num_unnamed_args..self.num_explicit_args]
508    }
509
510    pub fn explicit_args(&self) -> &[FormatArgument] {
511        &self.arguments[..self.num_explicit_args]
512    }
513
514    pub fn all_args(&self) -> &[FormatArgument] {
515        &self.arguments[..]
516    }
517
518    pub fn all_args_mut(&mut self) -> &mut Vec<FormatArgument> {
519        &mut self.arguments
520    }
521}
522
523impl FormatArgumentKind {
524    pub fn ident(&self) -> Option<&Name> {
525        match self {
526            Self::Normal => None,
527            Self::Named(id) => Some(id),
528            Self::Captured(id) => Some(id),
529        }
530    }
531}