Skip to content

Commit edc846f

Browse files
authored
Rollup merge of rust-lang#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 rust-lang#49384.
2 parents a2735a3 + 22ea38d commit edc846f

File tree

7 files changed

+463
-195
lines changed

7 files changed

+463
-195
lines changed

src/libfmt_macros/lib.rs

+48-24
Original file line numberDiff line numberDiff line change
@@ -56,16 +56,20 @@ pub struct Argument<'a> {
5656
/// Specification for the formatting of an argument in the format string.
5757
#[derive(Copy, Clone, PartialEq)]
5858
pub struct FormatSpec<'a> {
59-
/// Optionally specified character to fill alignment with
59+
/// Optionally specified character to fill alignment with.
6060
pub fill: Option<char>,
61-
/// Optionally specified alignment
61+
/// Optionally specified alignment.
6262
pub align: Alignment,
63-
/// Packed version of various flags provided
63+
/// Packed version of various flags provided.
6464
pub flags: u32,
65-
/// The integer precision to use
65+
/// The integer precision to use.
6666
pub precision: Count,
67-
/// The string width requested for the resulting format
67+
/// The span of the precision formatting flag (for diagnostics).
68+
pub precision_span: Option<InnerSpan>,
69+
/// The string width requested for the resulting format.
6870
pub width: Count,
71+
/// The span of the width formatting flag (for diagnostics).
72+
pub width_span: Option<InnerSpan>,
6973
/// The descriptor string representing the name of the format desired for
7074
/// this argument, this can be empty or any number of characters, although
7175
/// it is required to be one word.
@@ -282,19 +286,24 @@ impl<'a> Parser<'a> {
282286
}
283287

284288
/// Optionally consumes the specified character. If the character is not at
285-
/// the current position, then the current iterator isn't moved and false is
286-
/// returned, otherwise the character is consumed and true is returned.
289+
/// the current position, then the current iterator isn't moved and `false` is
290+
/// returned, otherwise the character is consumed and `true` is returned.
287291
fn consume(&mut self, c: char) -> bool {
288-
if let Some(&(_, maybe)) = self.cur.peek() {
292+
self.consume_pos(c).is_some()
293+
}
294+
295+
/// Optionally consumes the specified character. If the character is not at
296+
/// the current position, then the current iterator isn't moved and `None` is
297+
/// returned, otherwise the character is consumed and the current position is
298+
/// returned.
299+
fn consume_pos(&mut self, c: char) -> Option<usize> {
300+
if let Some(&(pos, maybe)) = self.cur.peek() {
289301
if c == maybe {
290302
self.cur.next();
291-
true
292-
} else {
293-
false
303+
return Some(pos);
294304
}
295-
} else {
296-
false
297305
}
306+
None
298307
}
299308

300309
fn to_span_index(&self, pos: usize) -> InnerOffset {
@@ -462,7 +471,9 @@ impl<'a> Parser<'a> {
462471
align: AlignUnknown,
463472
flags: 0,
464473
precision: CountImplied,
474+
precision_span: None,
465475
width: CountImplied,
476+
width_span: None,
466477
ty: &self.input[..0],
467478
};
468479
if !self.consume(':') {
@@ -499,6 +510,7 @@ impl<'a> Parser<'a> {
499510
}
500511
// Width and precision
501512
let mut havewidth = false;
513+
502514
if self.consume('0') {
503515
// small ambiguity with '0$' as a format string. In theory this is a
504516
// '0' flag and then an ill-formatted format string with just a '$'
@@ -512,17 +524,28 @@ impl<'a> Parser<'a> {
512524
}
513525
}
514526
if !havewidth {
515-
spec.width = self.count();
527+
let width_span_start = if let Some((pos, _)) = self.cur.peek() {
528+
*pos
529+
} else {
530+
0
531+
};
532+
let (w, sp) = self.count(width_span_start);
533+
spec.width = w;
534+
spec.width_span = sp;
516535
}
517-
if self.consume('.') {
518-
if self.consume('*') {
536+
if let Some(start) = self.consume_pos('.') {
537+
if let Some(end) = self.consume_pos('*') {
519538
// Resolve `CountIsNextParam`.
520539
// We can do this immediately as `position` is resolved later.
521540
let i = self.curarg;
522541
self.curarg += 1;
523542
spec.precision = CountIsParam(i);
543+
spec.precision_span =
544+
Some(self.to_span_index(start).to(self.to_span_index(end + 1)));
524545
} else {
525-
spec.precision = self.count();
546+
let (p, sp) = self.count(start);
547+
spec.precision = p;
548+
spec.precision_span = sp;
526549
}
527550
}
528551
// Optional radix followed by the actual format specifier
@@ -551,24 +574,25 @@ impl<'a> Parser<'a> {
551574
/// Parses a Count parameter at the current position. This does not check
552575
/// for 'CountIsNextParam' because that is only used in precision, not
553576
/// width.
554-
fn count(&mut self) -> Count {
577+
fn count(&mut self, start: usize) -> (Count, Option<InnerSpan>) {
555578
if let Some(i) = self.integer() {
556-
if self.consume('$') {
557-
CountIsParam(i)
579+
if let Some(end) = self.consume_pos('$') {
580+
let span = self.to_span_index(start).to(self.to_span_index(end + 1));
581+
(CountIsParam(i), Some(span))
558582
} else {
559-
CountIs(i)
583+
(CountIs(i), None)
560584
}
561585
} else {
562586
let tmp = self.cur.clone();
563587
let word = self.word();
564588
if word.is_empty() {
565589
self.cur = tmp;
566-
CountImplied
590+
(CountImplied, None)
567591
} else if self.consume('$') {
568-
CountIsName(Symbol::intern(word))
592+
(CountIsName(Symbol::intern(word)), None)
569593
} else {
570594
self.cur = tmp;
571-
CountImplied
595+
(CountImplied, None)
572596
}
573597
}
574598
}

0 commit comments

Comments
 (0)