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

@ -154,6 +154,8 @@ pub struct Parser<'a> {
style: Option<usize>,
/// How many newlines have been seen in the string so far, to adjust the error spans
seen_newlines: usize,
/// Start and end byte offset of every successfuly parsed argument
pub arg_places: Vec<(usize, usize)>,
}
impl<'a> Iterator for Parser<'a> {
@ -168,9 +170,13 @@ impl<'a> Iterator for Parser<'a> {
if self.consume('{') {
Some(String(self.string(pos + 1)))
} else {
let ret = Some(NextArgument(self.argument()));
self.must_consume('}');
ret
let mut arg = self.argument();
if let Some(arg_pos) = self.must_consume('}').map(|end| {
(pos + raw + 1, end + raw + 2)
}) {
self.arg_places.push(arg_pos);
}
Some(NextArgument(arg))
}
}
'}' => {
@ -211,6 +217,7 @@ impl<'a> Parser<'a> {
curarg: 0,
style,
seen_newlines: 0,
arg_places: vec![],
}
}
@ -271,7 +278,7 @@ impl<'a> Parser<'a> {
/// Forces consumption of the specified character. If the character is not
/// found, an error is emitted.
fn must_consume(&mut self, c: char) {
fn must_consume(&mut self, c: char) -> Option<usize> {
self.ws();
let raw = self.style.unwrap_or(0);
@ -279,12 +286,14 @@ impl<'a> Parser<'a> {
if let Some(&(pos, maybe)) = self.cur.peek() {
if c == maybe {
self.cur.next();
Some(pos)
} else {
let pos = pos + padding + 1;
self.err(format!("expected `{:?}`, found `{:?}`", c, maybe),
format!("expected `{}`", c),
pos,
pos);
None
}
} else {
let msg = format!("expected `{:?}` but string was terminated", c);
@ -302,6 +311,7 @@ impl<'a> Parser<'a> {
} else {
self.err(msg, format!("expected `{:?}`", c), pos, pos);
}
None
}
}

View File

@ -14,18 +14,18 @@ use self::Position::*;
use fmt_macros as parse;
use syntax::ast;
use syntax::ext::base::*;
use syntax::ext::base;
use syntax::ext::base::*;
use syntax::ext::build::AstBuilder;
use syntax::feature_gate;
use syntax::parse::token;
use syntax::ptr::P;
use syntax::symbol::Symbol;
use syntax_pos::{Span, DUMMY_SP};
use syntax::tokenstream;
use syntax_pos::{MultiSpan, Span, DUMMY_SP};
use std::collections::{HashMap, HashSet};
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
#[derive(PartialEq)]
enum ArgumentType {
@ -111,8 +111,14 @@ struct Context<'a, 'b: 'a> {
/// still existed in this phase of processing.
/// Used only for `all_pieces_simple` tracking in `build_piece`.
curarg: usize,
/// Keep track of invalid references to positional arguments
invalid_refs: Vec<usize>,
/// Current piece being evaluated, used for error reporting.
curpiece: usize,
/// Keep track of invalid references to positional arguments.
invalid_refs: Vec<(usize, usize)>,
/// Spans of all the formatting arguments, in order.
arg_spans: Vec<Span>,
/// Wether this formatting string is a literal or it comes from a macro.
is_literal: bool,
}
/// Parses the arguments from the given list of tokens, returning None
@ -155,15 +161,20 @@ fn parse_args(ecx: &mut ExtCtxt,
i
}
_ if named => {
ecx.span_err(p.span,
"expected ident, positional arguments \
cannot follow named arguments");
ecx.span_err(
p.span,
"expected ident, positional arguments cannot follow named arguments",
);
return None;
}
_ => {
ecx.span_err(p.span,
&format!("expected ident for named argument, found `{}`",
p.this_token_to_string()));
ecx.span_err(
p.span,
&format!(
"expected ident for named argument, found `{}`",
p.this_token_to_string()
),
);
return None;
}
};
@ -235,6 +246,7 @@ impl<'a, 'b> Context<'a, 'b> {
let ty = Placeholder(arg.format.ty.to_string());
self.verify_arg_type(pos, ty);
self.curpiece += 1;
}
}
}
@ -266,29 +278,59 @@ impl<'a, 'b> Context<'a, 'b> {
/// format string.
fn report_invalid_references(&self, numbered_position_args: bool) {
let mut e;
let mut refs: Vec<String> = self.invalid_refs
.iter()
.map(|r| r.to_string())
.collect();
let sp = if self.is_literal {
MultiSpan::from_spans(self.arg_spans.clone())
} else {
MultiSpan::from_span(self.fmtsp)
};
let mut refs: Vec<_> = self
.invalid_refs
.iter()
.map(|(r, pos)| (r.to_string(), self.arg_spans.get(*pos)))
.collect();
if self.names.is_empty() && !numbered_position_args {
e = self.ecx.mut_span_err(self.fmtsp,
&format!("{} positional argument{} in format string, but {}",
e = self.ecx.mut_span_err(
sp,
&format!(
"{} positional argument{} in format string, but {}",
self.pieces.len(),
if self.pieces.len() > 1 { "s" } else { "" },
self.describe_num_args()));
self.describe_num_args()
),
);
} else {
let arg_list = match refs.len() {
1 => format!("argument {}", refs.pop().unwrap()),
_ => format!("arguments {head} and {tail}",
tail=refs.pop().unwrap(),
head=refs.join(", "))
let (arg_list, mut sp) = match refs.len() {
1 => {
let (reg, pos) = refs.pop().unwrap();
(
format!("argument {}", reg),
MultiSpan::from_span(*pos.unwrap_or(&self.fmtsp)),
)
}
_ => {
let pos =
MultiSpan::from_spans(refs.iter().map(|(_, p)| *p.unwrap()).collect());
let mut refs: Vec<String> = refs.iter().map(|(s, _)| s.to_owned()).collect();
let reg = refs.pop().unwrap();
(
format!(
"arguments {head} and {tail}",
tail = reg,
head = refs.join(", ")
),
pos,
)
}
};
if !self.is_literal {
sp = MultiSpan::from_span(self.fmtsp);
}
e = self.ecx.mut_span_err(self.fmtsp,
e = self.ecx.mut_span_err(sp,
&format!("invalid reference to positional {} ({})",
arg_list,
self.describe_num_args()));
arg_list,
self.describe_num_args()));
e.note("positional arguments are zero-based");
};
@ -301,7 +343,7 @@ impl<'a, 'b> Context<'a, 'b> {
match arg {
Exact(arg) => {
if self.args.len() <= arg {
self.invalid_refs.push(arg);
self.invalid_refs.push((arg, self.curpiece));
return;
}
match ty {
@ -337,7 +379,13 @@ impl<'a, 'b> Context<'a, 'b> {
Some(e) => *e,
None => {
let msg = format!("there is no argument named `{}`", name);
self.ecx.span_err(self.fmtsp, &msg[..]);
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[..]);
err.emit();
return;
}
};
@ -505,33 +553,27 @@ impl<'a, 'b> Context<'a, 'b> {
let prec = self.build_count(arg.format.precision);
let width = self.build_count(arg.format.width);
let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "FormatSpec"));
let fmt =
self.ecx.expr_struct(sp,
let fmt = self.ecx.expr_struct(
sp,
path,
vec![self.ecx
.field_imm(sp, self.ecx.ident_of("fill"), fill),
self.ecx.field_imm(sp,
self.ecx.ident_of("align"),
align),
self.ecx.field_imm(sp,
self.ecx.ident_of("flags"),
flags),
self.ecx.field_imm(sp,
self.ecx.ident_of("precision"),
prec),
self.ecx.field_imm(sp,
self.ecx.ident_of("width"),
width)]);
vec![
self.ecx.field_imm(sp, self.ecx.ident_of("fill"), fill),
self.ecx.field_imm(sp, self.ecx.ident_of("align"), align),
self.ecx.field_imm(sp, self.ecx.ident_of("flags"), flags),
self.ecx.field_imm(sp, self.ecx.ident_of("precision"), prec),
self.ecx.field_imm(sp, self.ecx.ident_of("width"), width),
],
);
let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "Argument"));
Some(self.ecx.expr_struct(sp,
Some(self.ecx.expr_struct(
sp,
path,
vec![self.ecx.field_imm(sp,
self.ecx.ident_of("position"),
pos),
self.ecx.field_imm(sp,
self.ecx.ident_of("format"),
fmt)]))
vec![
self.ecx.field_imm(sp, self.ecx.ident_of("position"), pos),
self.ecx.field_imm(sp, self.ecx.ident_of("format"), fmt),
],
))
}
}
}
@ -544,9 +586,9 @@ impl<'a, 'b> Context<'a, 'b> {
let mut pats = Vec::new();
let mut heads = Vec::new();
let names_pos: Vec<_> = (0..self.args.len()).map(|i| {
self.ecx.ident_of(&format!("arg{}", i)).gensym()
}).collect();
let names_pos: Vec<_> = (0..self.args.len())
.map(|i| self.ecx.ident_of(&format!("arg{}", i)).gensym())
.collect();
// First, build up the static array which will become our precompiled
// format "string"
@ -690,10 +732,11 @@ pub fn expand_format_args<'cx>(ecx: &'cx mut ExtCtxt,
}
}
pub fn expand_format_args_nl<'cx>(ecx: &'cx mut ExtCtxt,
mut sp: Span,
tts: &[tokenstream::TokenTree])
-> Box<dyn base::MacResult + 'cx> {
pub fn expand_format_args_nl<'cx>(
ecx: &'cx mut ExtCtxt,
mut sp: Span,
tts: &[tokenstream::TokenTree],
) -> Box<dyn base::MacResult + 'cx> {
//if !ecx.ecfg.enable_allow_internal_unstable() {
// For some reason, the only one that actually works for `println` is the first check
@ -744,7 +787,6 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
let sugg_fmt = match args.len() {
0 => "{}".to_string(),
_ => format!("{}{{}}", "{} ".repeat(args.len())),
};
err.span_suggestion(
fmt_sp.shrink_to_lo(),
@ -753,7 +795,11 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
);
err.emit();
return DummyResult::raw_expr(sp);
},
}
};
let is_literal = match ecx.codemap().span_to_snippet(fmt_sp) {
Ok(ref s) if s.starts_with("\"") || s.starts_with("r#") => true,
_ => false,
};
let mut cx = Context {
@ -763,6 +809,7 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
arg_unique_types,
names,
curarg: 0,
curpiece: 0,
arg_index_map: Vec::new(),
count_args: Vec::new(),
count_positions: HashMap::new(),
@ -775,6 +822,8 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
macsp,
fmtsp: fmt.span,
invalid_refs: Vec::new(),
arg_spans: Vec::new(),
is_literal,
};
let fmt_str = &*fmt.node.0.as_str();
@ -783,12 +832,22 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
ast::StrStyle::Raw(raw) => Some(raw as usize),
};
let mut parser = parse::Parser::new(fmt_str, str_style);
let mut unverified_pieces = vec![];
let mut pieces = vec![];
while let Some(mut piece) = parser.next() {
while let Some(piece) = parser.next() {
if !parser.errors.is_empty() {
break;
}
unverified_pieces.push(piece);
}
cx.arg_spans = parser.arg_places.iter()
.map(|&(start, end)| fmt.span.from_inner_byte_pos(start, end))
.collect();
// This needs to happen *after* the Parser has consumed all pieces to create all the spans
for mut piece in unverified_pieces {
cx.verify_piece(&piece);
cx.resolve_name_inplace(&mut piece);
pieces.push(piece);
@ -856,24 +915,27 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
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 errs_len = errs.len();
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 {
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(
errs.iter().map(|&(sp, _)| sp).collect::<Vec<Span>>(),
"multiple unused formatting arguments"
"multiple unused formatting arguments",
);
diag.span_label(cx.fmtsp, "multiple missing formatting arguments");
diag.span_label(cx.fmtsp, "multiple missing formatting specifiers");
diag
}
};
// Used to ensure we only report translations for *one* kind of foreign format.
let mut found_foreign = false;
// Decide if we want to look for foreign formatting directives.
if args_used < args_unused {
use super::format_foreign as foreign;
@ -882,13 +944,11 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
// 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;
let mut suggestions = vec![];
for sub in foreign::$kind::iter_subs(fmt_str) {
let trn = match sub.translate() {
Some(trn) => trn,
@ -897,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;
@ -908,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 {
@ -917,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 use curly braces",
suggestions,
);
}
}};
}
@ -925,6 +999,9 @@ pub fn expand_preparsed_format_args(ecx: &mut ExtCtxt,
check_foreign!(shell);
}
}
if !found_foreign && errs_len == 1 {
diag.span_label(cx.fmtsp, "formatting specifier missing");
}
diag.emit();
}

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),

View File

@ -64,4 +64,11 @@ fn main() {
format!("foo }"); //~ ERROR: unmatched `}` found
format!("foo %s baz", "bar"); //~ ERROR: argument never used
format!(r##"
{foo}
"##);
//~^^^ ERROR: there is no argument named `foo`
}

View File

@ -0,0 +1,208 @@
error: 1 positional argument in format string, but no arguments were given
--> $DIR/ifmt-bad-arg.rs:16:14
|
LL | format!("{}");
| ^^
error: invalid reference to positional argument 1 (there is 1 argument)
--> $DIR/ifmt-bad-arg.rs:19:14
|
LL | format!("{1}", 1);
| ^^^
|
= note: positional arguments are zero-based
error: argument never used
--> $DIR/ifmt-bad-arg.rs:19:20
|
LL | format!("{1}", 1);
| ----- ^
| |
| formatting specifier missing
error: 2 positional arguments in format string, but no arguments were given
--> $DIR/ifmt-bad-arg.rs:23:14
|
LL | format!("{} {}");
| ^^ ^^
error: invalid reference to positional argument 1 (there is 1 argument)
--> $DIR/ifmt-bad-arg.rs:26:18
|
LL | format!("{0} {1}", 1);
| ^^^
|
= note: positional arguments are zero-based
error: invalid reference to positional argument 2 (there are 2 arguments)
--> $DIR/ifmt-bad-arg.rs:29:22
|
LL | format!("{0} {1} {2}", 1, 2);
| ^^^
|
= note: positional arguments are zero-based
error: invalid reference to positional argument 2 (there are 2 arguments)
--> $DIR/ifmt-bad-arg.rs:32:28
|
LL | format!("{} {value} {} {}", 1, value=2);
| ^^
|
= note: positional arguments are zero-based
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
error: there is no argument named `foo`
--> $DIR/ifmt-bad-arg.rs:37:17
|
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
| ^^^^^
error: there is no argument named `bar`
--> $DIR/ifmt-bad-arg.rs:37:26
|
LL | format!("{} {foo} {} {bar} {}", 1, 2, 3);
| ^^^^^
error: there is no argument named `foo`
--> $DIR/ifmt-bad-arg.rs:41:14
|
LL | format!("{foo}"); //~ ERROR: no argument named `foo`
| ^^^^^
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
error: argument never used
--> $DIR/ifmt-bad-arg.rs:43:22
|
LL | format!("{}", 1, 2); //~ ERROR: argument never used
| ---- ^
| |
| formatting specifier missing
error: argument never used
--> $DIR/ifmt-bad-arg.rs:44:20
|
LL | format!("{1}", 1, 2); //~ ERROR: argument never used
| ----- ^
| |
| formatting specifier missing
error: named argument never used
--> $DIR/ifmt-bad-arg.rs:45:26
|
LL | format!("{}", 1, foo=2); //~ ERROR: named argument never used
| ---- ^
| |
| formatting specifier missing
error: argument never used
--> $DIR/ifmt-bad-arg.rs:46:22
|
LL | format!("{foo}", 1, foo=2); //~ ERROR: argument never used
| ------- ^
| |
| formatting specifier missing
error: named argument never used
--> $DIR/ifmt-bad-arg.rs:47:21
|
LL | format!("", foo=2); //~ ERROR: named argument never used
| -- ^
| |
| formatting specifier missing
error: multiple unused formatting arguments
--> $DIR/ifmt-bad-arg.rs:48:32
|
LL | format!("{} {}", 1, 2, foo=1, bar=2); //~ ERROR: multiple unused formatting arguments
| ------- ^ ^
| |
| multiple missing formatting specifiers
error: duplicate argument named `foo`
--> $DIR/ifmt-bad-arg.rs:50:33
|
LL | format!("{foo}", foo=1, foo=2); //~ ERROR: duplicate argument
| ^
|
note: previously here
--> $DIR/ifmt-bad-arg.rs:50:26
|
LL | format!("{foo}", foo=1, foo=2); //~ ERROR: duplicate argument
| ^
error: expected ident, positional arguments cannot follow named arguments
--> $DIR/ifmt-bad-arg.rs:51:24
|
LL | format!("", foo=1, 2); //~ ERROR: positional arguments cannot follow
| ^
error: there is no argument named `valueb`
--> $DIR/ifmt-bad-arg.rs:55:23
|
LL | format!("{valuea} {valueb}", valuea=5, valuec=7);
| ^^^^^^^^
error: named argument never used
--> $DIR/ifmt-bad-arg.rs:55:51
|
LL | format!("{valuea} {valueb}", valuea=5, valuec=7);
| ------------------- ^
| |
| formatting specifier missing
error: invalid format string: expected `'}'` but string was terminated
--> $DIR/ifmt-bad-arg.rs:61:15
|
LL | format!("{"); //~ ERROR: expected `'}'` but string was terminated
| ^ expected `'}'` in format string
|
= note: if you intended to print `{`, you can escape it using `{{`
error: invalid format string: unmatched `}` found
--> $DIR/ifmt-bad-arg.rs:63:18
|
LL | format!("foo } bar"); //~ ERROR: unmatched `}` found
| ^ unmatched `}` in format string
|
= note: if you intended to print `}`, you can escape it using `}}`
error: invalid format string: unmatched `}` found
--> $DIR/ifmt-bad-arg.rs:64:18
|
LL | format!("foo }"); //~ ERROR: unmatched `}` found
| ^ unmatched `}` in format string
|
= note: if you intended to print `}`, you can escape it using `}}`
error: argument never used
--> $DIR/ifmt-bad-arg.rs:66:27
|
LL | format!("foo %s baz", "bar"); //~ ERROR: argument never used
| -- ^^^^^
| |
| help: format specifiers use curly braces: `{}`
|
= note: printf formatting not supported; see the documentation for `std::fmt`
error: there is no argument named `foo`
--> $DIR/ifmt-bad-arg.rs:70:9
|
LL | {foo}
| ^^^^^
error: aborting due to 27 previous errors

View File

@ -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.

View File

@ -4,29 +4,52 @@ error: multiple unused formatting arguments
LL | println!("%.*3$s %s!/n", "Hello,", "World", 4); //~ ERROR multiple unused formatting arguments
| -------------- ^^^^^^^^ ^^^^^^^ ^
| |
| multiple missing formatting arguments
| 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 use curly braces
|
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 use curly braces: `{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 use curly braces
|
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
| ^^^
| ------- ^^^
| |
| formatting specifier missing
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 +57,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

View File

@ -4,13 +4,13 @@ error: multiple unused formatting arguments
LL | println!("Test", 123, 456, 789);
| ------ ^^^ ^^^ ^^^
| |
| multiple missing formatting arguments
| multiple missing formatting specifiers
error: multiple unused formatting arguments
--> $DIR/format-unused-lables.rs:16:9
|
LL | println!("Test2",
| ------- multiple missing formatting arguments
| ------- multiple missing formatting specifiers
LL | 123, //~ ERROR multiple unused formatting arguments
| ^^^
LL | 456,
@ -22,13 +22,15 @@ error: named argument never used
--> $DIR/format-unused-lables.rs:21:35
|
LL | println!("Some stuff", UNUSED="args"); //~ ERROR named argument never used
| ^^^^^^
| ------------ ^^^^^^
| |
| formatting specifier missing
error: multiple unused formatting arguments
--> $DIR/format-unused-lables.rs:24:9
|
LL | println!("Some more $STUFF",
| ------------------ multiple missing formatting arguments
| ------------------ multiple missing formatting specifiers
LL | "woo!", //~ ERROR multiple unused formatting arguments
| ^^^^^^
LL | STUFF=