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
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();
+ }
}
}
}
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
/// 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
// -------------------------------------------------------------------------
/// 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
forbid,
format_args,
format_args_nl,
+ format_args_capture,
from,
From,
from_desugaring,
--- /dev/null
+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`
+}
--- /dev/null
+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
+
--- /dev/null
+#![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`
+}
--- /dev/null
+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
+
--- /dev/null
+#![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
+}
--- /dev/null
+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`.
--- /dev/null
+// 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--");
+}
|
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
|
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
|
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