diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs index 407aaacb88999..154c51b2c7b9e 100644 --- a/compiler/rustc_builtin_macros/src/format.rs +++ b/compiler/rustc_builtin_macros/src/format.rs @@ -23,6 +23,7 @@ enum ArgumentType { enum Position { Exact(usize), + Capture(usize), Named(Symbol), } @@ -47,6 +48,8 @@ struct Context<'a, 'b> { /// * `arg_unique_types` (in simplified JSON): `[["o", "x"], ["o", "x"], ["o", "x"]]` /// * `names` (in JSON): `{"foo": 2}` args: Vec>, + /// The number of arguments that were added by implicit capturing. + num_captured_args: usize, /// Placeholder slot numbers indexed by argument. arg_types: Vec>, /// Unique format specs seen for each argument. @@ -229,6 +232,11 @@ fn parse_args<'a>( } impl<'a, 'b> Context<'a, 'b> { + /// The number of arguments that were explicitly given. + fn num_args(&self) -> usize { + self.args.len() - self.num_captured_args + } + fn resolve_name_inplace(&self, p: &mut parse::Piece<'_>) { // NOTE: the `unwrap_or` branch is needed in case of invalid format // arguments, e.g., `format_args!("{foo}")`. @@ -343,7 +351,7 @@ impl<'a, 'b> Context<'a, 'b> { } fn describe_num_args(&self) -> Cow<'_, str> { - match self.args.len() { + match self.num_args() { 0 => "no arguments were given".into(), 1 => "there is 1 argument".into(), x => format!("there are {} arguments", x).into(), @@ -369,7 +377,7 @@ impl<'a, 'b> Context<'a, 'b> { let count = self.pieces.len() + self.arg_with_formatting.iter().filter(|fmt| fmt.precision_span.is_some()).count(); - if self.names.is_empty() && !numbered_position_args && count != self.args.len() { + if self.names.is_empty() && !numbered_position_args && count != self.num_args() { e = self.ecx.struct_span_err( sp, &format!( @@ -417,7 +425,7 @@ impl<'a, 'b> Context<'a, 'b> { if let Some(span) = fmt.precision_span { let span = self.fmtsp.from_inner(span); match fmt.precision { - parse::CountIsParam(pos) if pos > self.args.len() => { + parse::CountIsParam(pos) if pos > self.num_args() => { e.span_label( span, &format!( @@ -460,7 +468,7 @@ impl<'a, 'b> Context<'a, 'b> { if let Some(span) = fmt.width_span { let span = self.fmtsp.from_inner(span); match fmt.width { - parse::CountIsParam(pos) if pos > self.args.len() => { + parse::CountIsParam(pos) if pos > self.num_args() => { e.span_label( span, &format!( @@ -492,12 +500,15 @@ impl<'a, 'b> Context<'a, 'b> { /// Actually verifies and tracks a given format placeholder /// (a.k.a. argument). fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) { + if let Exact(arg) = arg { + if arg >= self.num_args() { + self.invalid_refs.push((arg, self.curpiece)); + return; + } + } + match arg { - Exact(arg) => { - if self.args.len() <= arg { - self.invalid_refs.push((arg, self.curpiece)); - return; - } + Exact(arg) | Capture(arg) => { match ty { Placeholder(_) => { // record every (position, type) combination only once @@ -524,7 +535,7 @@ impl<'a, 'b> Context<'a, 'b> { match self.names.get(&name) { Some(&idx) => { // Treat as positional arg. - self.verify_arg_type(Exact(idx), ty) + self.verify_arg_type(Capture(idx), ty) } None => { // For the moment capturing variables from format strings expanded from macros is @@ -539,9 +550,10 @@ impl<'a, 'b> Context<'a, 'b> { } else { self.fmtsp }; + self.num_captured_args += 1; self.args.push(self.ecx.expr_ident(span, Ident::new(name, span))); self.names.insert(name, idx); - self.verify_arg_type(Exact(idx), ty) + self.verify_arg_type(Capture(idx), ty) } else { let msg = format!("there is no argument named `{}`", name); let sp = if self.is_literal { @@ -1010,6 +1022,7 @@ pub fn expand_preparsed_format_args( let mut cx = Context { ecx, args, + num_captured_args: 0, arg_types, arg_unique_types, names, diff --git a/src/test/ui/fmt/format-args-capture-issue-93378.rs b/src/test/ui/fmt/format-args-capture-issue-93378.rs new file mode 100644 index 0000000000000..6744444426472 --- /dev/null +++ b/src/test/ui/fmt/format-args-capture-issue-93378.rs @@ -0,0 +1,11 @@ +fn main() { + let a = "a"; + let b = "b"; + + println!("{a} {b} {} {} {c} {}", c = "c"); + //~^ ERROR: invalid reference to positional arguments 1 and 2 (there is 1 argument) + + let n = 1; + println!("{a:.n$} {b:.*}"); + //~^ ERROR: invalid reference to positional argument 0 (no arguments were given) +} diff --git a/src/test/ui/fmt/format-args-capture-issue-93378.stderr b/src/test/ui/fmt/format-args-capture-issue-93378.stderr new file mode 100644 index 0000000000000..588541044fe13 --- /dev/null +++ b/src/test/ui/fmt/format-args-capture-issue-93378.stderr @@ -0,0 +1,22 @@ +error: invalid reference to positional arguments 1 and 2 (there is 1 argument) + --> $DIR/format-args-capture-issue-93378.rs:5:26 + | +LL | println!("{a} {b} {} {} {c} {}", c = "c"); + | ^^ ^^ + | + = note: positional arguments are zero-based + +error: invalid reference to positional argument 0 (no arguments were given) + --> $DIR/format-args-capture-issue-93378.rs:9:23 + | +LL | println!("{a:.n$} {b:.*}"); + | ------- ^^^--^ + | | | + | | this precision flag adds an extra required argument at position 0, which is why there are 3 arguments expected + | this parameter corresponds to the precision flag + | + = note: positional arguments are zero-based + = note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html + +error: aborting due to 2 previous errors + diff --git a/src/test/ui/fmt/format-args-capture.rs b/src/test/ui/fmt/format-args-capture.rs index e830a5bc9c5c8..d31d2a6c33657 100644 --- a/src/test/ui/fmt/format-args-capture.rs +++ b/src/test/ui/fmt/format-args-capture.rs @@ -5,6 +5,7 @@ fn main() { named_argument_takes_precedence_to_captured(); formatting_parameters_can_be_captured(); capture_raw_strings_and_idents(); + repeated_capture(); #[cfg(panic = "unwind")] { @@ -80,3 +81,10 @@ fn formatting_parameters_can_be_captured() { let s = format!("{x:-^width$.precision$}"); assert_eq!(&s, "--7.000--"); } + +fn repeated_capture() { + let a = 1; + let b = 2; + let s = format!("{a} {b} {a}"); + assert_eq!(&s, "1 2 1"); +}