about summary refs log tree commit diff
diff options
context:
space:
mode:
authorMara Bos <m-ou.se@m-ou.se>2022-02-07 14:08:33 +0000
committerGitHub <noreply@github.com>2022-02-07 14:08:33 +0000
commit4445a8ff84e1d729df1b0320760bd8f5dd618e78 (patch)
treebfb9137b3126a7f9246e2fea88910da93d9bf831
parente3c972e2524319a1eec1bf905bf8aafa5cda7218 (diff)
parentcef9b4758386622c2c52df0920eea978786283a0 (diff)
downloadrust-4445a8ff84e1d729df1b0320760bd8f5dd618e78.tar.gz
rust-4445a8ff84e1d729df1b0320760bd8f5dd618e78.zip
Rollup merge of #93394 - m-ou-se:fix-93378, r=estebank
Don't allow {} to refer to implicit captures in format_args.

Fixes #93378
-rw-r--r--compiler/rustc_builtin_macros/src/format.rs35
-rw-r--r--src/test/ui/fmt/format-args-capture-issue-93378.rs11
-rw-r--r--src/test/ui/fmt/format-args-capture-issue-93378.stderr22
-rw-r--r--src/test/ui/fmt/format-args-capture.rs8
4 files changed, 65 insertions, 11 deletions
diff --git a/compiler/rustc_builtin_macros/src/format.rs b/compiler/rustc_builtin_macros/src/format.rs
index aaa6580acc6..584fbd1b605 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<P<ast::Expr>>,
+    /// The number of arguments that were added by implicit capturing.
+    num_captured_args: usize,
     /// Placeholder slot numbers indexed by argument.
     arg_types: Vec<Vec<usize>>,
     /// 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 00000000000..67444444264
--- /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 00000000000..588541044fe
--- /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 e830a5bc9c5..d31d2a6c336 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");
+}