Skip to content

Commit 4445a8f

Browse files
authored
Rollup merge of #93394 - m-ou-se:fix-93378, r=estebank
Don't allow {} to refer to implicit captures in format_args. Fixes #93378
2 parents e3c972e + cef9b47 commit 4445a8f

File tree

4 files changed

+65
-11
lines changed

4 files changed

+65
-11
lines changed

compiler/rustc_builtin_macros/src/format.rs

+24-11
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ enum ArgumentType {
2525

2626
enum Position {
2727
Exact(usize),
28+
Capture(usize),
2829
Named(Symbol),
2930
}
3031

@@ -49,6 +50,8 @@ struct Context<'a, 'b> {
4950
/// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]`
5051
/// * `names` (in JSON): `{"foo": 2}`
5152
args: Vec<P<ast::Expr>>,
53+
/// The number of arguments that were added by implicit capturing.
54+
num_captured_args: usize,
5255
/// Placeholder slot numbers indexed by argument.
5356
arg_types: Vec<Vec<usize>>,
5457
/// Unique format specs seen for each argument.
@@ -231,6 +234,11 @@ fn parse_args<'a>(
231234
}
232235

233236
impl<'a, 'b> Context<'a, 'b> {
237+
/// The number of arguments that were explicitly given.
238+
fn num_args(&self) -> usize {
239+
self.args.len() - self.num_captured_args
240+
}
241+
234242
fn resolve_name_inplace(&self, p: &mut parse::Piece<'_>) {
235243
// NOTE: the `unwrap_or` branch is needed in case of invalid format
236244
// arguments, e.g., `format_args!("{foo}")`.
@@ -345,7 +353,7 @@ impl<'a, 'b> Context<'a, 'b> {
345353
}
346354

347355
fn describe_num_args(&self) -> Cow<'_, str> {
348-
match self.args.len() {
356+
match self.num_args() {
349357
0 => "no arguments were given".into(),
350358
1 => "there is 1 argument".into(),
351359
x => format!("there are {} arguments", x).into(),
@@ -371,7 +379,7 @@ impl<'a, 'b> Context<'a, 'b> {
371379

372380
let count = self.pieces.len()
373381
+ self.arg_with_formatting.iter().filter(|fmt| fmt.precision_span.is_some()).count();
374-
if self.names.is_empty() && !numbered_position_args && count != self.args.len() {
382+
if self.names.is_empty() && !numbered_position_args && count != self.num_args() {
375383
e = self.ecx.struct_span_err(
376384
sp,
377385
&format!(
@@ -419,7 +427,7 @@ impl<'a, 'b> Context<'a, 'b> {
419427
if let Some(span) = fmt.precision_span {
420428
let span = self.fmtsp.from_inner(span);
421429
match fmt.precision {
422-
parse::CountIsParam(pos) if pos > self.args.len() => {
430+
parse::CountIsParam(pos) if pos > self.num_args() => {
423431
e.span_label(
424432
span,
425433
&format!(
@@ -462,7 +470,7 @@ impl<'a, 'b> Context<'a, 'b> {
462470
if let Some(span) = fmt.width_span {
463471
let span = self.fmtsp.from_inner(span);
464472
match fmt.width {
465-
parse::CountIsParam(pos) if pos > self.args.len() => {
473+
parse::CountIsParam(pos) if pos > self.num_args() => {
466474
e.span_label(
467475
span,
468476
&format!(
@@ -494,12 +502,15 @@ impl<'a, 'b> Context<'a, 'b> {
494502
/// Actually verifies and tracks a given format placeholder
495503
/// (a.k.a. argument).
496504
fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) {
505+
if let Exact(arg) = arg {
506+
if arg >= self.num_args() {
507+
self.invalid_refs.push((arg, self.curpiece));
508+
return;
509+
}
510+
}
511+
497512
match arg {
498-
Exact(arg) => {
499-
if self.args.len() <= arg {
500-
self.invalid_refs.push((arg, self.curpiece));
501-
return;
502-
}
513+
Exact(arg) | Capture(arg) => {
503514
match ty {
504515
Placeholder(_) => {
505516
// record every (position, type) combination only once
@@ -526,7 +537,7 @@ impl<'a, 'b> Context<'a, 'b> {
526537
match self.names.get(&name) {
527538
Some(&idx) => {
528539
// Treat as positional arg.
529-
self.verify_arg_type(Exact(idx), ty)
540+
self.verify_arg_type(Capture(idx), ty)
530541
}
531542
None => {
532543
// For the moment capturing variables from format strings expanded from macros is
@@ -541,9 +552,10 @@ impl<'a, 'b> Context<'a, 'b> {
541552
} else {
542553
self.fmtsp
543554
};
555+
self.num_captured_args += 1;
544556
self.args.push(self.ecx.expr_ident(span, Ident::new(name, span)));
545557
self.names.insert(name, idx);
546-
self.verify_arg_type(Exact(idx), ty)
558+
self.verify_arg_type(Capture(idx), ty)
547559
} else {
548560
let msg = format!("there is no argument named `{}`", name);
549561
let sp = if self.is_literal {
@@ -1051,6 +1063,7 @@ pub fn expand_preparsed_format_args(
10511063
let mut cx = Context {
10521064
ecx,
10531065
args,
1066+
num_captured_args: 0,
10541067
arg_types,
10551068
arg_unique_types,
10561069
names,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
fn main() {
2+
let a = "a";
3+
let b = "b";
4+
5+
println!("{a} {b} {} {} {c} {}", c = "c");
6+
//~^ ERROR: invalid reference to positional arguments 1 and 2 (there is 1 argument)
7+
8+
let n = 1;
9+
println!("{a:.n$} {b:.*}");
10+
//~^ ERROR: invalid reference to positional argument 0 (no arguments were given)
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: invalid reference to positional arguments 1 and 2 (there is 1 argument)
2+
--> $DIR/format-args-capture-issue-93378.rs:5:26
3+
|
4+
LL | println!("{a} {b} {} {} {c} {}", c = "c");
5+
| ^^ ^^
6+
|
7+
= note: positional arguments are zero-based
8+
9+
error: invalid reference to positional argument 0 (no arguments were given)
10+
--> $DIR/format-args-capture-issue-93378.rs:9:23
11+
|
12+
LL | println!("{a:.n$} {b:.*}");
13+
| ------- ^^^--^
14+
| | |
15+
| | this precision flag adds an extra required argument at position 0, which is why there are 3 arguments expected
16+
| this parameter corresponds to the precision flag
17+
|
18+
= note: positional arguments are zero-based
19+
= note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
20+
21+
error: aborting due to 2 previous errors
22+

src/test/ui/fmt/format-args-capture.rs

+8
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ fn main() {
55
named_argument_takes_precedence_to_captured();
66
formatting_parameters_can_be_captured();
77
capture_raw_strings_and_idents();
8+
repeated_capture();
89

910
#[cfg(panic = "unwind")]
1011
{
@@ -80,3 +81,10 @@ fn formatting_parameters_can_be_captured() {
8081
let s = format!("{x:-^width$.precision$}");
8182
assert_eq!(&s, "--7.000--");
8283
}
84+
85+
fn repeated_capture() {
86+
let a = 1;
87+
let b = 2;
88+
let s = format!("{a} {b} {a}");
89+
assert_eq!(&s, "1 2 1");
90+
}

0 commit comments

Comments
 (0)