]> git.lizzy.rs Git - rust.git/commitdiff
Add `format_args_capture` feature
authorDavid Hewitt <1939362+davidhewitt@users.noreply.github.com>
Sat, 12 Oct 2019 14:07:13 +0000 (15:07 +0100)
committerDavid Hewitt <1939362+davidhewitt@users.noreply.github.com>
Wed, 24 Jun 2020 07:29:55 +0000 (08:29 +0100)
12 files changed:
src/librustc_builtin_macros/format.rs
src/librustc_feature/active.rs
src/librustc_parse_format/lib.rs
src/librustc_span/symbol.rs
src/test/ui/fmt/feature-gate-format-args-capture.rs [new file with mode: 0644]
src/test/ui/fmt/feature-gate-format-args-capture.stderr [new file with mode: 0644]
src/test/ui/fmt/format-args-capture-macro-hygiene.rs [new file with mode: 0644]
src/test/ui/fmt/format-args-capture-macro-hygiene.stderr [new file with mode: 0644]
src/test/ui/fmt/format-args-capture-missing-variables.rs [new file with mode: 0644]
src/test/ui/fmt/format-args-capture-missing-variables.stderr [new file with mode: 0644]
src/test/ui/fmt/format-args-capture.rs [new file with mode: 0644]
src/test/ui/if/ifmt-bad-arg.stderr

index e574b076bf84c8d68ba8cd9e79e0606448c6167e..538d513c5d6eaa42fe9565863f75728f9c923f7d 100644 (file)
@@ -107,6 +107,9 @@ struct Context<'a, 'b> {
     arg_spans: Vec<Span>,
     /// All the formatting arguments that have formatting flags set, in order for diagnostics.
     arg_with_formatting: Vec<parse::FormatSpec<'a>>,
+
+    /// Whether this format string came from a string literal, as opposed to a macro.
+    is_literal: bool,
 }
 
 /// Parses the arguments from the given list of tokens, returning the diagnostic
@@ -498,10 +501,59 @@ fn verify_arg_type(&mut self, arg: Position, ty: ArgumentType) {
                         self.verify_arg_type(Exact(idx), ty)
                     }
                     None => {
-                        let msg = format!("there is no argument named `{}`", name);
-                        let sp = *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp);
-                        let mut err = self.ecx.struct_span_err(sp, &msg[..]);
-                        err.emit();
+                        let capture_feature_enabled = self
+                            .ecx
+                            .ecfg
+                            .features
+                            .map_or(false, |features| features.format_args_capture);
+
+                        // For the moment capturing variables from format strings expanded from
+                        // literals is disabled (see RFC #2795)
+                        let can_capture = capture_feature_enabled && self.is_literal;
+
+                        if can_capture {
+                            // Treat this name as a variable to capture from the surrounding scope
+                            let idx = self.args.len();
+                            self.arg_types.push(Vec::new());
+                            self.arg_unique_types.push(Vec::new());
+                            self.args.push(
+                                self.ecx.expr_ident(self.fmtsp, Ident::new(name, self.fmtsp)),
+                            );
+                            self.names.insert(name, idx);
+                            self.verify_arg_type(Exact(idx), ty)
+                        } else {
+                            let msg = format!("there is no argument named `{}`", name);
+                            let sp = if self.is_literal {
+                                *self.arg_spans.get(self.curpiece).unwrap_or(&self.fmtsp)
+                            } else {
+                                self.fmtsp
+                            };
+                            let mut err = self.ecx.struct_span_err(sp, &msg[..]);
+
+                            if capture_feature_enabled && !self.is_literal {
+                                err.note(&format!(
+                                    "did you intend to capture a variable `{}` from \
+                                     the surrounding scope?",
+                                    name
+                                ));
+                                err.note(
+                                    "for hygiene reasons format_args! cannot capture variables \
+                                     when the format string is expanded from a macro",
+                                );
+                            } else if self.ecx.parse_sess().unstable_features.is_nightly_build() {
+                                err.note(&format!(
+                                    "did you intend to capture a variable `{}` from \
+                                     the surrounding scope?",
+                                    name
+                                ));
+                                err.help(
+                                    "add `#![feature(format_args_capture)]` to the crate \
+                                     attributes to enable",
+                                );
+                            }
+
+                            err.emit();
+                        }
                     }
                 }
             }
@@ -951,6 +1003,7 @@ pub fn expand_preparsed_format_args(
         invalid_refs: Vec::new(),
         arg_spans,
         arg_with_formatting: Vec::new(),
+        is_literal: parser.is_literal,
     };
 
     // This needs to happen *after* the Parser has consumed all pieces to create all the spans
index e2d497a3adab374a18941c0069884f024fcbd873..11c1908b57cac4f76fa5188b79e3d9902b945328 100644 (file)
@@ -577,6 +577,9 @@ pub fn set(&self, features: &mut Features, span: Span) {
     /// Be more precise when looking for live drops in a const context.
     (active, const_precise_live_drops, "1.46.0", Some(73255), None),
 
+    /// Allows capturing variables in scope using format_args!
+    (active, format_args_capture, "1.46.0", Some(67984), None),
+
     // -------------------------------------------------------------------------
     // feature-group-end: actual feature gates
     // -------------------------------------------------------------------------
index a5b5a1090cbfd5052e0d6beb64b0b79581a87698..7db62f3493ede969d97fc00309ab5080076c7641 100644 (file)
@@ -190,7 +190,7 @@ pub struct Parser<'a> {
     /// Whether the source string is comes from `println!` as opposed to `format!` or `print!`
     append_newline: bool,
     /// Whether this formatting string is a literal or it comes from a macro.
-    is_literal: bool,
+    pub is_literal: bool,
     /// Start position of the current line.
     cur_line_start: usize,
     /// Start and end byte offset of every line of the format string. Excludes
index 06d1f36622b94ef101b913a73f293c9c5793b1b7..185ce1afde90e0d0e33b0eced01f530b4163033b 100644 (file)
         forbid,
         format_args,
         format_args_nl,
+        format_args_capture,
         from,
         From,
         from_desugaring,
diff --git a/src/test/ui/fmt/feature-gate-format-args-capture.rs b/src/test/ui/fmt/feature-gate-format-args-capture.rs
new file mode 100644 (file)
index 0000000..21af916
--- /dev/null
@@ -0,0 +1,6 @@
+fn main() {
+    format!("{foo}");                //~ ERROR: there is no argument named `foo`
+
+    // panic! doesn't hit format_args! unless there are two or more arguments.
+    panic!("{foo} {bar}", bar=1);    //~ ERROR: there is no argument named `foo`
+}
diff --git a/src/test/ui/fmt/feature-gate-format-args-capture.stderr b/src/test/ui/fmt/feature-gate-format-args-capture.stderr
new file mode 100644 (file)
index 0000000..bbd4d75
--- /dev/null
@@ -0,0 +1,20 @@
+error: there is no argument named `foo`
+  --> $DIR/feature-gate-format-args-capture.rs:2:14
+   |
+LL |     format!("{foo}");
+   |              ^^^^^
+   |
+   = note: did you intend to capture a variable `foo` from the surrounding scope?
+   = help: add `#![feature(format_args_capture)]` to the crate attributes to enable
+
+error: there is no argument named `foo`
+  --> $DIR/feature-gate-format-args-capture.rs:5:13
+   |
+LL |     panic!("{foo} {bar}", bar=1);
+   |             ^^^^^
+   |
+   = note: did you intend to capture a variable `foo` from the surrounding scope?
+   = help: add `#![feature(format_args_capture)]` to the crate attributes to enable
+
+error: aborting due to 2 previous errors
+
diff --git a/src/test/ui/fmt/format-args-capture-macro-hygiene.rs b/src/test/ui/fmt/format-args-capture-macro-hygiene.rs
new file mode 100644 (file)
index 0000000..6ca7dcc
--- /dev/null
@@ -0,0 +1,6 @@
+#![feature(format_args_capture)]
+
+fn main() {
+    format!(concat!("{foo}"));         //~ ERROR: there is no argument named `foo`
+    format!(concat!("{ba", "r} {}"), 1);     //~ ERROR: there is no argument named `bar`
+}
diff --git a/src/test/ui/fmt/format-args-capture-macro-hygiene.stderr b/src/test/ui/fmt/format-args-capture-macro-hygiene.stderr
new file mode 100644 (file)
index 0000000..42039c3
--- /dev/null
@@ -0,0 +1,22 @@
+error: there is no argument named `foo`
+  --> $DIR/format-args-capture-macro-hygiene.rs:4:13
+   |
+LL |     format!(concat!("{foo}"));
+   |             ^^^^^^^^^^^^^^^^
+   |
+   = note: did you intend to capture a variable `foo` from the surrounding scope?
+   = note: for hygiene reasons format_args! cannot capture variables when the format string is expanded from a macro
+   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: there is no argument named `bar`
+  --> $DIR/format-args-capture-macro-hygiene.rs:5:13
+   |
+LL |     format!(concat!("{ba", "r} {}"), 1);
+   |             ^^^^^^^^^^^^^^^^^^^^^^^
+   |
+   = note: did you intend to capture a variable `bar` from the surrounding scope?
+   = note: for hygiene reasons format_args! cannot capture variables when the format string is expanded from a macro
+   = note: this error originates in a macro (in Nightly builds, run with -Z macro-backtrace for more info)
+
+error: aborting due to 2 previous errors
+
diff --git a/src/test/ui/fmt/format-args-capture-missing-variables.rs b/src/test/ui/fmt/format-args-capture-missing-variables.rs
new file mode 100644 (file)
index 0000000..3c596ae
--- /dev/null
@@ -0,0 +1,22 @@
+#![feature(format_args_capture)]
+
+fn main() {
+    format!("{} {foo} {} {bar} {}", 1, 2, 3);
+    //~^ ERROR: cannot find value `foo` in this scope
+    //~^^ ERROR: cannot find value `bar` in this scope
+
+    format!("{foo}");                //~ ERROR: cannot find value `foo` in this scope
+
+    format!("{valuea} {valueb}", valuea=5, valuec=7);
+    //~^ ERROR cannot find value `valueb` in this scope
+    //~^^ ERROR named argument never used
+
+    format!(r##"
+
+        {foo}
+
+    "##);
+    //~^^^^^ ERROR: cannot find value `foo` in this scope
+
+    panic!("{foo} {bar}", bar=1);    //~ ERROR: cannot find value `foo` in this scope
+}
diff --git a/src/test/ui/fmt/format-args-capture-missing-variables.stderr b/src/test/ui/fmt/format-args-capture-missing-variables.stderr
new file mode 100644 (file)
index 0000000..c3d740e
--- /dev/null
@@ -0,0 +1,52 @@
+error: named argument never used
+  --> $DIR/format-args-capture-missing-variables.rs:10:51
+   |
+LL |     format!("{valuea} {valueb}", valuea=5, valuec=7);
+   |             -------------------                   ^ named argument never used
+   |             |
+   |             formatting specifier missing
+
+error[E0425]: cannot find value `foo` in this scope
+  --> $DIR/format-args-capture-missing-variables.rs:4:13
+   |
+LL |     format!("{} {foo} {} {bar} {}", 1, 2, 3);
+   |             ^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `bar` in this scope
+  --> $DIR/format-args-capture-missing-variables.rs:4:13
+   |
+LL |     format!("{} {foo} {} {bar} {}", 1, 2, 3);
+   |             ^^^^^^^^^^^^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `foo` in this scope
+  --> $DIR/format-args-capture-missing-variables.rs:8:13
+   |
+LL |     format!("{foo}");
+   |             ^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `valueb` in this scope
+  --> $DIR/format-args-capture-missing-variables.rs:10:13
+   |
+LL |     format!("{valuea} {valueb}", valuea=5, valuec=7);
+   |             ^^^^^^^^^^^^^^^^^^^ not found in this scope
+
+error[E0425]: cannot find value `foo` in this scope
+  --> $DIR/format-args-capture-missing-variables.rs:14:13
+   |
+LL |       format!(r##"
+   |  _____________^
+LL | |
+LL | |         {foo}
+LL | |
+LL | |     "##);
+   | |_______^ not found in this scope
+
+error[E0425]: cannot find value `foo` in this scope
+  --> $DIR/format-args-capture-missing-variables.rs:21:12
+   |
+LL |     panic!("{foo} {bar}", bar=1);
+   |            ^^^^^^^^^^^^^ not found in this scope
+
+error: aborting due to 7 previous errors
+
+For more information about this error, try `rustc --explain E0425`.
diff --git a/src/test/ui/fmt/format-args-capture.rs b/src/test/ui/fmt/format-args-capture.rs
new file mode 100644 (file)
index 0000000..89dcfd8
--- /dev/null
@@ -0,0 +1,62 @@
+// run-pass
+#![feature(format_args_capture)]
+
+fn main() {
+    named_argument_takes_precedence_to_captured();
+    panic_with_single_argument_does_not_get_formatted();
+    panic_with_multiple_arguments_is_formatted();
+    formatting_parameters_can_be_captured();
+}
+
+fn named_argument_takes_precedence_to_captured() {
+    let foo = "captured";
+    let s = format!("{foo}", foo="named");
+    assert_eq!(&s, "named");
+
+    let s = format!("{foo}-{foo}-{foo}", foo="named");
+    assert_eq!(&s, "named-named-named");
+
+    let s = format!("{}-{bar}-{foo}", "positional", bar="named");
+    assert_eq!(&s, "positional-named-captured");
+}
+
+fn panic_with_single_argument_does_not_get_formatted() {
+    // panic! with a single argument does not perform string formatting.
+    // RFC #2795 suggests that this may need to change so that captured arguments are formatted.
+    // For stability reasons this will need to part of an edition change.
+
+    let msg = std::panic::catch_unwind(|| {
+        panic!("{foo}");
+    }).unwrap_err();
+
+    assert_eq!(msg.downcast_ref::<&str>(), Some(&"{foo}"))
+}
+
+fn panic_with_multiple_arguments_is_formatted() {
+    let foo = "captured";
+
+    let msg = std::panic::catch_unwind(|| {
+        panic!("{}-{bar}-{foo}", "positional", bar="named");
+    }).unwrap_err();
+
+    assert_eq!(msg.downcast_ref::<String>(), Some(&"positional-named-captured".to_string()))
+}
+
+fn formatting_parameters_can_be_captured() {
+    let width = 9;
+    let precision = 3;
+
+    let x = 7.0;
+
+    let s = format!("{x:width$}");
+    assert_eq!(&s, "        7");
+
+    let s = format!("{x:<width$}");
+    assert_eq!(&s, "7        ");
+
+    let s = format!("{x:-^width$}");
+    assert_eq!(&s, "----7----");
+
+    let s = format!("{x:-^width$.precision$}");
+    assert_eq!(&s, "--7.000--");
+}
index 3e5f5a6374216c1985efcd7cc210fc6b10a0918e..cbcb1df49851a930553a93687ad6743f5218a955 100644 (file)
@@ -63,18 +63,27 @@ error: there is no argument named `foo`
    |
 LL |     format!("{} {foo} {} {bar} {}", 1, 2, 3);
    |                 ^^^^^
+   |
+   = note: did you intend to capture a variable `foo` from the surrounding scope?
+   = help: add `#![feature(format_args_capture)]` to the crate attributes to enable
 
 error: there is no argument named `bar`
   --> $DIR/ifmt-bad-arg.rs:27:26
    |
 LL |     format!("{} {foo} {} {bar} {}", 1, 2, 3);
    |                          ^^^^^
+   |
+   = note: did you intend to capture a variable `bar` from the surrounding scope?
+   = help: add `#![feature(format_args_capture)]` to the crate attributes to enable
 
 error: there is no argument named `foo`
   --> $DIR/ifmt-bad-arg.rs:31:14
    |
 LL |     format!("{foo}");
    |              ^^^^^
+   |
+   = note: did you intend to capture a variable `foo` from the surrounding scope?
+   = help: add `#![feature(format_args_capture)]` to the crate attributes to enable
 
 error: multiple unused formatting arguments
   --> $DIR/ifmt-bad-arg.rs:32:17
@@ -155,6 +164,9 @@ error: there is no argument named `valueb`
    |
 LL |     format!("{valuea} {valueb}", valuea=5, valuec=7);
    |                       ^^^^^^^^
+   |
+   = note: did you intend to capture a variable `valueb` from the surrounding scope?
+   = help: add `#![feature(format_args_capture)]` to the crate attributes to enable
 
 error: named argument never used
   --> $DIR/ifmt-bad-arg.rs:45:51
@@ -205,6 +217,9 @@ error: there is no argument named `foo`
    |
 LL |         {foo}
    |         ^^^^^
+   |
+   = note: did you intend to capture a variable `foo` from the surrounding scope?
+   = help: add `#![feature(format_args_capture)]` to the crate attributes to enable
 
 error: invalid format string: expected `'}'`, found `'t'`
   --> $DIR/ifmt-bad-arg.rs:75:1