Skip to content

Commit 120e33f

Browse files
m-ou-seehuss
authored andcommitted
Rollup merge of rust-lang#93394 - m-ou-se:fix-93378, r=estebank
Don't allow {} to refer to implicit captures in format_args. Fixes rust-lang#93378
1 parent 560b4f3 commit 120e33f

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
@@ -23,6 +23,7 @@ enum ArgumentType {
2323

2424
enum Position {
2525
Exact(usize),
26+
Capture(usize),
2627
Named(Symbol),
2728
}
2829

@@ -47,6 +48,8 @@ struct Context<'a, 'b> {
4748
/// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]`
4849
/// * `names` (in JSON): `{"foo": 2}`
4950
args: Vec<P<ast::Expr>>,
51+
/// The number of arguments that were added by implicit capturing.
52+
num_captured_args: usize,
5053
/// Placeholder slot numbers indexed by argument.
5154
arg_types: Vec<Vec<usize>>,
5255
/// Unique format specs seen for each argument.
@@ -229,6 +232,11 @@ fn parse_args<'a>(
229232
}
230233

231234
impl<'a, 'b> Context<'a, 'b> {
235+
/// The number of arguments that were explicitly given.
236+
fn num_args(&self) -> usize {
237+
self.args.len() - self.num_captured_args
238+
}
239+
232240
fn resolve_name_inplace(&self, p: &mut parse::Piece<'_>) {
233241
// NOTE: the `unwrap_or` branch is needed in case of invalid format
234242
// arguments, e.g., `format_args!("{foo}")`.
@@ -343,7 +351,7 @@ impl<'a, 'b> Context<'a, 'b> {
343351
}
344352

345353
fn describe_num_args(&self) -> Cow<'_, str> {
346-
match self.args.len() {
354+
match self.num_args() {
347355
0 => "no arguments were given".into(),
348356
1 => "there is 1 argument".into(),
349357
x => format!("there are {} arguments", x).into(),
@@ -369,7 +377,7 @@ impl<'a, 'b> Context<'a, 'b> {
369377

370378
let count = self.pieces.len()
371379
+ self.arg_with_formatting.iter().filter(|fmt| fmt.precision_span.is_some()).count();
372-
if self.names.is_empty() && !numbered_position_args && count != self.args.len() {
380+
if self.names.is_empty() && !numbered_position_args && count != self.num_args() {
373381
e = self.ecx.struct_span_err(
374382
sp,
375383
&format!(
@@ -417,7 +425,7 @@ impl<'a, 'b> Context<'a, 'b> {
417425
if let Some(span) = fmt.precision_span {
418426
let span = self.fmtsp.from_inner(span);
419427
match fmt.precision {
420-
parse::CountIsParam(pos) if pos > self.args.len() => {
428+
parse::CountIsParam(pos) if pos > self.num_args() => {
421429
e.span_label(
422430
span,
423431
&format!(
@@ -460,7 +468,7 @@ impl<'a, 'b> Context<'a, 'b> {
460468
if let Some(span) = fmt.width_span {
461469
let span = self.fmtsp.from_inner(span);
462470
match fmt.width {
463-
parse::CountIsParam(pos) if pos > self.args.len() => {
471+
parse::CountIsParam(pos) if pos > self.num_args() => {
464472
e.span_label(
465473
span,
466474
&format!(
@@ -492,12 +500,15 @@ impl<'a, 'b> Context<'a, 'b> {
492500
/// Actually verifies and tracks a given format placeholder
493501
/// (a.k.a. argument).
494502
fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) {
503+
if let Exact(arg) = arg {
504+
if arg >= self.num_args() {
505+
self.invalid_refs.push((arg, self.curpiece));
506+
return;
507+
}
508+
}
509+
495510
match arg {
496-
Exact(arg) => {
497-
if self.args.len() <= arg {
498-
self.invalid_refs.push((arg, self.curpiece));
499-
return;
500-
}
511+
Exact(arg) | Capture(arg) => {
501512
match ty {
502513
Placeholder(_) => {
503514
// record every (position, type) combination only once
@@ -524,7 +535,7 @@ impl<'a, 'b> Context<'a, 'b> {
524535
match self.names.get(&name) {
525536
Some(&idx) => {
526537
// Treat as positional arg.
527-
self.verify_arg_type(Exact(idx), ty)
538+
self.verify_arg_type(Capture(idx), ty)
528539
}
529540
None => {
530541
// For the moment capturing variables from format strings expanded from macros is
@@ -539,9 +550,10 @@ impl<'a, 'b> Context<'a, 'b> {
539550
} else {
540551
self.fmtsp
541552
};
553+
self.num_captured_args += 1;
542554
self.args.push(self.ecx.expr_ident(span, Ident::new(name, span)));
543555
self.names.insert(name, idx);
544-
self.verify_arg_type(Exact(idx), ty)
556+
self.verify_arg_type(Capture(idx), ty)
545557
} else {
546558
let msg = format!("there is no argument named `{}`", name);
547559
let sp = if self.is_literal {
@@ -1010,6 +1022,7 @@ pub fn expand_preparsed_format_args(
10101022
let mut cx = Context {
10111023
ecx,
10121024
args,
1025+
num_captured_args: 0,
10131026
arg_types,
10141027
arg_unique_types,
10151028
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)