diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index aaa6580acc6f0..584fbd1b605cd 100644
--- a/compiler/rustc_builtin_macros/src/format.rs
+++ b/compiler/rustc_builtin_macros/src/format.rs
@@ -25,6 +25,7 @@ enum ArgumentType {
enum Position {
Exact(usize),
+ Capture(usize),
Named(Symbol),
}
@@ -49,6 +50,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.
@@ -231,6 +234,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}")`.
@@ -345,7 +353,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(),
@@ -371,7 +379,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!(
@@ -419,7 +427,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!(
@@ -462,7 +470,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!(
@@ -494,12 +502,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
@@ -526,7 +537,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
@@ -541,9 +552,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 {
@@ -1051,6 +1063,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");
+}