diff --git a/src/libsyntax_ext/format.rs b/src/libsyntax_ext/format.rs index 023fe77cb3c..ad05db91770 100644 --- a/src/libsyntax_ext/format.rs +++ b/src/libsyntax_ext/format.rs @@ -948,6 +948,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, ($kind:ident) => {{ let mut show_doc_note = false; + let mut suggestions = vec![]; for sub in foreign::$kind::iter_subs(fmt_str) { let trn = match sub.translate() { Some(trn) => trn, @@ -956,6 +957,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, None => continue, }; + let pos = sub.position(); let sub = String::from(sub.as_str()); if explained.contains(&sub) { continue; @@ -967,7 +969,14 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, show_doc_note = true; } - diag.help(&format!("`{}` should be written as `{}`", sub, trn)); + if let Some((start, end)) = pos { + // account for `"` and account for raw strings `r#` + let padding = str_style.map(|i| i + 2).unwrap_or(1); + let sp = fmt_sp.from_inner_byte_pos(start + padding, end + padding); + suggestions.push((sp, trn)); + } else { + diag.help(&format!("`{}` should be written as `{}`", sub, trn)); + } } if show_doc_note { @@ -976,6 +985,12 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt, " formatting not supported; see the documentation for `std::fmt`", )); } + if suggestions.len() > 0 { + diag.multipart_suggestion( + "format specifiers in Rust are written using `{}`", + suggestions, + ); + } }}; } diff --git a/src/libsyntax_ext/format_foreign.rs b/src/libsyntax_ext/format_foreign.rs index ff9663cdd3c..115f51c5e81 100644 --- a/src/libsyntax_ext/format_foreign.rs +++ b/src/libsyntax_ext/format_foreign.rs @@ -14,7 +14,7 @@ pub mod printf { /// Represents a single `printf`-style substitution. #[derive(Clone, PartialEq, Debug)] pub enum Substitution<'a> { - /// A formatted output substitution. + /// A formatted output substitution with its internal byte offset. Format(Format<'a>), /// A literal `%%` escape. Escape, @@ -28,6 +28,23 @@ pub mod printf { } } + pub fn position(&self) -> Option<(usize, usize)> { + match *self { + Substitution::Format(ref fmt) => Some(fmt.position), + _ => None, + } + } + + pub fn set_position(&mut self, start: usize, end: usize) { + match self { + Substitution::Format(ref mut fmt) => { + fmt.position = (start, end); + } + _ => {} + } + } + + /// Translate this substitution into an equivalent Rust formatting directive. /// /// This ignores cases where the substitution does not have an exact equivalent, or where @@ -57,6 +74,8 @@ pub mod printf { pub length: Option<&'a str>, /// Type of parameter being converted. pub type_: &'a str, + /// Byte offset for the start and end of this formatting directive. + pub position: (usize, usize), } impl<'a> Format<'a> { @@ -257,19 +276,28 @@ pub mod printf { pub fn iter_subs(s: &str) -> Substitutions { Substitutions { s, + pos: 0, } } /// Iterator over substitutions in a string. pub struct Substitutions<'a> { s: &'a str, + pos: usize, } impl<'a> Iterator for Substitutions<'a> { type Item = Substitution<'a>; fn next(&mut self) -> Option { - let (sub, tail) = parse_next_substitution(self.s)?; + let (mut sub, tail) = parse_next_substitution(self.s)?; self.s = tail; + match sub { + Substitution::Format(_) => if let Some((start, end)) = sub.position() { + sub.set_position(start + self.pos, end + self.pos); + self.pos += end; + } + Substitution::Escape => self.pos += 2, + } Some(sub) } @@ -301,7 +329,9 @@ pub mod printf { _ => {/* fall-through */}, } - Cur::new_at_start(&s[start..]) + //let _ = Cur::new_at_start_with_pos(&s[..], start); + //Cur::new_at_start(&s[start..]) + Cur::new_at_start_with_pos(&s[..], start) }; // This is meant to be a translation of the following regex: @@ -355,6 +385,7 @@ pub mod printf { precision: None, length: None, type_: at.slice_between(next).unwrap(), + position: (start.at, next.at), }), next.slice_after() )); @@ -541,6 +572,7 @@ pub mod printf { drop(next); end = at; + let position = (start.at, end.at); let f = Format { span: start.slice_between(end).unwrap(), @@ -550,6 +582,7 @@ pub mod printf { precision, length, type_, + position, }; Some((Substitution::Format(f), end.slice_after())) } @@ -755,6 +788,12 @@ pub mod shell { } } + pub fn position(&self) -> Option<(usize, usize)> { + match *self { + _ => None, + } + } + pub fn translate(&self) -> Option { match *self { Substitution::Ordinal(n) => Some(format!("{{{}}}", n)), @@ -918,7 +957,7 @@ mod strcursor { pub struct StrCursor<'a> { s: &'a str, - at: usize, + pub at: usize, } impl<'a> StrCursor<'a> { @@ -929,6 +968,13 @@ mod strcursor { } } + pub fn new_at_start_with_pos(s: &'a str, at: usize) -> StrCursor<'a> { + StrCursor { + s, + at, + } + } + pub fn at_next_cp(mut self) -> Option> { match self.try_seek_right_cp() { true => Some(self), diff --git a/src/test/ui/ifmt-bad-arg.stderr b/src/test/ui/ifmt-bad-arg.stderr index 92c44cf406b..a126998355e 100644 --- a/src/test/ui/ifmt-bad-arg.stderr +++ b/src/test/ui/ifmt-bad-arg.stderr @@ -178,9 +178,10 @@ error: argument never used --> $DIR/ifmt-bad-arg.rs:66:27 | LL | format!("foo %s baz", "bar"); //~ ERROR: argument never used - | ^^^^^ + | -- ^^^^^ + | | + | help: format specifiers in Rust are written using `{}`: `{}` | - = help: `%s` should be written as `{}` = note: printf formatting not supported; see the documentation for `std::fmt` error: there is no argument named `foo` diff --git a/src/test/ui/macros/format-foreign.rs b/src/test/ui/macros/format-foreign.rs index ec0eaed43ae..33401424c9a 100644 --- a/src/test/ui/macros/format-foreign.rs +++ b/src/test/ui/macros/format-foreign.rs @@ -11,6 +11,11 @@ fn main() { println!("%.*3$s %s!\n", "Hello,", "World", 4); //~ ERROR multiple unused formatting arguments println!("%1$*2$.*3$f", 123.456); //~ ERROR never used + println!(r###"%.*3$s + %s!\n +"###, "Hello,", "World", 4); + //~^ ERROR multiple unused formatting arguments + // correctly account for raw strings in inline suggestions // This should *not* produce hints, on the basis that there's equally as // many "correct" format specifiers. It's *probably* just an actual typo. diff --git a/src/test/ui/macros/format-foreign.stderr b/src/test/ui/macros/format-foreign.stderr index 83ff301dc19..93e68183b14 100644 --- a/src/test/ui/macros/format-foreign.stderr +++ b/src/test/ui/macros/format-foreign.stderr @@ -6,27 +6,48 @@ LL | println!("%.*3$s %s!/n", "Hello,", "World", 4); //~ ERROR multiple unus | | | multiple missing formatting specifiers | - = help: `%.*3$s` should be written as `{:.2$}` - = help: `%s` should be written as `{}` = note: printf formatting not supported; see the documentation for `std::fmt` +help: format specifiers in Rust are written using `{}` + | +LL | println!("{:.2$} {}!/n", "Hello,", "World", 4); //~ ERROR multiple unused formatting arguments + | ^^^^^^ ^^ error: argument never used --> $DIR/format-foreign.rs:13:29 | LL | println!("%1$*2$.*3$f", 123.456); //~ ERROR never used - | ^^^^^^^ + | ----------- ^^^^^^^ + | | + | help: format specifiers in Rust are written using `{}`: `{0:1$.2$}` | - = help: `%1$*2$.*3$f` should be written as `{0:1$.2$}` = note: printf formatting not supported; see the documentation for `std::fmt` +error: multiple unused formatting arguments + --> $DIR/format-foreign.rs:16:7 + | +LL | println!(r###"%.*3$s + | ______________- +LL | | %s!/n +LL | | "###, "Hello,", "World", 4); + | | - ^^^^^^^^ ^^^^^^^ ^ + | |____| + | multiple missing formatting specifiers + | + = note: printf formatting not supported; see the documentation for `std::fmt` +help: format specifiers in Rust are written using `{}` + | +LL | println!(r###"{:.2$} +LL | {}!/n + | + error: argument never used - --> $DIR/format-foreign.rs:17:30 + --> $DIR/format-foreign.rs:22:30 | LL | println!("{} %f", "one", 2.0); //~ ERROR never used | ^^^ error: named argument never used - --> $DIR/format-foreign.rs:19:39 + --> $DIR/format-foreign.rs:24:39 | LL | println!("Hi there, $NAME.", NAME="Tim"); //~ ERROR never used | ^^^^^ @@ -34,5 +55,5 @@ LL | println!("Hi there, $NAME.", NAME="Tim"); //~ ERROR never used = help: `$NAME` should be written as `{NAME}` = note: shell formatting not supported; see the documentation for `std::fmt` -error: aborting due to 4 previous errors +error: aborting due to 5 previous errors