Handle several #[diagnostic::on_unimplemented] attributes correctly

This PR fixes an issues where rustc would ignore subsequent
`#[diagnostic::on_unimplemented]` attributes. The [corresponding
RFC](https://rust-lang.github.io/rfcs/3368-diagnostic-attribute-namespace.html)
specifies that the first matching instance of each option is used.
Invalid attributes are linted and otherwise ignored.
This commit is contained in:
Georg Semmler 2023-10-11 21:57:53 +02:00
parent 71704c4f84
commit 232aaeba7c
No known key found for this signature in database
GPG Key ID: A87BCEE5205CE489
3 changed files with 108 additions and 12 deletions

View File

@ -1,6 +1,6 @@
use super::{ObligationCauseCode, PredicateObligation};
use crate::infer::error_reporting::TypeErrCtxt;
use rustc_ast::{MetaItem, NestedMetaItem};
use rustc_ast::{Attribute, MetaItem, NestedMetaItem};
use rustc_attr as attr;
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{struct_span_err, ErrorGuaranteed};
@ -474,18 +474,40 @@ fn parse(
}
pub fn of_item(tcx: TyCtxt<'tcx>, item_def_id: DefId) -> Result<Option<Self>, ErrorGuaranteed> {
let mut is_diagnostic_namespace_variant = false;
let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented).or_else(|| {
if tcx.features().diagnostic_namespace {
is_diagnostic_namespace_variant = true;
tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented]).next()
} else {
None
}
}) else {
return Ok(None);
};
if let Some(attr) = tcx.get_attr(item_def_id, sym::rustc_on_unimplemented) {
return Self::parse_attribute(attr, false, tcx, item_def_id);
} else if tcx.features().diagnostic_namespace {
tcx.get_attrs_by_path(item_def_id, &[sym::diagnostic, sym::on_unimplemented])
.filter_map(|attr| Self::parse_attribute(attr, true, tcx, item_def_id).transpose())
.try_fold(None, |aggr: Option<Self>, directive| {
let directive = directive?;
if let Some(aggr) = aggr {
let mut subcommands = aggr.subcommands;
subcommands.extend(directive.subcommands);
Ok(Some(Self {
condition: aggr.condition.or(directive.condition),
subcommands,
message: aggr.message.or(directive.message),
label: aggr.label.or(directive.label),
note: aggr.note.or(directive.note),
parent_label: aggr.parent_label.or(directive.parent_label),
append_const_msg: aggr.append_const_msg.or(directive.append_const_msg),
}))
} else {
Ok(Some(directive))
}
})
} else {
Ok(None)
}
}
fn parse_attribute(
attr: &Attribute,
is_diagnostic_namespace_variant: bool,
tcx: TyCtxt<'tcx>,
item_def_id: DefId,
) -> Result<Option<Self>, ErrorGuaranteed> {
let result = if let Some(items) = attr.meta_item_list() {
Self::parse(tcx, item_def_id, &items, attr.span, true, is_diagnostic_namespace_variant)
} else if let Some(value) = attr.value_str() {

View File

@ -0,0 +1,22 @@
#![feature(diagnostic_namespace)]
#[diagnostic::on_unimplemented(
//~^WARN malformed `on_unimplemented` attribute
//~|WARN malformed `on_unimplemented` attribute
if(Self = ()),
message = "not used yet",
label = "not used yet",
note = "not used yet"
)]
#[diagnostic::on_unimplemented(message = "fallback!!")]
#[diagnostic::on_unimplemented(label = "fallback label")]
#[diagnostic::on_unimplemented(note = "fallback note")]
#[diagnostic::on_unimplemented(message = "fallback2!!")]
trait Foo {}
fn takes_foo(_: impl Foo) {}
fn main() {
takes_foo(());
//~^ERROR fallback!!
}

View File

@ -0,0 +1,52 @@
warning: malformed `on_unimplemented` attribute
--> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:3:1
|
LL | / #[diagnostic::on_unimplemented(
LL | |
LL | |
LL | | if(Self = ()),
... |
LL | | note = "not used yet"
LL | | )]
| |__^
|
= note: `#[warn(unknown_or_malformed_diagnostic_attributes)]` on by default
warning: malformed `on_unimplemented` attribute
--> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:3:1
|
LL | / #[diagnostic::on_unimplemented(
LL | |
LL | |
LL | | if(Self = ()),
... |
LL | | note = "not used yet"
LL | | )]
| |__^
|
= note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no`
error[E0277]: fallback!!
--> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:20:15
|
LL | takes_foo(());
| --------- ^^ fallback label
| |
| required by a bound introduced by this call
|
= help: the trait `Foo` is not implemented for `()`
= note: fallback note
help: this trait has no implementations, consider adding one
--> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:15:1
|
LL | trait Foo {}
| ^^^^^^^^^
note: required by a bound in `takes_foo`
--> $DIR/ignore_unsupported_options_and_continue_to_use_fallback.rs:17:22
|
LL | fn takes_foo(_: impl Foo) {}
| ^^^ required by this bound in `takes_foo`
error: aborting due to previous error; 2 warnings emitted
For more information about this error, try `rustc --explain E0277`.