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    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}
150
151#[derive(Clone, PartialEq, Eq, Debug)]
152pub enum FormatArgumentKind {
153    /// `format_args(…, arg)`
154    Normal,
155    /// `format_args(…, arg = 1)`
156    Named(Name),
157    /// `format_args("… {arg} …")`
158    Captured(Name),
159}
160
161// Only used in parse_args and report_invalid_references,
162// to indicate how a referred argument was used.
163#[derive(Clone, Copy, Debug, PartialEq, Eq)]
164enum PositionUsedAs {
165    Placeholder(Option<TextRange>),
166    Precision,
167    Width,
168}
169use PositionUsedAs::*;
170
171#[allow(clippy::unnecessary_lazy_evaluations)]
172pub(crate) fn parse(
173    s: &ast::String,
174    fmt_snippet: Option<String>,
175    mut args: FormatArgumentsCollector,
176    is_direct_literal: bool,
177    mut synth: impl FnMut(Name, Option<TextRange>) -> ExprId,
178    mut record_usage: impl FnMut(Name, Option<TextRange>),
179    call_ctx: SyntaxContext,
180) -> FormatArgs {
181    let Ok(text) = s.value() else {
182        return FormatArgs {
183            template: Default::default(),
184            arguments: args.finish(),
185            orphans: vec![],
186        };
187    };
188    let str_style = match s.quote_offsets() {
189        Some(offsets) => {
190            let raw = usize::from(offsets.quotes.0.len()) - 1;
191            // subtract 1 for the `r` prefix
192            (raw != 0).then(|| raw - 1)
193        }
194        None => None,
195    };
196    let mut parser =
197        parse::Parser::new(&text, str_style, fmt_snippet, false, parse::ParseMode::Format);
198
199    let mut pieces = Vec::new();
200    while let Some(piece) = parser.next() {
201        if !parser.errors.is_empty() {
202            break;
203        } else {
204            pieces.push(piece);
205        }
206    }
207    let is_source_literal = parser.is_source_literal;
208    if !parser.errors.is_empty() {
209        // FIXME: Diagnose
210        return FormatArgs {
211            template: Default::default(),
212            arguments: args.finish(),
213            orphans: vec![],
214        };
215    }
216
217    let to_span = |inner_span: std::ops::Range<usize>| {
218        is_source_literal.then(|| {
219            TextRange::new(inner_span.start.try_into().unwrap(), inner_span.end.try_into().unwrap())
220        })
221    };
222
223    let mut used = vec![false; args.explicit_args().len()];
224    let mut invalid_refs = Vec::new();
225    let mut numeric_references_to_named_arg = Vec::new();
226
227    enum ArgRef<'a> {
228        Index(usize),
229        Name(&'a str, Option<TextRange>),
230    }
231    let mut lookup_arg = |arg: ArgRef<'_>,
232                          span: Option<TextRange>,
233                          used_as: PositionUsedAs,
234                          kind: FormatArgPositionKind|
235     -> FormatArgPosition {
236        let index = match arg {
237            ArgRef::Index(index) => {
238                if let Some(arg) = args.by_index(index) {
239                    used[index] = true;
240                    if arg.kind.ident().is_some() {
241                        // This was a named argument, but it was used as a positional argument.
242                        numeric_references_to_named_arg.push((index, span, used_as));
243                    }
244                    Ok(index)
245                } else {
246                    // Doesn't exist as an explicit argument.
247                    invalid_refs.push((Either::Left(index), span, used_as, kind));
248                    Err(Either::Left(index))
249                }
250            }
251            ArgRef::Name(name, span) => {
252                let name = Name::new(name, call_ctx);
253                if let Some((index, _)) = args.by_name(&name) {
254                    record_usage(name, span);
255                    // Name found in `args`, so we resolve it to its index.
256                    if index < args.explicit_args().len() {
257                        // Mark it as used, if it was an explicit argument.
258                        used[index] = true;
259                    }
260                    Ok(index)
261                } else {
262                    // Name not found in `args`, so we add it as an implicitly captured argument.
263                    if !is_direct_literal {
264                        // For the moment capturing variables from format strings expanded from macros is
265                        // disabled (see RFC #2795)
266                        // FIXME: Diagnose
267                        invalid_refs.push((Either::Right(name.clone()), span, used_as, kind));
268                        Err(Either::Right(name))
269                    } else {
270                        record_usage(name.clone(), span);
271                        Ok(args.add(FormatArgument {
272                            kind: FormatArgumentKind::Captured(name.clone()),
273                            // FIXME: This is problematic, we might want to synthesize a dummy
274                            // expression proper and/or desugar these.
275                            expr: synth(name, span),
276                        }))
277                    }
278                }
279            }
280        };
281        FormatArgPosition { index, kind, span }
282    };
283
284    let mut template = Vec::new();
285    let mut unfinished_literal = String::new();
286    let mut placeholder_index = 0;
287
288    for piece in pieces {
289        match piece {
290            parse::Piece::Lit(s) => {
291                unfinished_literal.push_str(s);
292            }
293            parse::Piece::NextArgument(arg) => {
294                let parse::Argument { position, position_span, format } = *arg;
295                if !unfinished_literal.is_empty() {
296                    template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
297                    unfinished_literal.clear();
298                }
299
300                let span =
301                    parser.arg_places.get(placeholder_index).and_then(|s| to_span(s.clone()));
302                placeholder_index += 1;
303
304                let position_span = to_span(position_span);
305                let argument = match position {
306                    parse::ArgumentImplicitlyIs(i) => lookup_arg(
307                        ArgRef::Index(i),
308                        position_span,
309                        Placeholder(span),
310                        FormatArgPositionKind::Implicit,
311                    ),
312                    parse::ArgumentIs(i) => lookup_arg(
313                        ArgRef::Index(i),
314                        position_span,
315                        Placeholder(span),
316                        FormatArgPositionKind::Number,
317                    ),
318                    parse::ArgumentNamed(name) => lookup_arg(
319                        ArgRef::Name(name, position_span),
320                        position_span,
321                        Placeholder(span),
322                        FormatArgPositionKind::Named,
323                    ),
324                };
325
326                let alignment = match format.align {
327                    parse::AlignUnknown => None,
328                    parse::AlignLeft => Some(FormatAlignment::Left),
329                    parse::AlignRight => Some(FormatAlignment::Right),
330                    parse::AlignCenter => Some(FormatAlignment::Center),
331                };
332
333                let format_trait = match format.ty {
334                    "" => FormatTrait::Display,
335                    "?" => FormatTrait::Debug,
336                    "e" => FormatTrait::LowerExp,
337                    "E" => FormatTrait::UpperExp,
338                    "o" => FormatTrait::Octal,
339                    "p" => FormatTrait::Pointer,
340                    "b" => FormatTrait::Binary,
341                    "x" => FormatTrait::LowerHex,
342                    "X" => FormatTrait::UpperHex,
343                    _ => {
344                        // FIXME: Diagnose
345                        FormatTrait::Display
346                    }
347                };
348
349                let precision_span = format.precision_span.and_then(to_span);
350                let precision = match format.precision {
351                    parse::CountIs(n) => Some(FormatCount::Literal(n)),
352                    parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
353                        ArgRef::Name(name, to_span(name_span)),
354                        precision_span,
355                        Precision,
356                        FormatArgPositionKind::Named,
357                    ))),
358                    parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
359                        ArgRef::Index(i),
360                        precision_span,
361                        Precision,
362                        FormatArgPositionKind::Number,
363                    ))),
364                    parse::CountIsStar(i) => Some(FormatCount::Argument(lookup_arg(
365                        ArgRef::Index(i),
366                        precision_span,
367                        Precision,
368                        FormatArgPositionKind::Implicit,
369                    ))),
370                    parse::CountImplied => None,
371                };
372
373                let width_span = format.width_span.and_then(to_span);
374                let width = match format.width {
375                    parse::CountIs(n) => Some(FormatCount::Literal(n)),
376                    parse::CountIsName(name, name_span) => Some(FormatCount::Argument(lookup_arg(
377                        ArgRef::Name(name, to_span(name_span)),
378                        width_span,
379                        Width,
380                        FormatArgPositionKind::Named,
381                    ))),
382                    parse::CountIsParam(i) => Some(FormatCount::Argument(lookup_arg(
383                        ArgRef::Index(i),
384                        width_span,
385                        Width,
386                        FormatArgPositionKind::Number,
387                    ))),
388                    parse::CountIsStar(_) => unreachable!(),
389                    parse::CountImplied => None,
390                };
391
392                template.push(FormatArgsPiece::Placeholder(FormatPlaceholder {
393                    argument,
394                    span,
395                    format_trait,
396                    format_options: FormatOptions {
397                        fill: format.fill,
398                        alignment,
399                        sign: format.sign.map(|s| match s {
400                            parse::Sign::Plus => FormatSign::Plus,
401                            parse::Sign::Minus => FormatSign::Minus,
402                        }),
403                        alternate: format.alternate,
404                        zero_pad: format.zero_pad,
405                        debug_hex: format.debug_hex.map(|s| match s {
406                            parse::DebugHex::Lower => FormatDebugHex::Lower,
407                            parse::DebugHex::Upper => FormatDebugHex::Upper,
408                        }),
409                        precision,
410                        width,
411                    },
412                }));
413            }
414        }
415    }
416
417    if !unfinished_literal.is_empty() {
418        template.push(FormatArgsPiece::Literal(Symbol::intern(&unfinished_literal)));
419    }
420
421    if !invalid_refs.is_empty() {
422        // FIXME: Diagnose
423    }
424
425    let unused = used
426        .iter()
427        .enumerate()
428        .filter(|&(_, used)| !used)
429        .map(|(i, _)| {
430            let named = matches!(args.explicit_args()[i].kind, FormatArgumentKind::Named(_));
431            (args.explicit_args()[i].expr, named)
432        })
433        .collect::<Vec<_>>();
434
435    if !unused.is_empty() {
436        // FIXME: Diagnose
437    }
438
439    FormatArgs {
440        template: template.into_boxed_slice(),
441        arguments: args.finish(),
442        orphans: unused.into_iter().map(TupleExt::head).collect(),
443    }
444}
445
446#[derive(Clone, Debug, Default, Eq, PartialEq)]
447pub struct FormatArgumentsCollector {
448    arguments: Vec<FormatArgument>,
449    num_unnamed_args: usize,
450    num_explicit_args: usize,
451    names: Vec<(Name, usize)>,
452}
453
454impl FormatArgumentsCollector {
455    pub(crate) fn finish(self) -> FormatArguments {
456        FormatArguments {
457            arguments: self.arguments.into_boxed_slice(),
458            num_unnamed_args: self.num_unnamed_args,
459            num_explicit_args: self.num_explicit_args,
460            names: self.names.into_boxed_slice(),
461        }
462    }
463
464    pub fn add(&mut self, arg: FormatArgument) -> usize {
465        let index = self.arguments.len();
466        if let Some(name) = arg.kind.ident() {
467            self.names.push((name.clone(), index));
468        } else if self.names.is_empty() {
469            // Only count the unnamed args before the first named arg.
470            // (Any later ones are errors.)
471            self.num_unnamed_args += 1;
472        }
473        if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
474            // This is an explicit argument.
475            // Make sure that all arguments so far are explicit.
476            assert_eq!(
477                self.num_explicit_args,
478                self.arguments.len(),
479                "captured arguments must be added last"
480            );
481            self.num_explicit_args += 1;
482        }
483        self.arguments.push(arg);
484        index
485    }
486
487    pub fn by_name(&self, name: &Name) -> Option<(usize, &FormatArgument)> {
488        let &(_, i) = self.names.iter().find(|(n, _)| n == name)?;
489        Some((i, &self.arguments[i]))
490    }
491
492    pub fn by_index(&self, i: usize) -> Option<&FormatArgument> {
493        (i < self.num_explicit_args).then(|| &self.arguments[i])
494    }
495
496    pub fn unnamed_args(&self) -> &[FormatArgument] {
497        &self.arguments[..self.num_unnamed_args]
498    }
499
500    pub fn named_args(&self) -> &[FormatArgument] {
501        &self.arguments[self.num_unnamed_args..self.num_explicit_args]
502    }
503
504    pub fn explicit_args(&self) -> &[FormatArgument] {
505        &self.arguments[..self.num_explicit_args]
506    }
507
508    pub fn all_args(&self) -> &[FormatArgument] {
509        &self.arguments[..]
510    }
511
512    pub fn all_args_mut(&mut self) -> &mut Vec<FormatArgument> {
513        &mut self.arguments
514    }
515}
516
517impl FormatArgumentKind {
518    pub fn ident(&self) -> Option<&Name> {
519        match self {
520            Self::Normal => None,
521            Self::Named(id) => Some(id),
522            Self::Captured(id) => Some(id),
523        }
524    }
525}