Skip to content

Commit 94e8a24

Browse files
authored
Rollup merge of #141474 - mejrs:diagnostic_mode, r=compiler-errors
Add `ParseMode::Diagnostic` and fix multiline spans in diagnostic attribute lints Best viewed commit by commit. The first commit is a test, the commits following that are small refactors to `rustc_parse_format`. Originally I wanted to do a much larger change (doing these smaller fixes first would have that made easier to review), but ended up doing something else instead. An observable change from this is that the diagnostic attribute no longer tries to parse align/fill/width/etc parameters. For an example (see also test changes), a string like `"{Self:!}"` no longer says "missing '}'", instead it says that format parameters are not allowed. It'll now also format the string as if the user wrote just `"{Self}"`
2 parents 2591439 + ba1c650 commit 94e8a24

File tree

9 files changed

+451
-279
lines changed

9 files changed

+451
-279
lines changed

compiler/rustc_parse_format/src/lib.rs

Lines changed: 136 additions & 163 deletions
Large diffs are not rendered by default.

compiler/rustc_parse_format/src/tests.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,3 +553,45 @@ fn asm_concat() {
553553
assert_eq!(parser.by_ref().collect::<Vec<Piece<'static>>>(), &[Lit(asm)]);
554554
assert_eq!(parser.line_spans, &[]);
555555
}
556+
557+
#[test]
558+
fn diagnostic_format_flags() {
559+
let lit = "{thing:blah}";
560+
let mut parser = Parser::new(lit, None, None, false, ParseMode::Diagnostic);
561+
assert!(!parser.is_source_literal);
562+
563+
let [NextArgument(arg)] = &*parser.by_ref().collect::<Vec<Piece<'static>>>() else { panic!() };
564+
565+
assert_eq!(
566+
**arg,
567+
Argument {
568+
position: ArgumentNamed("thing"),
569+
position_span: 2..7,
570+
format: FormatSpec { ty: ":blah", ty_span: Some(7..12), ..Default::default() },
571+
}
572+
);
573+
574+
assert_eq!(parser.line_spans, &[]);
575+
assert!(parser.errors.is_empty());
576+
}
577+
578+
#[test]
579+
fn diagnostic_format_mod() {
580+
let lit = "{thing:+}";
581+
let mut parser = Parser::new(lit, None, None, false, ParseMode::Diagnostic);
582+
assert!(!parser.is_source_literal);
583+
584+
let [NextArgument(arg)] = &*parser.by_ref().collect::<Vec<Piece<'static>>>() else { panic!() };
585+
586+
assert_eq!(
587+
**arg,
588+
Argument {
589+
position: ArgumentNamed("thing"),
590+
position_span: 2..7,
591+
format: FormatSpec { ty: ":+", ty_span: Some(7..9), ..Default::default() },
592+
}
593+
);
594+
595+
assert_eq!(parser.line_spans, &[]);
596+
assert!(parser.errors.is_empty());
597+
}

compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs

Lines changed: 18 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -810,7 +810,8 @@ impl<'tcx> OnUnimplementedFormatString {
810810

811811
let mut result = Ok(());
812812

813-
match FormatString::parse(self.symbol, self.span, &ctx) {
813+
let snippet = tcx.sess.source_map().span_to_snippet(self.span).ok();
814+
match FormatString::parse(self.symbol, snippet, self.span, &ctx) {
814815
// Warnings about format specifiers, deprecated parameters, wrong parameters etc.
815816
// In other words we'd like to let the author know, but we can still try to format the string later
816817
Ok(FormatString { warnings, .. }) => {
@@ -848,34 +849,27 @@ impl<'tcx> OnUnimplementedFormatString {
848849
}
849850
}
850851
}
851-
// Errors from the underlying `rustc_parse_format::Parser`
852-
Err(errors) => {
852+
// Error from the underlying `rustc_parse_format::Parser`
853+
Err(e) => {
853854
// we cannot return errors from processing the format string as hard error here
854855
// as the diagnostic namespace guarantees that malformed input cannot cause an error
855856
//
856857
// if we encounter any error while processing we nevertheless want to show it as warning
857858
// so that users are aware that something is not correct
858-
for e in errors {
859-
if self.is_diagnostic_namespace_variant {
860-
if let Some(trait_def_id) = trait_def_id.as_local() {
861-
tcx.emit_node_span_lint(
862-
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
863-
tcx.local_def_id_to_hir_id(trait_def_id),
864-
self.span,
865-
WrappedParserError { description: e.description, label: e.label },
866-
);
867-
}
868-
} else {
869-
let reported = struct_span_code_err!(
870-
tcx.dcx(),
859+
if self.is_diagnostic_namespace_variant {
860+
if let Some(trait_def_id) = trait_def_id.as_local() {
861+
tcx.emit_node_span_lint(
862+
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES,
863+
tcx.local_def_id_to_hir_id(trait_def_id),
871864
self.span,
872-
E0231,
873-
"{}",
874-
e.description,
875-
)
876-
.emit();
877-
result = Err(reported);
865+
WrappedParserError { description: e.description, label: e.label },
866+
);
878867
}
868+
} else {
869+
let reported =
870+
struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description,)
871+
.emit();
872+
result = Err(reported);
879873
}
880874
}
881875
}
@@ -896,7 +890,8 @@ impl<'tcx> OnUnimplementedFormatString {
896890
Ctx::RustcOnUnimplemented { tcx, trait_def_id }
897891
};
898892

899-
if let Ok(s) = FormatString::parse(self.symbol, self.span, &ctx) {
893+
// No point passing a snippet here, we already did that in `verify`
894+
if let Ok(s) = FormatString::parse(self.symbol, None, self.span, &ctx) {
900895
s.format(args)
901896
} else {
902897
// we cannot return errors from processing the format string as hard error here

compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ enum LitOrArg {
198198

199199
impl FilterFormatString {
200200
fn parse(input: Symbol) -> Self {
201-
let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Format)
201+
let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic)
202202
.map(|p| match p {
203203
Piece::Lit(s) => LitOrArg::Lit(s.to_owned()),
204204
// We just ignore formatspecs here

compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_format.rs

Lines changed: 36 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ use errors::*;
55
use rustc_middle::ty::print::TraitRefPrintSugared;
66
use rustc_middle::ty::{GenericParamDefKind, TyCtxt};
77
use rustc_parse_format::{
8-
Alignment, Argument, Count, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece,
9-
Position,
8+
Argument, FormatSpec, ParseError, ParseMode, Parser, Piece as RpfPiece, Position,
109
};
1110
use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
1211
use rustc_span::def_id::DefId;
13-
use rustc_span::{BytePos, Pos, Span, Symbol, kw, sym};
12+
use rustc_span::{InnerSpan, Span, Symbol, kw, sym};
1413

1514
/// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces",
1615
/// either as string pieces or dynamic arguments.
@@ -160,32 +159,32 @@ impl FormatString {
160159

161160
pub fn parse<'tcx>(
162161
input: Symbol,
162+
snippet: Option<String>,
163163
span: Span,
164164
ctx: &Ctx<'tcx>,
165-
) -> Result<Self, Vec<ParseError>> {
165+
) -> Result<Self, ParseError> {
166166
let s = input.as_str();
167-
let mut parser = Parser::new(s, None, None, false, ParseMode::Format);
168-
let mut pieces = Vec::new();
167+
let mut parser = Parser::new(s, None, snippet, false, ParseMode::Diagnostic);
168+
let pieces: Vec<_> = parser.by_ref().collect();
169+
170+
if let Some(err) = parser.errors.into_iter().next() {
171+
return Err(err);
172+
}
169173
let mut warnings = Vec::new();
170174

171-
for piece in &mut parser {
172-
match piece {
173-
RpfPiece::Lit(lit) => {
174-
pieces.push(Piece::Lit(lit.into()));
175-
}
175+
let pieces = pieces
176+
.into_iter()
177+
.map(|piece| match piece {
178+
RpfPiece::Lit(lit) => Piece::Lit(lit.into()),
176179
RpfPiece::NextArgument(arg) => {
177-
warn_on_format_spec(arg.format.clone(), &mut warnings, span);
178-
let arg = parse_arg(&arg, ctx, &mut warnings, span);
179-
pieces.push(Piece::Arg(arg));
180+
warn_on_format_spec(&arg.format, &mut warnings, span, parser.is_source_literal);
181+
let arg = parse_arg(&arg, ctx, &mut warnings, span, parser.is_source_literal);
182+
Piece::Arg(arg)
180183
}
181-
}
182-
}
184+
})
185+
.collect();
183186

184-
if parser.errors.is_empty() {
185-
Ok(FormatString { input, pieces, span, warnings })
186-
} else {
187-
Err(parser.errors)
188-
}
187+
Ok(FormatString { input, pieces, span, warnings })
189188
}
190189

191190
pub fn format(&self, args: &FormatArgs<'_>) -> String {
@@ -229,11 +228,12 @@ fn parse_arg<'tcx>(
229228
ctx: &Ctx<'tcx>,
230229
warnings: &mut Vec<FormatWarning>,
231230
input_span: Span,
231+
is_source_literal: bool,
232232
) -> FormatArg {
233233
let (Ctx::RustcOnUnimplemented { tcx, trait_def_id }
234234
| Ctx::DiagnosticOnUnimplemented { tcx, trait_def_id }) = ctx;
235235

236-
let span = slice_span(input_span, arg.position_span.clone());
236+
let span = slice_span(input_span, arg.position_span.clone(), is_source_literal);
237237

238238
match arg.position {
239239
// Something like "hello {name}"
@@ -283,39 +283,24 @@ fn parse_arg<'tcx>(
283283

284284
/// `#[rustc_on_unimplemented]` and `#[diagnostic::...]` don't actually do anything
285285
/// with specifiers, so emit a warning if they are used.
286-
fn warn_on_format_spec(spec: FormatSpec<'_>, warnings: &mut Vec<FormatWarning>, input_span: Span) {
287-
if !matches!(
288-
spec,
289-
FormatSpec {
290-
fill: None,
291-
fill_span: None,
292-
align: Alignment::AlignUnknown,
293-
sign: None,
294-
alternate: false,
295-
zero_pad: false,
296-
debug_hex: None,
297-
precision: Count::CountImplied,
298-
precision_span: None,
299-
width: Count::CountImplied,
300-
width_span: None,
301-
ty: _,
302-
ty_span: _,
303-
},
304-
) {
305-
let span = spec.ty_span.map(|inner| slice_span(input_span, inner)).unwrap_or(input_span);
286+
fn warn_on_format_spec(
287+
spec: &FormatSpec<'_>,
288+
warnings: &mut Vec<FormatWarning>,
289+
input_span: Span,
290+
is_source_literal: bool,
291+
) {
292+
if spec.ty != "" {
293+
let span = spec
294+
.ty_span
295+
.as_ref()
296+
.map(|inner| slice_span(input_span, inner.clone(), is_source_literal))
297+
.unwrap_or(input_span);
306298
warnings.push(FormatWarning::InvalidSpecifier { span, name: spec.ty.into() })
307299
}
308300
}
309301

310-
fn slice_span(input: Span, range: Range<usize>) -> Span {
311-
let span = input.data();
312-
313-
Span::new(
314-
span.lo + BytePos::from_usize(range.start),
315-
span.lo + BytePos::from_usize(range.end),
316-
span.ctxt,
317-
span.parent,
318-
)
302+
fn slice_span(input: Span, Range { start, end }: Range<usize>, is_source_literal: bool) -> Span {
303+
if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input }
319304
}
320305

321306
pub mod errors {
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#![crate_type = "lib"]
2+
#![deny(unknown_or_malformed_diagnostic_attributes)]
3+
4+
5+
#[diagnostic::on_unimplemented(message = "here is a big \
6+
multiline string \
7+
{unknown}")]
8+
//~^ ERROR there is no parameter `unknown` on trait `MultiLine` [unknown_or_malformed_diagnostic_attributes]
9+
pub trait MultiLine {}
10+
11+
#[diagnostic::on_unimplemented(message = "here is a big \
12+
multiline string {unknown}")]
13+
//~^ ERROR there is no parameter `unknown` on trait `MultiLine2` [unknown_or_malformed_diagnostic_attributes]
14+
pub trait MultiLine2 {}
15+
16+
#[diagnostic::on_unimplemented(message = "here is a big \
17+
multiline string {unknown}")]
18+
//~^ ERROR there is no parameter `unknown` on trait `MultiLine3` [unknown_or_malformed_diagnostic_attributes]
19+
pub trait MultiLine3 {}
20+
21+
22+
#[diagnostic::on_unimplemented(message = "here is a big \
23+
\
24+
\
25+
\
26+
\
27+
multiline string {unknown}")]
28+
//~^ ERROR there is no parameter `unknown` on trait `MultiLine4` [unknown_or_malformed_diagnostic_attributes]
29+
pub trait MultiLine4 {}
30+
31+
#[diagnostic::on_unimplemented(message = "here is a big \
32+
multiline string \
33+
{Self:+}")]
34+
//~^ ERROR invalid format specifier [unknown_or_malformed_diagnostic_attributes]
35+
pub trait MultiLineFmt {}
36+
37+
#[diagnostic::on_unimplemented(message = "here is a big \
38+
multiline string {Self:X}")]
39+
//~^ ERROR invalid format specifier [unknown_or_malformed_diagnostic_attributes]
40+
pub trait MultiLineFmt2 {}
41+
42+
#[diagnostic::on_unimplemented(message = "here is a big \
43+
multiline string {Self:#}")]
44+
//~^ ERROR invalid format specifier [unknown_or_malformed_diagnostic_attributes]
45+
pub trait MultiLineFmt3 {}
46+
47+
48+
#[diagnostic::on_unimplemented(message = "here is a big \
49+
\
50+
\
51+
\
52+
\
53+
multiline string {Self:?}")]
54+
//~^ ERROR invalid format specifier [unknown_or_malformed_diagnostic_attributes]
55+
pub trait MultiLineFmt4 {}
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
error: there is no parameter `unknown` on trait `MultiLine`
2+
--> $DIR/multiline_spans.rs:7:43
3+
|
4+
LL | ... {unknown}")]
5+
| ^^^^^^^
6+
|
7+
= help: expect either a generic argument name or `{Self}` as format argument
8+
note: the lint level is defined here
9+
--> $DIR/multiline_spans.rs:2:9
10+
|
11+
LL | #![deny(unknown_or_malformed_diagnostic_attributes)]
12+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
13+
14+
error: there is no parameter `unknown` on trait `MultiLine2`
15+
--> $DIR/multiline_spans.rs:12:60
16+
|
17+
LL | ... multiline string {unknown}")]
18+
| ^^^^^^^
19+
|
20+
= help: expect either a generic argument name or `{Self}` as format argument
21+
22+
error: there is no parameter `unknown` on trait `MultiLine3`
23+
--> $DIR/multiline_spans.rs:17:23
24+
|
25+
LL | multiline string {unknown}")]
26+
| ^^^^^^^
27+
|
28+
= help: expect either a generic argument name or `{Self}` as format argument
29+
30+
error: there is no parameter `unknown` on trait `MultiLine4`
31+
--> $DIR/multiline_spans.rs:27:23
32+
|
33+
LL | multiline string {unknown}")]
34+
| ^^^^^^^
35+
|
36+
= help: expect either a generic argument name or `{Self}` as format argument
37+
38+
error: invalid format specifier
39+
--> $DIR/multiline_spans.rs:33:47
40+
|
41+
LL | ... {Self:+}")]
42+
| ^^
43+
|
44+
= help: no format specifier are supported in this position
45+
46+
error: invalid format specifier
47+
--> $DIR/multiline_spans.rs:38:64
48+
|
49+
LL | ... multiline string {Self:X}")]
50+
| ^^
51+
|
52+
= help: no format specifier are supported in this position
53+
54+
error: invalid format specifier
55+
--> $DIR/multiline_spans.rs:43:27
56+
|
57+
LL | multiline string {Self:#}")]
58+
| ^^
59+
|
60+
= help: no format specifier are supported in this position
61+
62+
error: invalid format specifier
63+
--> $DIR/multiline_spans.rs:53:27
64+
|
65+
LL | multiline string {Self:?}")]
66+
| ^^
67+
|
68+
= help: no format specifier are supported in this position
69+
70+
error: aborting due to 8 previous errors
71+

0 commit comments

Comments
 (0)