diff options
| author | Mazdak Farrokhzad <twingoow@gmail.com> | 2019-08-03 00:09:06 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2019-08-03 00:09:06 +0200 |
| commit | edc846f29eac8a45555f19a9fd951ed414398776 (patch) | |
| tree | 0be1fc6ac0861dd9dcb59c83e9327e7b657c20ac /src/libsyntax_ext | |
| parent | a2735a3e0dd58e4b41b933731928678b7a2da700 (diff) | |
| parent | 22ea38dd792ec9983084a101ca6159999a9b851a (diff) | |
| download | rust-edc846f29eac8a45555f19a9fd951ed414398776.tar.gz rust-edc846f29eac8a45555f19a9fd951ed414398776.zip | |
Rollup merge of #63121 - estebank:formatting-pos, r=alexcrichton
On `format!()` arg count mismatch provide extra info
When positional width and precision formatting flags are present in a
formatting string that has an argument count mismatch, provide extra
information pointing at them making it easiser to understand where the
problem may lay:
```
error: 4 positional arguments in format string, but there are 3 arguments
--> $DIR/ifmt-bad-arg.rs:78:15
|
LL | println!("{} {:.*} {}", 1, 3.2, 4);
| ^^ ^^--^ ^^ --- this parameter corresponds to the precision flag
| |
| this precision flag adds an extra required argument at position 1, which is why there are 4 arguments expected
|
= note: positional arguments are zero-based
= note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
error: 4 positional arguments in format string, but there are 3 arguments
--> $DIR/ifmt-bad-arg.rs:81:15
|
LL | println!("{} {:07$.*} {}", 1, 3.2, 4);
| ^^ ^^-----^ ^^ --- this parameter corresponds to the precision flag
| | |
| | this precision flag adds an extra required argument at position 1, which is why there are 4 arguments expected
| this width flag expects an `usize` argument at position 7, but there are 3 arguments
|
= note: positional arguments are zero-based
= note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
error: invalid reference to positional argument 7 (there are 3 arguments)
--> $DIR/ifmt-bad-arg.rs:84:18
|
LL | println!("{} {:07$} {}", 1, 3.2, 4);
| ^^^--^
| |
| this width flag expects an `usize` argument at position 7, but there are 3 arguments
|
= note: positional arguments are zero-based
= note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
```
Fix #49384.
Diffstat (limited to 'src/libsyntax_ext')
| -rw-r--r-- | src/libsyntax_ext/format.rs | 146 |
1 files changed, 122 insertions, 24 deletions
diff --git a/src/libsyntax_ext/format.rs b/src/libsyntax_ext/format.rs index e53660b6568..fe9cad1e32f 100644 --- a/src/libsyntax_ext/format.rs +++ b/src/libsyntax_ext/format.rs @@ -109,6 +109,8 @@ struct Context<'a, 'b> { invalid_refs: Vec<(usize, usize)>, /// Spans of all the formatting arguments, in order. arg_spans: Vec<Span>, + /// All the formatting arguments that have formatting flags set, in order for diagnostics. + arg_with_formatting: Vec<parse::FormatSpec<'a>>, /// Whether this formatting string is a literal or it comes from a macro. is_literal: bool, } @@ -273,31 +275,44 @@ impl<'a, 'b> Context<'a, 'b> { } else { MultiSpan::from_span(self.fmtsp) }; - let refs_len = self.invalid_refs.len(); - let mut refs = self + let refs = self .invalid_refs .iter() .map(|(r, pos)| (r.to_string(), self.arg_spans.get(*pos))); - if self.names.is_empty() && !numbered_position_args { + let mut zero_based_note = false; + + let count = self.pieces.len() + self.arg_with_formatting + .iter() + .filter(|fmt| fmt.precision_span.is_some()) + .count(); + if self.names.is_empty() && !numbered_position_args && count != self.args.len() { 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() + count, + if count > 1 { "s" } else { "" }, + self.describe_num_args(), ), ); } else { - let (arg_list, mut sp) = if refs_len == 1 { - let (reg, pos) = refs.next().unwrap(); + let (mut refs, spans): (Vec<_>, Vec<_>) = refs.unzip(); + // Avoid `invalid reference to positional arguments 7 and 7 (there is 1 argument)` + // for `println!("{7:7$}", 1);` + refs.sort(); + refs.dedup(); + let (arg_list, mut sp) = if refs.len() == 1 { + let spans: Vec<_> = spans.into_iter().filter_map(|sp| sp.map(|sp| *sp)).collect(); ( - format!("argument {}", reg), - MultiSpan::from_span(*pos.unwrap_or(&self.fmtsp)), + format!("argument {}", refs[0]), + if spans.is_empty() { + MultiSpan::from_span(self.fmtsp) + } else { + MultiSpan::from_spans(spans) + }, ) } else { - let (mut refs, spans): (Vec<_>, Vec<_>) = refs.unzip(); let pos = MultiSpan::from_spans(spans.into_iter().map(|s| *s.unwrap()).collect()); let reg = refs.pop().unwrap(); ( @@ -317,9 +332,70 @@ impl<'a, 'b> Context<'a, 'b> { &format!("invalid reference to positional {} ({})", arg_list, self.describe_num_args())); - e.note("positional arguments are zero-based"); + zero_based_note = true; }; + for fmt in &self.arg_with_formatting { + if let Some(span) = fmt.precision_span { + let span = self.fmtsp.from_inner(span); + match fmt.precision { + parse::CountIsParam(pos) if pos > self.args.len() => { + e.span_label(span, &format!( + "this precision flag expects an `usize` argument at position {}, \ + but {}", + pos, + self.describe_num_args(), + )); + zero_based_note = true; + } + parse::CountIsParam(pos) => { + let count = self.pieces.len() + self.arg_with_formatting + .iter() + .filter(|fmt| fmt.precision_span.is_some()) + .count(); + e.span_label(span, &format!( + "this precision flag adds an extra required argument at position {}, \ + which is why there {} expected", + pos, + if count == 1 { + "is 1 argument".to_string() + } else { + format!("are {} arguments", count) + }, + )); + e.span_label( + self.args[pos].span, + "this parameter corresponds to the precision flag", + ); + zero_based_note = true; + } + _ => {} + } + } + if let Some(span) = fmt.width_span { + let span = self.fmtsp.from_inner(span); + match fmt.width { + parse::CountIsParam(pos) if pos > self.args.len() => { + e.span_label(span, &format!( + "this width flag expects an `usize` argument at position {}, \ + but {}", + pos, + self.describe_num_args(), + )); + zero_based_note = true; + } + _ => {} + } + } + } + if zero_based_note { + e.note("positional arguments are zero-based"); + } + if !self.arg_with_formatting.is_empty() { + e.note("for information about formatting flags, visit \ + https://doc.rust-lang.org/std/fmt/index.html"); + } + e.emit(); } @@ -435,10 +511,11 @@ impl<'a, 'b> Context<'a, 'b> { /// Builds a static `rt::Argument` from a `parse::Piece` or append /// to the `literal` string. - fn build_piece(&mut self, - piece: &parse::Piece<'_>, - arg_index_consumed: &mut Vec<usize>) - -> Option<P<ast::Expr>> { + fn build_piece( + &mut self, + piece: &parse::Piece<'a>, + arg_index_consumed: &mut Vec<usize>, + ) -> Option<P<ast::Expr>> { let sp = self.macsp; match *piece { parse::String(s) => { @@ -496,7 +573,9 @@ impl<'a, 'b> Context<'a, 'b> { align: parse::AlignUnknown, flags: 0, precision: parse::CountImplied, + precision_span: None, width: parse::CountImplied, + width_span: None, ty: arg.format.ty, }, }; @@ -506,6 +585,9 @@ impl<'a, 'b> Context<'a, 'b> { let pos_simple = arg.position.index() == simple_arg.position.index(); + if arg.format.precision_span.is_some() || arg.format.width_span.is_some() { + self.arg_with_formatting.push(arg.format); + } if !pos_simple || arg.format != simple_arg.format || fill != ' ' { self.all_pieces_simple = false; } @@ -530,7 +612,7 @@ impl<'a, 'b> Context<'a, 'b> { let path = self.ecx.path_global(sp, Context::rtpath(self.ecx, "FormatSpec")); let fmt = self.ecx.expr_struct( sp, - path, + path, vec