Add foreign formatting directive detection.
This teaches `format_args!` how to interpret format printf- and shell-style format directives. This is used in cases where there are unused formatting arguments, and the reason for that *might* be because the programmer is trying to use the wrong kind of formatting string. This was prompted by an issue encountered by simulacrum on the #rust IRC channel. In short: although `println!` told them that they weren't using all of the conversion arguments, the problem was in using printf-syle directives rather than ones `println!` would undertand. Where possible, `format_args!` will tell the programmer what they should use instead. For example, it will suggest replacing `%05d` with `{:0>5}`, or `%2$.*3$s` with `{1:.3$}`. Even if it cannot suggest a replacement, it will explicitly note that Rust does not support that style of directive, and direct the user to the `std::fmt` documentation.
This commit is contained in:
parent
e96b9d2bb4
commit
455723c638
6 changed files with 1163 additions and 2 deletions
|
@ -22,7 +22,7 @@ use syntax::ptr::P;
|
||||||
use syntax_pos::{Span, DUMMY_SP};
|
use syntax_pos::{Span, DUMMY_SP};
|
||||||
use syntax::tokenstream;
|
use syntax::tokenstream;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::{HashMap, HashSet};
|
||||||
use std::collections::hash_map::Entry;
|
use std::collections::hash_map::Entry;
|
||||||
|
|
||||||
#[derive(PartialEq)]
|
#[derive(PartialEq)]
|
||||||
|
@ -767,6 +767,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
|
||||||
|
|
||||||
// Make sure that all arguments were used and all arguments have types.
|
// Make sure that all arguments were used and all arguments have types.
|
||||||
let num_pos_args = cx.args.len() - cx.names.len();
|
let num_pos_args = cx.args.len() - cx.names.len();
|
||||||
|
let mut errs = vec![];
|
||||||
for (i, ty) in cx.arg_types.iter().enumerate() {
|
for (i, ty) in cx.arg_types.iter().enumerate() {
|
||||||
if ty.len() == 0 {
|
if ty.len() == 0 {
|
||||||
if cx.count_positions.contains_key(&i) {
|
if cx.count_positions.contains_key(&i) {
|
||||||
|
@ -779,9 +780,80 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
|
||||||
// positional argument
|
// positional argument
|
||||||
"argument never used"
|
"argument never used"
|
||||||
};
|
};
|
||||||
cx.ecx.span_err(cx.args[i].span, msg);
|
errs.push((cx.args[i].span, msg));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if errs.len() > 0 {
|
||||||
|
let args_used = cx.arg_types.len() - errs.len();
|
||||||
|
let args_unused = errs.len();
|
||||||
|
|
||||||
|
let mut diag = {
|
||||||
|
if errs.len() == 1 {
|
||||||
|
let (sp, msg) = errs.into_iter().next().unwrap();
|
||||||
|
cx.ecx.struct_span_err(sp, msg)
|
||||||
|
} else {
|
||||||
|
let mut diag = cx.ecx.struct_span_err(cx.fmtsp,
|
||||||
|
"multiple unused formatting arguments");
|
||||||
|
for (sp, msg) in errs {
|
||||||
|
diag.span_note(sp, msg);
|
||||||
|
}
|
||||||
|
diag
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Decide if we want to look for foreign formatting directives.
|
||||||
|
if args_used < args_unused {
|
||||||
|
use super::format_foreign as foreign;
|
||||||
|
let fmt_str = &fmt.node.0[..];
|
||||||
|
|
||||||
|
// The set of foreign substitutions we've explained. This prevents spamming the user
|
||||||
|
// with `%d should be written as {}` over and over again.
|
||||||
|
let mut explained = HashSet::new();
|
||||||
|
|
||||||
|
// Used to ensure we only report translations for *one* kind of foreign format.
|
||||||
|
let mut found_foreign = false;
|
||||||
|
|
||||||
|
macro_rules! check_foreign {
|
||||||
|
($kind:ident) => {{
|
||||||
|
let mut show_doc_note = false;
|
||||||
|
|
||||||
|
for sub in foreign::$kind::iter_subs(fmt_str) {
|
||||||
|
let trn = match sub.translate() {
|
||||||
|
Some(trn) => trn,
|
||||||
|
|
||||||
|
// If it has no translation, don't call it out specifically.
|
||||||
|
None => continue,
|
||||||
|
};
|
||||||
|
|
||||||
|
let sub = String::from(sub.as_str());
|
||||||
|
if explained.contains(&sub) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
explained.insert(sub.clone());
|
||||||
|
|
||||||
|
if !found_foreign {
|
||||||
|
found_foreign = true;
|
||||||
|
show_doc_note = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
diag.help(&format!("`{}` should be written as `{}`", sub, trn));
|
||||||
|
}
|
||||||
|
|
||||||
|
if show_doc_note {
|
||||||
|
diag.note(concat!(stringify!($kind), " formatting not supported; see \
|
||||||
|
the documentation for `std::fmt`"));
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
check_foreign!(printf);
|
||||||
|
if !found_foreign {
|
||||||
|
check_foreign!(shell);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
diag.emit();
|
||||||
|
}
|
||||||
|
|
||||||
cx.into_expr()
|
cx.into_expr()
|
||||||
}
|
}
|
||||||
|
|
1013
src/libsyntax_ext/format_foreign.rs
Normal file
1013
src/libsyntax_ext/format_foreign.rs
Normal file
File diff suppressed because it is too large
Load diff
|
@ -40,6 +40,7 @@ mod concat;
|
||||||
mod concat_idents;
|
mod concat_idents;
|
||||||
mod env;
|
mod env;
|
||||||
mod format;
|
mod format;
|
||||||
|
mod format_foreign;
|
||||||
mod log_syntax;
|
mod log_syntax;
|
||||||
mod trace_macros;
|
mod trace_macros;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ fn main() {
|
||||||
//~^ ERROR: argument never used
|
//~^ ERROR: argument never used
|
||||||
format!("{foo}"); //~ ERROR: no argument named `foo`
|
format!("{foo}"); //~ ERROR: no argument named `foo`
|
||||||
|
|
||||||
|
format!("", 1, 2); //~ ERROR: multiple unused formatting arguments
|
||||||
format!("{}", 1, 2); //~ ERROR: argument never used
|
format!("{}", 1, 2); //~ ERROR: argument never used
|
||||||
format!("{1}", 1, 2); //~ ERROR: argument never used
|
format!("{1}", 1, 2); //~ ERROR: argument never used
|
||||||
format!("{}", 1, foo=2); //~ ERROR: named argument never used
|
format!("{}", 1, foo=2); //~ ERROR: named argument never used
|
||||||
|
@ -53,4 +54,6 @@ fn main() {
|
||||||
|
|
||||||
format!("foo } bar"); //~ ERROR: unmatched `}` found
|
format!("foo } bar"); //~ ERROR: unmatched `}` found
|
||||||
format!("foo }"); //~ ERROR: unmatched `}` found
|
format!("foo }"); //~ ERROR: unmatched `}` found
|
||||||
|
|
||||||
|
format!("foo %s baz", "bar"); //~ ERROR: argument never used
|
||||||
}
|
}
|
||||||
|
|
20
src/test/ui/macros/format-foreign.rs
Normal file
20
src/test/ui/macros/format-foreign.rs
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
|
||||||
|
// file at the top-level directory of this distribution and at
|
||||||
|
// http://rust-lang.org/COPYRIGHT.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
|
||||||
|
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
|
||||||
|
// option. This file may not be copied, modified, or distributed
|
||||||
|
// except according to those terms.
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("%.*3$s %s!\n", "Hello,", "World", 4);
|
||||||
|
println!("%1$*2$.*3$f", 123.456);
|
||||||
|
|
||||||
|
// This should *not* produce hints, on the basis that there's equally as
|
||||||
|
// many "correct" format specifiers. It's *probably* just an actual typo.
|
||||||
|
println!("{} %f", "one", 2.0);
|
||||||
|
|
||||||
|
println!("Hi there, $NAME.", NAME="Tim");
|
||||||
|
}
|
52
src/test/ui/macros/format-foreign.stderr
Normal file
52
src/test/ui/macros/format-foreign.stderr
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
error: multiple unused formatting arguments
|
||||||
|
--> $DIR/format-foreign.rs:12:5
|
||||||
|
|
|
||||||
|
12 | println!("%.*3$s %s!/n", "Hello,", "World", 4);
|
||||||
|
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
||||||
|
note: argument never used
|
||||||
|
--> $DIR/format-foreign.rs:12:30
|
||||||
|
|
|
||||||
|
12 | println!("%.*3$s %s!/n", "Hello,", "World", 4);
|
||||||
|
| ^^^^^^^^
|
||||||
|
note: argument never used
|
||||||
|
--> $DIR/format-foreign.rs:12:40
|
||||||
|
|
|
||||||
|
12 | println!("%.*3$s %s!/n", "Hello,", "World", 4);
|
||||||
|
| ^^^^^^^
|
||||||
|
note: argument never used
|
||||||
|
--> $DIR/format-foreign.rs:12:49
|
||||||
|
|
|
||||||
|
12 | println!("%.*3$s %s!/n", "Hello,", "World", 4);
|
||||||
|
| ^
|
||||||
|
= 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`
|
||||||
|
= note: this error originates in a macro outside of the current crate
|
||||||
|
|
||||||
|
error: argument never used
|
||||||
|
--> $DIR/format-foreign.rs:13:29
|
||||||
|
|
|
||||||
|
13 | println!("%1$*2$.*3$f", 123.456);
|
||||||
|
| ^^^^^^^
|
||||||
|
|
|
||||||
|
= help: `%1$*2$.*3$f` should be written as `{0:1$.2$}`
|
||||||
|
= note: printf formatting not supported; see the documentation for `std::fmt`
|
||||||
|
|
||||||
|
error: argument never used
|
||||||
|
--> $DIR/format-foreign.rs:17:30
|
||||||
|
|
|
||||||
|
17 | println!("{} %f", "one", 2.0);
|
||||||
|
| ^^^
|
||||||
|
|
||||||
|
error: named argument never used
|
||||||
|
--> $DIR/format-foreign.rs:19:39
|
||||||
|
|
|
||||||
|
19 | println!("Hi there, $NAME.", NAME="Tim");
|
||||||
|
| ^^^^^
|
||||||
|
|
|
||||||
|
= 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
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue