1
Fork 0

Improve error message for printf-style format strings

This commit is contained in:
Fabian Wolff 2021-09-29 00:11:55 +02:00
parent 1d71ba8623
commit 6490ed30e1
6 changed files with 105 additions and 26 deletions

View file

@ -1154,11 +1154,12 @@ pub fn expand_preparsed_format_args(
// account for `"` and account for raw strings `r#` // account for `"` and account for raw strings `r#`
let padding = str_style.map(|i| i + 2).unwrap_or(1); let padding = str_style.map(|i| i + 2).unwrap_or(1);
for sub in foreign::$kind::iter_subs(fmt_str, padding) { for sub in foreign::$kind::iter_subs(fmt_str, padding) {
let trn = match sub.translate() { let (trn, success) = match sub.translate() {
Some(trn) => trn, Ok(trn) => (trn, true),
Err(Some(msg)) => (msg, false),
// If it has no translation, don't call it out specifically. // If it has no translation, don't call it out specifically.
None => continue, _ => continue,
}; };
let pos = sub.position(); let pos = sub.position();
@ -1175,9 +1176,24 @@ pub fn expand_preparsed_format_args(
if let Some(inner_sp) = pos { if let Some(inner_sp) = pos {
let sp = fmt_sp.from_inner(inner_sp); let sp = fmt_sp.from_inner(inner_sp);
if success {
suggestions.push((sp, trn)); suggestions.push((sp, trn));
} else { } else {
diag.span_note(
sp,
&format!("format specifiers use curly braces, and {}", trn),
);
}
} else {
if success {
diag.help(&format!("`{}` should be written as `{}`", sub, trn)); diag.help(&format!("`{}` should be written as `{}`", sub, trn));
} else {
diag.note(&format!(
"`{}` should use curly braces, and {}",
sub, trn
));
}
} }
} }

View file

@ -1,4 +1,4 @@
pub mod printf { pub(crate) mod printf {
use super::strcursor::StrCursor as Cur; use super::strcursor::StrCursor as Cur;
use rustc_span::InnerSpan; use rustc_span::InnerSpan;
@ -36,10 +36,10 @@ pub mod printf {
/// ///
/// This ignores cases where the substitution does not have an exact equivalent, or where /// This ignores cases where the substitution does not have an exact equivalent, or where
/// the substitution would be unnecessary. /// the substitution would be unnecessary.
pub fn translate(&self) -> Option<String> { pub fn translate(&self) -> Result<String, Option<String>> {
match *self { match *self {
Substitution::Format(ref fmt) => fmt.translate(), Substitution::Format(ref fmt) => fmt.translate(),
Substitution::Escape => None, Substitution::Escape => Err(None),
} }
} }
} }
@ -68,9 +68,9 @@ pub mod printf {
impl Format<'_> { impl Format<'_> {
/// Translate this directive into an equivalent Rust formatting directive. /// Translate this directive into an equivalent Rust formatting directive.
/// ///
/// Returns `None` in cases where the `printf` directive does not have an exact Rust /// Returns `Err` in cases where the `printf` directive does not have an exact Rust
/// equivalent, rather than guessing. /// equivalent, rather than guessing.
pub fn translate(&self) -> Option<String> { pub fn translate(&self) -> Result<String, Option<String>> {
use std::fmt::Write; use std::fmt::Write;
let (c_alt, c_zero, c_left, c_plus) = { let (c_alt, c_zero, c_left, c_plus) = {
@ -84,7 +84,12 @@ pub mod printf {
'0' => c_zero = true, '0' => c_zero = true,
'-' => c_left = true, '-' => c_left = true,
'+' => c_plus = true, '+' => c_plus = true,
_ => return None, _ => {
return Err(Some(format!(
"the flag `{}` is unknown or unsupported",
c
)));
}
} }
} }
(c_alt, c_zero, c_left, c_plus) (c_alt, c_zero, c_left, c_plus)
@ -104,7 +109,9 @@ pub mod printf {
let width = match self.width { let width = match self.width {
Some(Num::Next) => { Some(Num::Next) => {
// NOTE: Rust doesn't support this. // NOTE: Rust doesn't support this.
return None; return Err(Some(
"you have to use a positional or named parameter for the width".to_string(),
));
} }
w @ Some(Num::Arg(_)) => w, w @ Some(Num::Arg(_)) => w,
w @ Some(Num::Num(_)) => w, w @ Some(Num::Num(_)) => w,
@ -125,13 +132,21 @@ pub mod printf {
"p" => (Some(self.type_), false, true), "p" => (Some(self.type_), false, true),
"g" => (Some("e"), true, false), "g" => (Some("e"), true, false),
"G" => (Some("E"), true, false), "G" => (Some("E"), true, false),
_ => return None, _ => {
return Err(Some(format!(
"the conversion specifier `{}` is unknown or unsupported",
self.type_
)));
}
}; };
let (fill, width, precision) = match (is_int, width, precision) { let (fill, width, precision) = match (is_int, width, precision) {
(true, Some(_), Some(_)) => { (true, Some(_), Some(_)) => {
// Rust can't duplicate this insanity. // Rust can't duplicate this insanity.
return None; return Err(Some(
"width and precision cannot both be specified for integer conversions"
.to_string(),
));
} }
(true, None, Some(p)) => (Some("0"), Some(p), None), (true, None, Some(p)) => (Some("0"), Some(p), None),
(true, w, None) => (fill, w, None), (true, w, None) => (fill, w, None),
@ -169,7 +184,17 @@ pub mod printf {
s.push('{'); s.push('{');
if let Some(arg) = self.parameter { if let Some(arg) = self.parameter {
write!(s, "{}", arg.checked_sub(1)?).ok()?; match write!(
s,
"{}",
match arg.checked_sub(1) {
Some(a) => a,
None => return Err(None),
}
) {
Err(_) => return Err(None),
_ => {}
}
} }
if has_options { if has_options {
@ -199,12 +224,18 @@ pub mod printf {
} }
if let Some(width) = width { if let Some(width) = width {
width.translate(&mut s).ok()?; match width.translate(&mut s) {
Err(_) => return Err(None),
_ => {}
}
} }
if let Some(precision) = precision { if let Some(precision) = precision {
s.push('.'); s.push('.');
precision.translate(&mut s).ok()?; match precision.translate(&mut s) {
Err(_) => return Err(None),
_ => {}
}
} }
if let Some(type_) = type_ { if let Some(type_) = type_ {
@ -213,7 +244,7 @@ pub mod printf {
} }
s.push('}'); s.push('}');
Some(s) Ok(s)
} }
} }
@ -623,11 +654,11 @@ pub mod shell {
} }
} }
pub fn translate(&self) -> Option<String> { pub fn translate(&self) -> Result<String, Option<String>> {
match *self { match *self {
Substitution::Ordinal(n, _) => Some(format!("{{{}}}", n)), Substitution::Ordinal(n, _) => Ok(format!("{{{}}}", n)),
Substitution::Name(n, _) => Some(format!("{{{}}}", n)), Substitution::Name(n, _) => Ok(format!("{{{}}}", n)),
Substitution::Escape(_) => None, Substitution::Escape(_) => Err(None),
} }
} }
} }

View file

@ -3,7 +3,7 @@ use super::{iter_subs, parse_next_substitution as pns, Format as F, Num as N, Su
macro_rules! assert_eq_pnsat { macro_rules! assert_eq_pnsat {
($lhs:expr, $rhs:expr) => { ($lhs:expr, $rhs:expr) => {
assert_eq!( assert_eq!(
pns($lhs).and_then(|(s, _)| s.translate()), pns($lhs).and_then(|(s, _)| s.translate().ok()),
$rhs.map(<String as From<&str>>::from) $rhs.map(<String as From<&str>>::from)
) )
}; };
@ -98,7 +98,7 @@ fn test_parse() {
#[test] #[test]
fn test_iter() { fn test_iter() {
let s = "The %d'th word %% is: `%.*s` %!\n"; let s = "The %d'th word %% is: `%.*s` %!\n";
let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate()).collect(); let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate().ok()).collect();
assert_eq!( assert_eq!(
subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(), subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
vec![Some("{}"), None, Some("{:.*}"), None] vec![Some("{}"), None, Some("{:.*}"), None]

View file

@ -3,7 +3,7 @@ use super::{parse_next_substitution as pns, Substitution as S};
macro_rules! assert_eq_pnsat { macro_rules! assert_eq_pnsat {
($lhs:expr, $rhs:expr) => { ($lhs:expr, $rhs:expr) => {
assert_eq!( assert_eq!(
pns($lhs).and_then(|(f, _)| f.translate()), pns($lhs).and_then(|(f, _)| f.translate().ok()),
$rhs.map(<String as From<&str>>::from) $rhs.map(<String as From<&str>>::from)
) )
}; };
@ -37,7 +37,7 @@ fn test_parse() {
fn test_iter() { fn test_iter() {
use super::iter_subs; use super::iter_subs;
let s = "The $0'th word $$ is: `$WORD` $!\n"; let s = "The $0'th word $$ is: `$WORD` $!\n";
let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate()).collect(); let subs: Vec<_> = iter_subs(s, 0).map(|sub| sub.translate().ok()).collect();
assert_eq!( assert_eq!(
subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(), subs.iter().map(|ms| ms.as_ref().map(|s| &s[..])).collect::<Vec<_>>(),
vec![Some("{0}"), None, Some("{WORD}")] vec![Some("{0}"), None, Some("{WORD}")]

View file

@ -0,0 +1,14 @@
// Regression test for #89173: Make sure a helpful note is issued for
// printf-style format strings using `*` to specify the width.
fn main() {
let num = 0x0abcde;
let width = 6;
print!("%0*x", width, num);
//~^ ERROR: multiple unused formatting arguments
//~| NOTE: multiple missing formatting specifiers
//~| NOTE: argument never used
//~| NOTE: argument never used
//~| NOTE: format specifiers use curly braces, and you have to use a positional or named parameter for the width
//~| NOTE: printf formatting not supported
}

View file

@ -0,0 +1,18 @@
error: multiple unused formatting arguments
--> $DIR/issue-89173.rs:7:20
|
LL | print!("%0*x", width, num);
| ------ ^^^^^ ^^^ argument never used
| | |
| | argument never used
| multiple missing formatting specifiers
|
note: format specifiers use curly braces, and you have to use a positional or named parameter for the width
--> $DIR/issue-89173.rs:7:13
|
LL | print!("%0*x", width, num);
| ^^^^
= note: printf formatting not supported; see the documentation for `std::fmt`
error: aborting due to previous error