Skip to content

Commit 299cc07

Browse files
authored
Rollup merge of #66134 - estebank:unknown-formatting-trait, r=nikomatsakis
Point at formatting descriptor string when it is invalid When a formatting string contains an invalid descriptor, point at it instead of the argument: ``` error: unknown format trait `foo` --> $DIR/ifmt-bad-arg.rs:86:17 | LL | println!("{:foo}", 1); | ^^^ | = note: the only appropriate formatting traits are: - ``, which uses the `Display` trait - `?`, which uses the `Debug` trait - `e`, which uses the `LowerExp` trait - `E`, which uses the `UpperExp` trait - `o`, which uses the `Octal` trait - `p`, which uses the `Pointer` trait - `b`, which uses the `Binary` trait - `x`, which uses the `LowerHex` trait - `X`, which uses the `UpperHex` trait ```
2 parents ae0373e + 543fe5b commit 299cc07

File tree

5 files changed

+104
-59
lines changed

5 files changed

+104
-59
lines changed

src/libfmt_macros/lib.rs

+17-7
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ impl InnerOffset {
3535

3636
/// A piece is a portion of the format string which represents the next part
3737
/// to emit. These are emitted as a stream by the `Parser` class.
38-
#[derive(Copy, Clone, PartialEq)]
38+
#[derive(Copy, Clone, Debug, PartialEq)]
3939
pub enum Piece<'a> {
4040
/// A literal string which should directly be emitted
4141
String(&'a str),
@@ -45,7 +45,7 @@ pub enum Piece<'a> {
4545
}
4646

4747
/// Representation of an argument specification.
48-
#[derive(Copy, Clone, PartialEq)]
48+
#[derive(Copy, Clone, Debug, PartialEq)]
4949
pub struct Argument<'a> {
5050
/// Where to find this argument
5151
pub position: Position,
@@ -54,7 +54,7 @@ pub struct Argument<'a> {
5454
}
5555

5656
/// Specification for the formatting of an argument in the format string.
57-
#[derive(Copy, Clone, PartialEq)]
57+
#[derive(Copy, Clone, Debug, PartialEq)]
5858
pub struct FormatSpec<'a> {
5959
/// Optionally specified character to fill alignment with.
6060
pub fill: Option<char>,
@@ -74,10 +74,12 @@ pub struct FormatSpec<'a> {
7474
/// this argument, this can be empty or any number of characters, although
7575
/// it is required to be one word.
7676
pub ty: &'a str,
77+
/// The span of the descriptor string (for diagnostics).
78+
pub ty_span: Option<InnerSpan>,
7779
}
7880

7981
/// Enum describing where an argument for a format can be located.
80-
#[derive(Copy, Clone, PartialEq)]
82+
#[derive(Copy, Clone, Debug, PartialEq)]
8183
pub enum Position {
8284
/// The argument is implied to be located at an index
8385
ArgumentImplicitlyIs(usize),
@@ -97,7 +99,7 @@ impl Position {
9799
}
98100

99101
/// Enum of alignments which are supported.
100-
#[derive(Copy, Clone, PartialEq)]
102+
#[derive(Copy, Clone, Debug, PartialEq)]
101103
pub enum Alignment {
102104
/// The value will be aligned to the left.
103105
AlignLeft,
@@ -111,7 +113,7 @@ pub enum Alignment {
111113

112114
/// Various flags which can be applied to format strings. The meaning of these
113115
/// flags is defined by the formatters themselves.
114-
#[derive(Copy, Clone, PartialEq)]
116+
#[derive(Copy, Clone, Debug, PartialEq)]
115117
pub enum Flag {
116118
/// A `+` will be used to denote positive numbers.
117119
FlagSignPlus,
@@ -131,7 +133,7 @@ pub enum Flag {
131133

132134
/// A count is used for the precision and width parameters of an integer, and
133135
/// can reference either an argument or a literal integer.
134-
#[derive(Copy, Clone, PartialEq)]
136+
#[derive(Copy, Clone, Debug, PartialEq)]
135137
pub enum Count {
136138
/// The count is specified explicitly.
137139
CountIs(usize),
@@ -475,6 +477,7 @@ impl<'a> Parser<'a> {
475477
width: CountImplied,
476478
width_span: None,
477479
ty: &self.input[..0],
480+
ty_span: None,
478481
};
479482
if !self.consume(':') {
480483
return spec;
@@ -548,6 +551,7 @@ impl<'a> Parser<'a> {
548551
spec.precision_span = sp;
549552
}
550553
}
554+
let ty_span_start = self.cur.peek().map(|(pos, _)| *pos);
551555
// Optional radix followed by the actual format specifier
552556
if self.consume('x') {
553557
if self.consume('?') {
@@ -567,6 +571,12 @@ impl<'a> Parser<'a> {
567571
spec.ty = "?";
568572
} else {
569573
spec.ty = self.word();
574+
let ty_span_end = self.cur.peek().map(|(pos, _)| *pos);
575+
if !spec.ty.is_empty() {
576+
spec.ty_span = ty_span_start
577+
.and_then(|s| ty_span_end.map(|e| (s, e)))
578+
.map(|(start, end)| self.to_span_index(start).to(self.to_span_index(end)));
579+
}
570580
}
571581
spec
572582
}

src/libfmt_macros/tests.rs

+28-15
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use super::*;
22

33
fn same(fmt: &'static str, p: &[Piece<'static>]) {
44
let parser = Parser::new(fmt, None, vec![], false);
5-
assert!(parser.collect::<Vec<Piece<'static>>>() == p);
5+
assert_eq!(parser.collect::<Vec<Piece<'static>>>(), p);
66
}
77

88
fn fmtdflt() -> FormatSpec<'static> {
@@ -15,6 +15,7 @@ fn fmtdflt() -> FormatSpec<'static> {
1515
precision_span: None,
1616
width_span: None,
1717
ty: "",
18+
ty_span: None,
1819
};
1920
}
2021

@@ -82,7 +83,7 @@ fn format_position_nothing_else() {
8283
#[test]
8384
fn format_type() {
8485
same(
85-
"{3:a}",
86+
"{3:x}",
8687
&[NextArgument(Argument {
8788
position: ArgumentIs(3),
8889
format: FormatSpec {
@@ -93,7 +94,8 @@ fn format_type() {
9394
width: CountImplied,
9495
precision_span: None,
9596
width_span: None,
96-
ty: "a",
97+
ty: "x",
98+
ty_span: None,
9799
},
98100
})]);
99101
}
@@ -112,6 +114,7 @@ fn format_align_fill() {
112114
precision_span: None,
113115
width_span: None,
114116
ty: "",
117+
ty_span: None,
115118
},
116119
})]);
117120
same(
@@ -127,6 +130,7 @@ fn format_align_fill() {
127130
precision_span: None,
128131
width_span: None,
129132
ty: "",
133+
ty_span: None,
130134
},
131135
})]);
132136
same(
@@ -142,6 +146,7 @@ fn format_align_fill() {
142146
precision_span: None,
143147
width_span: None,
144148
ty: "abcd",
149+
ty_span: Some(InnerSpan::new(6, 10)),
145150
},
146151
})]);
147152
}
@@ -150,7 +155,7 @@ fn format_counts() {
150155
use syntax_pos::{GLOBALS, Globals, edition};
151156
GLOBALS.set(&Globals::new(edition::DEFAULT_EDITION), || {
152157
same(
153-
"{:10s}",
158+
"{:10x}",
154159
&[NextArgument(Argument {
155160
position: ArgumentImplicitlyIs(0),
156161
format: FormatSpec {
@@ -161,11 +166,12 @@ fn format_counts() {
161166
width: CountIs(10),
162167
precision_span: None,
163168
width_span: None,
164-
ty: "s",
169+
ty: "x",
170+
ty_span: None,
165171
},
166172
})]);
167173
same(
168-
"{:10$.10s}",
174+
"{:10$.10x}",
169175
&[NextArgument(Argument {
170176
position: ArgumentImplicitlyIs(0),
171177
format: FormatSpec {
@@ -176,11 +182,12 @@ fn format_counts() {
176182
width: CountIsParam(10),
177183
precision_span: None,
178184
width_span: Some(InnerSpan::new(3, 6)),
179-
ty: "s",
185+
ty: "x",
186+
ty_span: None,
180187
},
181188
})]);
182189
same(
183-
"{:.*s}",
190+
"{:.*x}",
184191
&[NextArgument(Argument {
185192
position: ArgumentImplicitlyIs(1),
186193
format: FormatSpec {
@@ -191,11 +198,12 @@ fn format_counts() {
191198
width: CountImplied,
192199
precision_span: Some(InnerSpan::new(3, 5)),
193200
width_span: None,
194-
ty: "s",
201+
ty: "x",
202+
ty_span: None,
195203
},
196204
})]);
197205
same(
198-
"{:.10$s}",
206+
"{:.10$x}",
199207
&[NextArgument(Argument {
200208
position: ArgumentImplicitlyIs(0),
201209
format: FormatSpec {
@@ -206,11 +214,12 @@ fn format_counts() {
206214
width: CountImplied,
207215
precision_span: Some(InnerSpan::new(3, 7)),
208216
width_span: None,
209-
ty: "s",
217+
ty: "x",
218+
ty_span: None,
210219
},
211220
})]);
212221
same(
213-
"{:a$.b$s}",
222+
"{:a$.b$?}",
214223
&[NextArgument(Argument {
215224
position: ArgumentImplicitlyIs(0),
216225
format: FormatSpec {
@@ -221,7 +230,8 @@ fn format_counts() {
221230
width: CountIsName(Symbol::intern("a")),
222231
precision_span: None,
223232
width_span: None,
224-
ty: "s",
233+
ty: "?",
234+
ty_span: None,
225235
},
226236
})]);
227237
});
@@ -241,6 +251,7 @@ fn format_flags() {
241251
precision_span: None,
242252
width_span: None,
243253
ty: "",
254+
ty_span: None,
244255
},
245256
})]);
246257
same(
@@ -256,13 +267,14 @@ fn format_flags() {
256267
precision_span: None,
257268
width_span: None,
258269
ty: "",
270+
ty_span: None,
259271
},
260272
})]);
261273
}
262274
#[test]
263275
fn format_mixture() {
264276
same(
265-
"abcd {3:a} efg",
277+
"abcd {3:x} efg",
266278
&[
267279
String("abcd "),
268280
NextArgument(Argument {
@@ -275,7 +287,8 @@ fn format_mixture() {
275287
width: CountImplied,
276288
precision_span: None,
277289
width_span: None,
278-
ty: "a",
290+
ty: "x",
291+
ty_span: None,
279292
},
280293
}),
281294
String(" efg"),

src/libsyntax_ext/format.rs

+55-33
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ use std::collections::hash_map::Entry;
2121

2222
#[derive(PartialEq)]
2323
enum ArgumentType {
24-
Placeholder(String),
24+
Placeholder(&'static str),
2525
Count,
2626
}
2727

@@ -244,7 +244,57 @@ impl<'a, 'b> Context<'a, 'b> {
244244
parse::ArgumentNamed(s) => Named(s),
245245
};
246246

247-
let ty = Placeholder(arg.format.ty.to_string());
247+
let ty = Placeholder(match &arg.format.ty[..] {
248+
"" => "Display",
249+
"?" => "Debug",
250+
"e" => "LowerExp",
251+
"E" => "UpperExp",
252+
"o" => "Octal",
253+
"p" => "Pointer",
254+
"b" => "Binary",
255+
"x" => "LowerHex",
256+
"X" => "UpperHex",
257+
_ => {
258+
let fmtsp = self.fmtsp;
259+
let sp = arg.format.ty_span.map(|sp| fmtsp.from_inner(sp));
260+
let mut err = self.ecx.struct_span_err(
261+
sp.unwrap_or(fmtsp),
262+
&format!("unknown format trait `{}`", arg.format.ty),
263+
);
264+
err.note("the only appropriate formatting traits are:\n\
265+
- ``, which uses the `Display` trait\n\
266+
- `?`, which uses the `Debug` trait\n\
267+
- `e`, which uses the `LowerExp` trait\n\
268+
- `E`, which uses the `UpperExp` trait\n\
269+
- `o`, which uses the `Octal` trait\n\
270+
- `p`, which uses the `Pointer` trait\n\
271+
- `b`, which uses the `Binary` trait\n\
272+
- `x`, which uses the `LowerHex` trait\n\
273+
- `X`, which uses the `UpperHex` trait");
274+
if let Some(sp) = sp {
275+
for (fmt, name) in &[
276+
("", "Display"),
277+
("?", "Debug"),
278+
("e", "LowerExp"),
279+
("E", "UpperExp"),
280+
("o", "Octal"),
281+
("p", "Pointer"),
282+
("b", "Binary"),
283+
("x", "LowerHex"),
284+
("X", "UpperHex"),
285+
] {
286+
err.tool_only_span_suggestion(
287+
sp,
288+
&format!("use the `{}` trait", name),
289+
fmt.to_string(),
290+
Applicability::MaybeIncorrect,
291+
);
292+
}
293+
}
294+
err.emit();
295+
"<invalid>"
296+
}
297+
});
248298
self.verify_arg_type(pos, ty);
249299
self.curpiece += 1;
250300
}
@@ -590,6 +640,7 @@ impl<'a, 'b> Context<'a, 'b> {
590640
width: parse::CountImplied,
591641
width_span: None,
592642
ty: arg.format.ty,
643+
ty_span: arg.format.ty_span,
593644
},
594645
};
595646

@@ -761,37 +812,8 @@ impl<'a, 'b> Context<'a, 'b> {
761812
sp = ecx.with_def_site_ctxt(sp);
762813
let arg = ecx.expr_ident(sp, arg);
763814
let trait_ = match *ty {
764-
Placeholder(ref tyname) => {
765-
match &tyname[..] {
766-
"" => "Display",
767-
"?" => "Debug",
768-
"e" => "LowerExp",
769-
"E" => "UpperExp",
770-
"o" => "Octal",
771-
"p" => "Pointer",
772-
"b" => "Binary",
773-
"x" => "LowerHex",
774-
"X" => "UpperHex",
775-
_ => {
776-
let mut err = ecx.struct_span_err(
777-
sp,
778-
&format!("unknown format trait `{}`", *tyname),
779-
);
780-
err.note("the only appropriate formatting traits are:\n\
781-
- ``, which uses the `Display` trait\n\
782-
- `?`, which uses the `Debug` trait\n\
783-
- `e`, which uses the `LowerExp` trait\n\
784-
- `E`, which uses the `UpperExp` trait\n\
785-
- `o`, which uses the `Octal` trait\n\
786-
- `p`, which uses the `Pointer` trait\n\
787-
- `b`, which uses the `Binary` trait\n\
788-
- `x`, which uses the `LowerHex` trait\n\
789-
- `X`, which uses the `UpperHex` trait");
790-
err.emit();
791-
return DummyResult::raw_expr(sp, true);
792-
}
793-
}
794-
}
815+
Placeholder(trait_) if trait_ == "<invalid>" => return DummyResult::raw_expr(sp, true),
816+
Placeholder(trait_) => trait_,
795817
Count => {
796818
let path = ecx.std_path(&[sym::fmt, sym::ArgumentV1, sym::from_usize]);
797819
return ecx.expr_call_global(macsp, path, vec![arg]);

src/test/ui/if/ifmt-bad-arg.stderr

+2-2
Original file line numberDiff line numberDiff line change
@@ -257,10 +257,10 @@ LL | println!("{} {:07$} {}", 1, 3.2, 4);
257257
= note: for information about formatting flags, visit https://doc.rust-lang.org/std/fmt/index.html
258258

259259
error: unknown format trait `foo`
260-
--> $DIR/ifmt-bad-arg.rs:86:24
260+
--> $DIR/ifmt-bad-arg.rs:86:17
261261
|
262262
LL | println!("{:foo}", 1);
263-
| ^
263+
| ^^^
264264
|
265265
= note: the only appropriate formatting traits are:
266266
- ``, which uses the `Display` trait

0 commit comments

Comments
 (0)