1
Fork 0

Rollup merge of #52649 - estebank:fmt-span, r=oli-obk

Point spans to inner elements of format strings

- Point at missing positional specifiers in string literal
```
error: invalid reference to positional arguments 3, 4 and 5 (there are 3 arguments)
  --> $DIR/ifmt-bad-arg.rs:34:38
   |
LL |     format!("{name} {value} {} {} {} {} {} {}", 0, name=1, value=2);
   |                                      ^^ ^^ ^^
   |
   = note: positional arguments are zero-based
```

- Point at named formatting specifier in string literal
```
error: there is no argument named `foo`
  --> $DIR/ifmt-bad-arg.rs:37:17
   |
LL |     format!("{} {foo} {} {bar} {}", 1, 2, 3);
   |                 ^^^^^
```

- Update label for formatting string in "multiple unused formatting arguments" to be more correct
```
error: multiple unused formatting arguments
  --> $DIR/ifmt-bad-arg.rs:42:17
   |
LL |     format!("", 1, 2);               //~ ERROR: multiple unused formatting arguments
   |             --  ^  ^
   |             |
   |             multiple missing formatting specifiers
```

- When using `printf` string formatting, provide a structured suggestion instead of a note
```
error: multiple unused formatting arguments
  --> $DIR/format-foreign.rs:12:30
   |
LL |     println!("%.*3$s %s!/n", "Hello,", "World", 4); //~ ERROR multiple unused formatting arguments
   |              --------------  ^^^^^^^^  ^^^^^^^  ^
   |              |
   |              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!("{:.2$} {}!/n", "Hello,", "World", 4); //~ ERROR multiple unused formatting arguments
   |               ^^^^^^ ^^
```
This commit is contained in:
Mark Rousskov 2018-07-26 09:18:30 -06:00 committed by GitHub
commit 2aec4e882c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 494 additions and 116 deletions

View file

@ -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<Self::Item> {
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,7 @@ pub mod printf {
_ => {/* fall-through */},
}
Cur::new_at_start(&s[start..])
Cur::new_at(&s[..], start)
};
// This is meant to be a translation of the following regex:
@ -355,6 +383,7 @@ pub mod printf {
precision: None,
length: None,
type_: at.slice_between(next).unwrap(),
position: (start.at, next.at),
}),
next.slice_after()
));
@ -541,6 +570,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 +580,7 @@ pub mod printf {
precision,
length,
type_,
position,
};
Some((Substitution::Format(f), end.slice_after()))
}
@ -616,6 +647,7 @@ pub mod printf {
($in_:expr, {
$param:expr, $flags:expr,
$width:expr, $prec:expr, $len:expr, $type_:expr,
$pos:expr,
}) => {
assert_eq!(
pns(concat!($in_, "!")),
@ -628,6 +660,7 @@ pub mod printf {
precision: $prec,
length: $len,
type_: $type_,
position: $pos,
}),
"!"
))
@ -636,53 +669,53 @@ pub mod printf {
}
assert_pns_eq_sub!("%!",
{ None, "", None, None, None, "!", });
{ None, "", None, None, None, "!", (0, 2), });
assert_pns_eq_sub!("%c",
{ None, "", None, None, None, "c", });
{ None, "", None, None, None, "c", (0, 2), });
assert_pns_eq_sub!("%s",
{ None, "", None, None, None, "s", });
{ None, "", None, None, None, "s", (0, 2), });
assert_pns_eq_sub!("%06d",
{ None, "0", Some(N::Num(6)), None, None, "d", });
{ None, "0", Some(N::Num(6)), None, None, "d", (0, 4), });
assert_pns_eq_sub!("%4.2f",
{ None, "", Some(N::Num(4)), Some(N::Num(2)), None, "f", });
{ None, "", Some(N::Num(4)), Some(N::Num(2)), None, "f", (0, 5), });
assert_pns_eq_sub!("%#x",
{ None, "#", None, None, None, "x", });
{ None, "#", None, None, None, "x", (0, 3), });
assert_pns_eq_sub!("%-10s",
{ None, "-", Some(N::Num(10)), None, None, "s", });
{ None, "-", Some(N::Num(10)), None, None, "s", (0, 5), });
assert_pns_eq_sub!("%*s",
{ None, "", Some(N::Next), None, None, "s", });
{ None, "", Some(N::Next), None, None, "s", (0, 3), });
assert_pns_eq_sub!("%-10.*s",
{ None, "-", Some(N::Num(10)), Some(N::Next), None, "s", });
{ None, "-", Some(N::Num(10)), Some(N::Next), None, "s", (0, 7), });
assert_pns_eq_sub!("%-*.*s",
{ None, "-", Some(N::Next), Some(N::Next), None, "s", });
{ None, "-", Some(N::Next), Some(N::Next), None, "s", (0, 6), });
assert_pns_eq_sub!("%.6i",
{ None, "", None, Some(N::Num(6)), None, "i", });
{ None, "", None, Some(N::Num(6)), None, "i", (0, 4), });
assert_pns_eq_sub!("%+i",
{ None, "+", None, None, None, "i", });
{ None, "+", None, None, None, "i", (0, 3), });
assert_pns_eq_sub!("%08X",
{ None, "0", Some(N::Num(8)), None, None, "X", });
{ None, "0", Some(N::Num(8)), None, None, "X", (0, 4), });
assert_pns_eq_sub!("%lu",
{ None, "", None, None, Some("l"), "u", });
{ None, "", None, None, Some("l"), "u", (0, 3), });
assert_pns_eq_sub!("%Iu",
{ None, "", None, None, Some("I"), "u", });
{ None, "", None, None, Some("I"), "u", (0, 3), });
assert_pns_eq_sub!("%I32u",
{ None, "", None, None, Some("I32"), "u", });
{ None, "", None, None, Some("I32"), "u", (0, 5), });
assert_pns_eq_sub!("%I64u",
{ None, "", None, None, Some("I64"), "u", });
{ None, "", None, None, Some("I64"), "u", (0, 5), });
assert_pns_eq_sub!("%'d",
{ None, "'", None, None, None, "d", });
{ None, "'", None, None, None, "d", (0, 3), });
assert_pns_eq_sub!("%10s",
{ None, "", Some(N::Num(10)), None, None, "s", });
{ None, "", Some(N::Num(10)), None, None, "s", (0, 4), });
assert_pns_eq_sub!("%-10.10s",
{ None, "-", Some(N::Num(10)), Some(N::Num(10)), None, "s", });
{ None, "-", Some(N::Num(10)), Some(N::Num(10)), None, "s", (0, 8), });
assert_pns_eq_sub!("%1$d",
{ Some(1), "", None, None, None, "d", });
{ Some(1), "", None, None, None, "d", (0, 4), });
assert_pns_eq_sub!("%2$.*3$d",
{ Some(2), "", None, Some(N::Arg(3)), None, "d", });
{ Some(2), "", None, Some(N::Arg(3)), None, "d", (0, 8), });
assert_pns_eq_sub!("%1$*2$.*3$d",
{ Some(1), "", Some(N::Arg(2)), Some(N::Arg(3)), None, "d", });
{ Some(1), "", Some(N::Arg(2)), Some(N::Arg(3)), None, "d", (0, 11), });
assert_pns_eq_sub!("%-8ld",
{ None, "-", Some(N::Num(8)), None, Some("l"), "d", });
{ None, "-", Some(N::Num(8)), None, Some("l"), "d", (0, 5), });
}
#[test]
@ -755,6 +788,12 @@ pub mod shell {
}
}
pub fn position(&self) -> Option<(usize, usize)> {
match *self {
_ => None,
}
}
pub fn translate(&self) -> Option<String> {
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(s: &'a str, at: usize) -> StrCursor<'a> {
StrCursor {
s,
at,
}
}
pub fn at_next_cp(mut self) -> Option<StrCursor<'a>> {
match self.try_seek_right_cp() {
true => Some(self),