Skip to content

Commit cf53fef

Browse files
committed
Turn format arguments Vec into its own struct.
With efficient lookup through a hash map.
1 parent 1406563 commit cf53fef

File tree

3 files changed

+146
-84
lines changed

3 files changed

+146
-84
lines changed

Diff for: compiler/rustc_builtin_macros/src/format.rs

+48-71
Original file line numberDiff line numberDiff line change
@@ -45,14 +45,14 @@ use PositionUsedAs::*;
4545
/// If parsing succeeds, the return value is:
4646
///
4747
/// ```text
48-
/// Some((fmtstr, parsed arguments))
48+
/// Ok((fmtstr, parsed arguments))
4949
/// ```
5050
fn parse_args<'a>(
5151
ecx: &mut ExtCtxt<'a>,
5252
sp: Span,
5353
tts: TokenStream,
54-
) -> PResult<'a, (P<Expr>, Vec<(P<Expr>, FormatArgKind)>)> {
55-
let mut args = Vec::<(P<Expr>, FormatArgKind)>::new();
54+
) -> PResult<'a, (P<Expr>, FormatArguments)> {
55+
let mut args = FormatArguments::new();
5656

5757
let mut p = ecx.new_parser_from_tts(tts);
5858

@@ -81,7 +81,6 @@ fn parse_args<'a>(
8181
};
8282

8383
let mut first = true;
84-
let mut named = false;
8584

8685
while p.token != token::Eof {
8786
if !p.eat(&token::Comma) {
@@ -113,40 +112,40 @@ fn parse_args<'a>(
113112
} // accept trailing commas
114113
match p.token.ident() {
115114
Some((ident, _)) if p.look_ahead(1, |t| *t == token::Eq) => {
116-
named = true;
117115
p.bump();
118116
p.expect(&token::Eq)?;
119-
let e = p.parse_expr()?;
120-
if let Some(prev) =
121-
args.iter().rev().map_while(|a| a.1.ident()).find(|n| n.name == ident.name)
122-
{
117+
let expr = p.parse_expr()?;
118+
if let Some((_, prev)) = args.by_name(ident.name) {
123119
ecx.struct_span_err(
124120
ident.span,
125121
&format!("duplicate argument named `{}`", ident),
126122
)
127-
.span_label(prev.span, "previously here")
123+
.span_label(prev.kind.ident().unwrap().span, "previously here")
128124
.span_label(ident.span, "duplicate argument")
129125
.emit();
130126
continue;
131127
}
132-
args.push((e, FormatArgKind::Named(ident)));
128+
args.add(FormatArgument { kind: FormatArgumentKind::Named(ident), expr });
133129
}
134130
_ => {
135-
let e = p.parse_expr()?;
136-
if named {
131+
let expr = p.parse_expr()?;
132+
if !args.named_args().is_empty() {
137133
let mut err = ecx.struct_span_err(
138-
e.span,
134+
expr.span,
139135
"positional arguments cannot follow named arguments",
140136
);
141-
err.span_label(e.span, "positional arguments must be before named arguments");
142-
for arg in &args {
143-
if let Some(name) = arg.1.ident() {
144-
err.span_label(name.span.to(arg.0.span), "named argument");
137+
err.span_label(
138+
expr.span,
139+
"positional arguments must be before named arguments",
140+
);
141+
for arg in args.named_args() {
142+
if let Some(name) = arg.kind.ident() {
143+
err.span_label(name.span.to(arg.expr.span), "named argument");
145144
}
146145
}
147146
err.emit();
148147
}
149-
args.push((e, FormatArgKind::Normal));
148+
args.add(FormatArgument { kind: FormatArgumentKind::Normal, expr });
150149
}
151150
}
152151
}
@@ -156,12 +155,9 @@ fn parse_args<'a>(
156155
pub fn make_format_args(
157156
ecx: &mut ExtCtxt<'_>,
158157
efmt: P<Expr>,
159-
mut args: Vec<(P<Expr>, FormatArgKind)>,
158+
mut args: FormatArguments,
160159
append_newline: bool,
161160
) -> Result<FormatArgs, ()> {
162-
let start_of_named_args =
163-
args.iter().position(|arg| arg.1.ident().is_some()).unwrap_or(args.len());
164-
165161
let msg = "format argument must be a string literal";
166162
let fmt_span = efmt.span;
167163
let (fmt_str, fmt_style, fmt_span) = match expr_to_spanned_string(ecx, efmt, msg) {
@@ -172,9 +168,9 @@ pub fn make_format_args(
172168
Ok(fmt) => fmt,
173169
Err(err) => {
174170
if let Some((mut err, suggested)) = err {
175-
let sugg_fmt = match args.len() {
171+
let sugg_fmt = match args.explicit_args().len() {
176172
0 => "{}".to_string(),
177-
_ => format!("{}{{}}", "{} ".repeat(args.len())),
173+
_ => format!("{}{{}}", "{} ".repeat(args.explicit_args().len())),
178174
};
179175
if !suggested {
180176
err.span_suggestion(
@@ -243,14 +239,14 @@ pub fn make_format_args(
243239
let captured_arg_span =
244240
fmt_span.from_inner(InnerSpan::new(err.span.start, err.span.end));
245241
if let Ok(arg) = ecx.source_map().span_to_snippet(captured_arg_span) {
246-
let span = match args[..start_of_named_args].last() {
247-
Some(arg) => arg.0.span,
242+
let span = match args.unnamed_args().last() {
243+
Some(arg) => arg.expr.span,
248244
None => fmt_span,
249245
};
250246
e.multipart_suggestion_verbose(
251247
"consider using a positional formatting argument instead",
252248
vec![
253-
(captured_arg_span, start_of_named_args.to_string()),
249+
(captured_arg_span, args.unnamed_args().len().to_string()),
254250
(span.shrink_to_hi(), format!(", {}", arg)),
255251
],
256252
Applicability::MachineApplicable,
@@ -267,8 +263,7 @@ pub fn make_format_args(
267263
})
268264
};
269265

270-
let num_explicit_args = args.len();
271-
let mut used = vec![false; num_explicit_args];
266+
let mut used = vec![false; args.explicit_args().len()];
272267
let mut invalid_refs = Vec::new();
273268
let mut numeric_refences_to_named_arg = Vec::new();
274269

@@ -285,32 +280,24 @@ pub fn make_format_args(
285280
-> FormatArgPosition {
286281
let index = match arg {
287282
Index(index) => {
288-
match args.get(index) {
289-
Some((_, FormatArgKind::Normal)) => {
290-
used[index] = true;
291-
Ok(index)
292-
}
293-
Some((_, FormatArgKind::Named(_))) => {
294-
used[index] = true;
283+
if let Some(arg) = args.by_index(index) {
284+
used[index] = true;
285+
if arg.kind.ident().is_some() {
286+
// This was a named argument, but it was used as a positional argument.
295287
numeric_refences_to_named_arg.push((index, span, used_as));
296-
Ok(index)
297-
}
298-
Some((_, FormatArgKind::Captured(_))) | None => {
299-
// Doesn't exist as an explicit argument.
300-
invalid_refs.push((index, span, used_as, kind));
301-
Err(index)
302288
}
289+
Ok(index)
290+
} else {
291+
// Doesn't exist as an explicit argument.
292+
invalid_refs.push((index, span, used_as, kind));
293+
Err(index)
303294
}
304295
}
305296
Name(name, span) => {
306297
let name = Symbol::intern(name);
307-
if let Some(i) = args[start_of_named_args..]
308-
.iter()
309-
.position(|arg| arg.1.ident().is_some_and(|id| id.name == name))
310-
{
311-
// Name found in `args`, so we resolve it to its index in that Vec.
312-
let index = start_of_named_args + i;
313-
if !matches!(args[index].1, FormatArgKind::Captured(_)) {
298+
if let Some((index, _)) = args.by_name(name) {
299+
// Name found in `args`, so we resolve it to its index.
300+
if index < args.explicit_args().len() {
314301
// Mark it as used, if it was an explicit argument.
315302
used[index] = true;
316303
}
@@ -319,7 +306,7 @@ pub fn make_format_args(
319306
// Name not found in `args`, so we add it as an implicitly captured argument.
320307
let span = span.unwrap_or(fmt_span);
321308
let ident = Ident::new(name, span);
322-
let arg = if is_literal {
309+
let expr = if is_literal {
323310
ecx.expr_ident(span, ident)
324311
} else {
325312
// For the moment capturing variables from format strings expanded from macros is
@@ -330,8 +317,7 @@ pub fn make_format_args(
330317
.emit();
331318
DummyResult::raw_expr(span, true)
332319
};
333-
args.push((arg, FormatArgKind::Captured(ident)));
334-
Ok(args.len() - 1)
320+
Ok(args.add(FormatArgument { kind: FormatArgumentKind::Captured(ident), expr }))
335321
}
336322
}
337323
};
@@ -466,35 +452,27 @@ pub fn make_format_args(
466452
}
467453

468454
if !invalid_refs.is_empty() {
469-
report_invalid_references(
470-
ecx,
471-
&invalid_refs,
472-
&template,
473-
fmt_span,
474-
num_explicit_args,
475-
&args,
476-
parser,
477-
);
455+
report_invalid_references(ecx, &invalid_refs, &template, fmt_span, &args, parser);
478456
}
479457

480458
let unused = used
481459
.iter()
482460
.enumerate()
483461
.filter(|&(_, used)| !used)
484462
.map(|(i, _)| {
485-
let msg = if let FormatArgKind::Named(_) = args[i].1 {
463+
let msg = if let FormatArgumentKind::Named(_) = args.explicit_args()[i].kind {
486464
"named argument never used"
487465
} else {
488466
"argument never used"
489467
};
490-
(args[i].0.span, msg)
468+
(args.explicit_args()[i].expr.span, msg)
491469
})
492470
.collect::<Vec<_>>();
493471

494472
if !unused.is_empty() {
495473
// If there's a lot of unused arguments,
496474
// let's check if this format arguments looks like another syntax (printf / shell).
497-
let detect_foreign_fmt = unused.len() > num_explicit_args / 2;
475+
let detect_foreign_fmt = unused.len() > args.explicit_args().len() / 2;
498476
report_missing_placeholders(ecx, unused, detect_foreign_fmt, str_style, fmt_str, fmt_span);
499477
}
500478

@@ -511,7 +489,7 @@ pub fn make_format_args(
511489
}
512490
Width => (span, span),
513491
};
514-
let arg_name = args[index].1.ident().unwrap();
492+
let arg_name = args.explicit_args()[index].kind.ident().unwrap();
515493
ecx.buffered_early_lint.push(BufferedEarlyLint {
516494
span: arg_name.span.into(),
517495
msg: format!("named argument `{}` is not used by name", arg_name.name).into(),
@@ -695,11 +673,10 @@ fn report_invalid_references(
695673
invalid_refs: &[(usize, Option<Span>, PositionUsedAs, FormatArgPositionKind)],
696674
template: &[FormatArgsPiece],
697675
fmt_span: Span,
698-
num_explicit_args: usize,
699-
args: &[(P<Expr>, FormatArgKind)],
676+
args: &FormatArguments,
700677
parser: parse::Parser<'_>,
701678
) {
702-
let num_args_desc = match num_explicit_args {
679+
let num_args_desc = match args.explicit_args().len() {
703680
0 => "no arguments were given".to_string(),
704681
1 => "there is 1 argument".to_string(),
705682
n => format!("there are {} arguments", n),
@@ -785,8 +762,8 @@ fn report_invalid_references(
785762
num_args_desc,
786763
),
787764
);
788-
for (arg, _) in &args[..num_explicit_args] {
789-
e.span_label(arg.span, "");
765+
for arg in args.explicit_args() {
766+
e.span_label(arg.expr.span, "");
790767
}
791768
// Point out `{:.*}` placeholders: those take an extra argument.
792769
let mut has_precision_star = false;

Diff for: compiler/rustc_builtin_macros/src/format/ast.rs

+84-3
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use rustc_ast::ptr::P;
22
use rustc_ast::Expr;
3+
use rustc_data_structures::fx::FxHashMap;
34
use rustc_span::symbol::{Ident, Symbol};
45
use rustc_span::Span;
56

@@ -42,17 +43,97 @@ use rustc_span::Span;
4243
pub struct FormatArgs {
4344
pub span: Span,
4445
pub template: Vec<FormatArgsPiece>,
45-
pub arguments: Vec<(P<Expr>, FormatArgKind)>,
46+
pub arguments: FormatArguments,
4647
}
4748

49+
/// A piece of a format template string.
50+
///
51+
/// E.g. "hello" or "{name}".
4852
#[derive(Clone, Debug)]
4953
pub enum FormatArgsPiece {
5054
Literal(Symbol),
5155
Placeholder(FormatPlaceholder),
5256
}
5357

58+
/// The arguments to format_args!().
59+
///
60+
/// E.g. `1, 2, name="ferris", n=3`,
61+
/// but also implicit captured arguments like `x` in `format_args!("{x}")`.
62+
#[derive(Clone, Debug)]
63+
pub struct FormatArguments {
64+
arguments: Vec<FormatArgument>,
65+
num_unnamed_args: usize,
66+
num_explicit_args: usize,
67+
names: FxHashMap<Symbol, usize>,
68+
}
69+
70+
impl FormatArguments {
71+
pub fn new() -> Self {
72+
Self {
73+
arguments: Vec::new(),
74+
names: FxHashMap::default(),
75+
num_unnamed_args: 0,
76+
num_explicit_args: 0,
77+
}
78+
}
79+
80+
pub fn add(&mut self, arg: FormatArgument) -> usize {
81+
let index = self.arguments.len();
82+
if let Some(name) = arg.kind.ident() {
83+
self.names.insert(name.name, index);
84+
} else if self.names.is_empty() {
85+
// Only count the unnamed args before the first named arg.
86+
// (Any later ones are errors.)
87+
self.num_unnamed_args += 1;
88+
}
89+
if !matches!(arg.kind, FormatArgumentKind::Captured(..)) {
90+
// This is an explicit argument.
91+
// Make sure that all arguments so far are explcit.
92+
assert_eq!(
93+
self.num_explicit_args,
94+
self.arguments.len(),
95+
"captured arguments must be added last"
96+
);
97+
self.num_explicit_args += 1;
98+
}
99+
self.arguments.push(arg);
100+
index
101+
}
102+
103+
pub fn by_name(&self, name: Symbol) -> Option<(usize, &FormatArgument)> {
104+
let i = *self.names.get(&name)?;
105+
Some((i, &self.arguments[i]))
106+
}
107+
108+
pub fn by_index(&self, i: usize) -> Option<&FormatArgument> {
109+
(i < self.num_explicit_args).then(|| &self.arguments[i])
110+
}
111+
112+
pub fn unnamed_args(&self) -> &[FormatArgument] {
113+
&self.arguments[..self.num_unnamed_args]
114+
}
115+
116+
pub fn named_args(&self) -> &[FormatArgument] {
117+
&self.arguments[self.num_unnamed_args..self.num_explicit_args]
118+
}
119+
120+
pub fn explicit_args(&self) -> &[FormatArgument] {
121+
&self.arguments[..self.num_explicit_args]
122+
}
123+
124+
pub fn into_vec(self) -> Vec<FormatArgument> {
125+
self.arguments
126+
}
127+
}
128+
129+
#[derive(Clone, Debug)]
130+
pub struct FormatArgument {
131+
pub kind: FormatArgumentKind,
132+
pub expr: P<Expr>,
133+
}
134+
54135
#[derive(Clone, Debug)]
55-
pub enum FormatArgKind {
136+
pub enum FormatArgumentKind {
56137
/// `format_args(…, arg)`
57138
Normal,
58139
/// `format_args(…, arg = 1)`
@@ -61,7 +142,7 @@ pub enum FormatArgKind {
61142
Captured(Ident),
62143
}
63144

64-
impl FormatArgKind {
145+
impl FormatArgumentKind {
65146
pub fn ident(&self) -> Option<Ident> {
66147
match self {
67148
&Self::Normal => None,

0 commit comments

Comments
 (0)