-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Make #[diagnostic::on_unimplemented]
format string parsing more robust
#122402
Merged
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -367,6 +367,23 @@ pub struct UnknownFormatParameterForOnUnimplementedAttr { | |
trait_name: Symbol, | ||
} | ||
|
||
#[derive(LintDiagnostic)] | ||
#[diag(trait_selection_disallowed_positional_argument)] | ||
#[help] | ||
pub struct DisallowedPositionalArgument; | ||
|
||
#[derive(LintDiagnostic)] | ||
#[diag(trait_selection_invalid_format_specifier)] | ||
#[help] | ||
pub struct InvalidFormatSpecifier; | ||
|
||
#[derive(LintDiagnostic)] | ||
#[diag(trait_selection_wrapped_parser_error)] | ||
pub struct WrappedParserError { | ||
description: String, | ||
label: String, | ||
} | ||
|
||
impl<'tcx> OnUnimplementedDirective { | ||
fn parse( | ||
tcx: TyCtxt<'tcx>, | ||
|
@@ -758,64 +775,108 @@ impl<'tcx> OnUnimplementedFormatString { | |
let trait_name = tcx.item_name(trait_def_id); | ||
let generics = tcx.generics_of(item_def_id); | ||
let s = self.symbol.as_str(); | ||
let parser = Parser::new(s, None, None, false, ParseMode::Format); | ||
let mut parser = Parser::new(s, None, None, false, ParseMode::Format); | ||
let mut result = Ok(()); | ||
for token in parser { | ||
for token in &mut parser { | ||
match token { | ||
Piece::String(_) => (), // Normal string, no need to check it | ||
Piece::NextArgument(a) => match a.position { | ||
Position::ArgumentNamed(s) => { | ||
match Symbol::intern(s) { | ||
// `{ThisTraitsName}` is allowed | ||
s if s == trait_name && !self.is_diagnostic_namespace_variant => (), | ||
s if ALLOWED_FORMAT_SYMBOLS.contains(&s) | ||
&& !self.is_diagnostic_namespace_variant => | ||
{ | ||
() | ||
} | ||
// So is `{A}` if A is a type parameter | ||
s if generics.params.iter().any(|param| param.name == s) => (), | ||
s => { | ||
if self.is_diagnostic_namespace_variant { | ||
tcx.emit_node_span_lint( | ||
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, | ||
tcx.local_def_id_to_hir_id(item_def_id.expect_local()), | ||
self.span, | ||
UnknownFormatParameterForOnUnimplementedAttr { | ||
argument_name: s, | ||
trait_name, | ||
}, | ||
); | ||
} else { | ||
result = Err(struct_span_code_err!( | ||
tcx.dcx(), | ||
self.span, | ||
E0230, | ||
"there is no parameter `{}` on {}", | ||
s, | ||
if trait_def_id == item_def_id { | ||
format!("trait `{trait_name}`") | ||
} else { | ||
"impl".to_string() | ||
} | ||
) | ||
.emit()); | ||
Piece::NextArgument(a) => { | ||
let format_spec = a.format; | ||
if self.is_diagnostic_namespace_variant | ||
&& (format_spec.ty_span.is_some() | ||
|| format_spec.width_span.is_some() | ||
|| format_spec.precision_span.is_some() | ||
|| format_spec.fill_span.is_some()) | ||
{ | ||
tcx.emit_node_span_lint( | ||
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, | ||
tcx.local_def_id_to_hir_id(item_def_id.expect_local()), | ||
self.span, | ||
InvalidFormatSpecifier, | ||
); | ||
} | ||
match a.position { | ||
Position::ArgumentNamed(s) => { | ||
match Symbol::intern(s) { | ||
// `{ThisTraitsName}` is allowed | ||
s if s == trait_name && !self.is_diagnostic_namespace_variant => (), | ||
s if ALLOWED_FORMAT_SYMBOLS.contains(&s) | ||
&& !self.is_diagnostic_namespace_variant => | ||
{ | ||
() | ||
} | ||
// So is `{A}` if A is a type parameter | ||
s if generics.params.iter().any(|param| param.name == s) => (), | ||
s => { | ||
if self.is_diagnostic_namespace_variant { | ||
tcx.emit_node_span_lint( | ||
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, | ||
tcx.local_def_id_to_hir_id(item_def_id.expect_local()), | ||
self.span, | ||
UnknownFormatParameterForOnUnimplementedAttr { | ||
argument_name: s, | ||
trait_name, | ||
}, | ||
); | ||
} else { | ||
result = Err(struct_span_code_err!( | ||
tcx.dcx(), | ||
self.span, | ||
E0230, | ||
"there is no parameter `{}` on {}", | ||
s, | ||
if trait_def_id == item_def_id { | ||
format!("trait `{trait_name}`") | ||
} else { | ||
"impl".to_string() | ||
} | ||
) | ||
.emit()); | ||
} | ||
} | ||
} | ||
} | ||
// `{:1}` and `{}` are not to be used | ||
Position::ArgumentIs(..) | Position::ArgumentImplicitlyIs(_) => { | ||
if self.is_diagnostic_namespace_variant { | ||
tcx.emit_node_span_lint( | ||
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, | ||
tcx.local_def_id_to_hir_id(item_def_id.expect_local()), | ||
self.span, | ||
DisallowedPositionalArgument, | ||
); | ||
} else { | ||
let reported = struct_span_code_err!( | ||
tcx.dcx(), | ||
self.span, | ||
E0231, | ||
"only named generic parameters are allowed" | ||
) | ||
.emit(); | ||
result = Err(reported); | ||
} | ||
} | ||
} | ||
// `{:1}` and `{}` are not to be used | ||
Position::ArgumentIs(..) | Position::ArgumentImplicitlyIs(_) => { | ||
let reported = struct_span_code_err!( | ||
tcx.dcx(), | ||
self.span, | ||
E0231, | ||
"only named generic parameters are allowed" | ||
) | ||
.emit(); | ||
result = Err(reported); | ||
} | ||
}, | ||
} | ||
} | ||
} | ||
// we cannot return errors from processing the format string as hard error here | ||
// as the diagnostic namespace gurantees that malformed input cannot cause an error | ||
// | ||
// if we encounter any error while processing we nevertheless want to show it as warning | ||
// so that users are aware that something is not correct | ||
for e in parser.errors { | ||
if self.is_diagnostic_namespace_variant { | ||
tcx.emit_node_span_lint( | ||
UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES, | ||
tcx.local_def_id_to_hir_id(item_def_id.expect_local()), | ||
self.span, | ||
WrappedParserError { description: e.description, label: e.label }, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kind of a hack, but I guess it's fine |
||
); | ||
} else { | ||
let reported = | ||
struct_span_code_err!(tcx.dcx(), self.span, E0231, "{}", e.description,).emit(); | ||
result = Err(reported); | ||
} | ||
} | ||
|
||
|
@@ -853,9 +914,9 @@ impl<'tcx> OnUnimplementedFormatString { | |
let empty_string = String::new(); | ||
|
||
let s = self.symbol.as_str(); | ||
let parser = Parser::new(s, None, None, false, ParseMode::Format); | ||
let mut parser = Parser::new(s, None, None, false, ParseMode::Format); | ||
let item_context = (options.get(&sym::ItemContext)).unwrap_or(&empty_string); | ||
parser | ||
let constructed_message = (&mut parser) | ||
.map(|p| match p { | ||
Piece::String(s) => s.to_owned(), | ||
Piece::NextArgument(a) => match a.position { | ||
|
@@ -895,9 +956,29 @@ impl<'tcx> OnUnimplementedFormatString { | |
} | ||
} | ||
} | ||
Position::ArgumentImplicitlyIs(_) if self.is_diagnostic_namespace_variant => { | ||
String::from("{}") | ||
} | ||
Position::ArgumentIs(idx) if self.is_diagnostic_namespace_variant => { | ||
format!("{{{idx}}}") | ||
} | ||
_ => bug!("broken on_unimplemented {:?} - bad format arg", self.symbol), | ||
}, | ||
}) | ||
.collect() | ||
.collect(); | ||
// we cannot return errors from processing the format string as hard error here | ||
// as the diagnostic namespace gurantees that malformed input cannot cause an error | ||
// | ||
// if we encounter any error while processing the format string | ||
// we don't want to show the potentially half assembled formated string, | ||
// therefore we fall back to just showing the input string in this case | ||
// | ||
// The actual parser errors are emitted earlier | ||
// as lint warnings in OnUnimplementedFormatString::verify | ||
if self.is_diagnostic_namespace_variant && !parser.errors.is_empty() { | ||
String::from(s) | ||
} else { | ||
constructed_message | ||
} | ||
Comment on lines
+978
to
+982
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This seems subtle. Please document why we need this. |
||
} | ||
} |
45 changes: 45 additions & 0 deletions
45
tests/ui/diagnostic_namespace/on_unimplemented/broken_format.rs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
#[diagnostic::on_unimplemented(message = "{{Test } thing")] | ||
//~^WARN unmatched `}` found | ||
//~|WARN unmatched `}` found | ||
trait ImportantTrait1 {} | ||
|
||
#[diagnostic::on_unimplemented(message = "Test {}")] | ||
//~^WARN positional format arguments are not allowed here | ||
//~|WARN positional format arguments are not allowed here | ||
trait ImportantTrait2 {} | ||
|
||
#[diagnostic::on_unimplemented(message = "Test {1:}")] | ||
//~^WARN positional format arguments are not allowed here | ||
//~|WARN positional format arguments are not allowed here | ||
trait ImportantTrait3 {} | ||
|
||
#[diagnostic::on_unimplemented(message = "Test {Self:123}")] | ||
//~^WARN invalid format specifier | ||
//~|WARN invalid format specifier | ||
trait ImportantTrait4 {} | ||
|
||
#[diagnostic::on_unimplemented(message = "Test {Self:!}")] | ||
//~^WARN expected `'}'`, found `'!'` | ||
//~|WARN expected `'}'`, found `'!'` | ||
//~|WARN unmatched `}` found | ||
//~|WARN unmatched `}` found | ||
trait ImportantTrait5 {} | ||
|
||
fn check_1(_: impl ImportantTrait1) {} | ||
fn check_2(_: impl ImportantTrait2) {} | ||
fn check_3(_: impl ImportantTrait3) {} | ||
fn check_4(_: impl ImportantTrait4) {} | ||
fn check_5(_: impl ImportantTrait5) {} | ||
|
||
fn main() { | ||
check_1(()); | ||
//~^ERROR {{Test } thing | ||
check_2(()); | ||
//~^ERROR Test {} | ||
check_3(()); | ||
//~^ERROR Test {1} | ||
check_4(()); | ||
//~^ERROR Test () | ||
check_5(()); | ||
//~^ERROR Test {Self:!} | ||
} |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm a bit unsure about that "hack". That's here to forward the values of
ParserError
as lint. I'm not sure if there is a better way of doing that. If that's the case I'm happy to change it.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's fine until these parsing errors become translatable.