Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 16 additions & 22 deletions compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ use std::ops::Range;
use rustc_errors::E0232;
use rustc_hir::AttrPath;
use rustc_hir::attrs::diagnostic::{
Directive, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name, NameValue,
OnUnimplementedCondition, Piece, Predicate,
Directive, Filter, FilterFormatString, Flag, FormatArg, FormatString, LitOrArg, Name,
NameValue, Piece, Predicate,
};
use rustc_macros::Diagnostic;
use rustc_parse_format::{
Expand Down Expand Up @@ -201,12 +201,11 @@ fn parse_directive_items<'p>(
items: impl Iterator<Item = &'p MetaItemOrLitParser>,
is_root: bool,
) -> Option<Directive> {
let condition = None;
let mut message: Option<(Span, _)> = None;
let mut label: Option<(Span, _)> = None;
let mut notes = ThinVec::new();
let mut parent_label = None;
let mut subcommands = ThinVec::new();
let mut filters = ThinVec::new();

for item in items {
let span = item.span();
Expand Down Expand Up @@ -330,29 +329,27 @@ fn parse_directive_items<'p>(
if is_root {
let items = or_malformed!(item.args().as_list()?);
let mut iter = items.mixed();
let condition: &MetaItemOrLitParser = match iter.next() {
let filter: &MetaItemOrLitParser = match iter.next() {
Some(c) => c,
None => {
cx.emit_err(InvalidOnClause::Empty { span });
continue;
}
};

let condition = parse_condition(condition);
let filter = parse_filter(filter);

if items.len() < 2 {
// Something like `#[rustc_on_unimplemented(on(.., /* nothing */))]`
// There's a condition but no directive behind it, this is a mistake.
// There's a filter but no directive behind it, this is a mistake.
malformed!();
}

let mut directive =
or_malformed!(parse_directive_items(cx, mode, iter, false)?);

match condition {
Ok(c) => {
directive.condition = Some(c);
subcommands.push(directive);
match filter {
Ok(filter) => {
let directive =
or_malformed!(parse_directive_items(cx, mode, iter, false)?);
filters.push((filter, directive));
}
Err(e) => {
cx.emit_err(e);
Expand All @@ -371,8 +368,7 @@ fn parse_directive_items<'p>(

Some(Directive {
is_rustc_attr: matches!(mode, Mode::RustcOnUnimplemented),
condition,
subcommands,
filters,
message,
label,
notes,
Expand Down Expand Up @@ -513,12 +509,10 @@ fn slice_span(input: Span, Range { start, end }: Range<usize>, is_source_literal
if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input }
}

pub(crate) fn parse_condition(
input: &MetaItemOrLitParser,
) -> Result<OnUnimplementedCondition, InvalidOnClause> {
pub(crate) fn parse_filter(input: &MetaItemOrLitParser) -> Result<Filter, InvalidOnClause> {
let span = input.span();
let pred = parse_predicate(input)?;
Ok(OnUnimplementedCondition { span, pred })
Ok(Filter { span, pred })
}

fn parse_predicate(input: &MetaItemOrLitParser) -> Result<Predicate, InvalidOnClause> {
Expand Down Expand Up @@ -553,7 +547,7 @@ fn parse_predicate(input: &MetaItemOrLitParser) -> Result<Predicate, InvalidOnCl
return Err(InvalidOnClause::UnsupportedLiteral { span: p.args_span() });
};
let name = parse_name(predicate.name);
let value = parse_filter(value.name);
let value = parse_filter_format(value.name);
let kv = NameValue { name, value };
Ok(Predicate::Match(kv))
}
Expand Down Expand Up @@ -588,7 +582,7 @@ fn parse_name(name: Symbol) -> Name {
}
}

fn parse_filter(input: Symbol) -> FilterFormatString {
fn parse_filter_format(input: Symbol) -> FilterFormatString {
let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Diagnostic)
.map(|p| match p {
RpfPiece::Lit(s) => LitOrArg::Lit(Symbol::intern(s)),
Expand Down
107 changes: 46 additions & 61 deletions compiler/rustc_hir/src/attrs/diagnostic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ use crate::attrs::PrintAttribute;
#[derive(Clone, Default, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
pub struct Directive {
pub is_rustc_attr: bool,
pub condition: Option<OnUnimplementedCondition>,
pub subcommands: ThinVec<Directive>,
/// This is never nested more than once, i.e. the directives in this
/// thinvec have no filters of their own.
pub filters: ThinVec<(Filter, Directive)>,
Copy link
Copy Markdown
Contributor

@jdonszelmann jdonszelmann Apr 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

are toplevel and nested directives any different? like are some fields like mesage always None at the top level? Just wondering if it would maybe make sense to have a TopLevelDirective and a Directive for example

View changes since the review

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can write a rustc_on_unimplemented attribute that populates all those fields, at both top level and secondary level. Just the filters in that secondary level are always empty.

Regular diagnostic attributes are just a much more restricted form of that, where only message, label and note can be populated.

if it would maybe make sense to have a TopLevelDirective and a Directive for example

It's possible; this structure would essentially be:

pub struct DiagnosticAttrDirective {
    pub message: Option<(Span, FormatString)>,
    pub label: Option<(Span, FormatString)>,
    pub notes: ThinVec<FormatString>,
}

pub struct TopRustcOnUnImplDirective {
    pub filters: ThinVec<(Filter, RustcOnUnImplDirective)>,
    pub message: Option<(Span, FormatString)>,
    pub label: Option<(Span, FormatString)>,
    pub notes: ThinVec<FormatString>,
    pub parent_label: Option<FormatString>,
}

pub struct RustcOnUnImplDirective {
    pub message: Option<(Span, FormatString)>,
    pub label: Option<(Span, FormatString)>,
    pub notes: ThinVec<FormatString>,
    pub parent_label: Option<FormatString>,
}

presumably we can do clever things with generics so we can reuse things and you end up with fields such as filters: ThinVec<!> etc to enforce this at the type level.

The downside of all of this is that everything becomes more complex, boilerplate-y and harder to read. The upside is that it's harder to misuse. But making things harder to misuse is only really important for the unwashed masses. This is important when you write a http framework because arbitrary people are are programming against your code. It's not as important in this case because diagnostic attr parsing infra is only used by people programming in the same crate. These are the same people that might as well just mess up the types themselves - so what is the point?

pub message: Option<(Span, FormatString)>,
pub label: Option<(Span, FormatString)>,
pub notes: ThinVec<FormatString>,
Expand All @@ -28,11 +29,8 @@ impl Directive {
/// We can't check this while parsing the attribute because `rustc_attr_parsing` doesn't have
/// access to the item an attribute is on. Instead we later call this function in `check_attr`.
pub fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) {
if let Some(condition) = &self.condition {
condition.visit_params(visit);
}

for subcommand in &self.subcommands {
for (filter, subcommand) in &self.filters {
filter.visit_params(visit);
subcommand.visit_params(visit);
}

Expand All @@ -54,61 +52,33 @@ impl Directive {

pub fn eval(
&self,
condition_options: Option<&ConditionOptions>,
filter_options: Option<&FilterOptions>,
args: &FormatArgs,
) -> CustomDiagnostic {
let this = &args.this;
debug!(
"Directive::eval({self:?}, this={this}, options={condition_options:?}, args ={args:?})"
"Directive::eval({self:?}, this={this}, options={filter_options:?}, args ={args:?})"
);

let Some(condition_options) = condition_options else {
let mut ret = CustomDiagnostic::default();

if let Some(filter_options) = filter_options {
for (filter, directive) in &self.filters {
if filter.matches_predicate(filter_options) {
debug!("eval: {filter:?} succeeded");
ret.update(directive, args);
} else {
debug!("eval: skipping {filter:?} due to {filter_options:?}");
}
}
} else {
debug_assert!(
!self.is_rustc_attr,
"Directive::eval called for `rustc_on_unimplemented` without `condition_options`"
"Directive::eval called for `rustc_on_unimplemented` without `filter_options`"
);
return CustomDiagnostic {
label: self.label.as_ref().map(|l| l.1.format(args)),
message: self.message.as_ref().map(|m| m.1.format(args)),
notes: self.notes.iter().map(|n| n.format(args)).collect(),
parent_label: None,
};
};
let mut message = None;
let mut label = None;
let mut notes = Vec::new();
let mut parent_label = None;

for command in self.subcommands.iter().chain(Some(self)).rev() {
debug!(?command);
if let Some(ref condition) = command.condition
&& !condition.matches_predicate(condition_options)
{
debug!("eval: skipping {command:?} due to condition");
continue;
}
debug!("eval: {command:?} succeeded");
if let Some(ref message_) = command.message {
message = Some(message_.clone());
}

if let Some(ref label_) = command.label {
label = Some(label_.clone());
}

notes.extend(command.notes.clone());

if let Some(ref parent_label_) = command.parent_label {
parent_label = Some(parent_label_.clone());
}
}

CustomDiagnostic {
label: label.map(|l| l.1.format(args)),
message: message.map(|m| m.1.format(args)),
notes: notes.into_iter().map(|n| n.format(args)).collect(),
parent_label: parent_label.map(|e_s| e_s.format(args)),
}
ret.update(self, args);
ret
}
}

Expand All @@ -121,6 +91,22 @@ pub struct CustomDiagnostic {
pub parent_label: Option<String>,
}

impl CustomDiagnostic {
fn update(&mut self, di: &Directive, args: &FormatArgs) {
if self.message.is_none() {
self.message = di.message.as_ref().map(|m| m.1.format(args));
}
if self.label.is_none() {
self.label = di.label.as_ref().map(|l| l.1.format(args));
}
if self.parent_label.is_none() {
self.parent_label = di.parent_label.as_ref().map(|p| p.format(args));
}

self.notes.extend(di.notes.iter().map(|n| n.format(args)))
}
}

/// Like [std::fmt::Arguments] this is a string that has been parsed into "pieces",
/// either as string pieces or dynamic arguments.
#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
Expand Down Expand Up @@ -252,12 +238,12 @@ pub enum FormatArg {

/// Represents the `on` filter in `#[rustc_on_unimplemented]`.
#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
pub struct OnUnimplementedCondition {
pub struct Filter {
pub span: Span,
pub pred: Predicate,
}
impl OnUnimplementedCondition {
pub fn matches_predicate(self: &OnUnimplementedCondition, options: &ConditionOptions) -> bool {
impl Filter {
pub fn matches_predicate(&self, options: &FilterOptions) -> bool {
self.pred.eval(&mut |p| match p {
FlagOrNv::Flag(b) => options.has_flag(*b),
FlagOrNv::NameValue(NameValue { name, value }) => {
Expand All @@ -272,7 +258,7 @@ impl OnUnimplementedCondition {
}
}

/// Predicate(s) in `#[rustc_on_unimplemented]`'s `on` filter. See [`OnUnimplementedCondition`].
/// Predicate(s) in `#[rustc_on_unimplemented]`'s `on` filter. See [`Filter`].
///
/// It is similar to the predicate in the `cfg` attribute,
/// and may contain nested predicates.
Expand Down Expand Up @@ -406,8 +392,7 @@ pub enum LitOrArg {
Arg(Symbol),
}

/// Used with `OnUnimplementedCondition::matches_predicate` to evaluate the
/// [`OnUnimplementedCondition`].
/// Used with `Filter::matches_predicate` to evaluate the [`Filter`].
///
/// For example, given a
/// ```rust,ignore (just an example)
Expand All @@ -433,7 +418,7 @@ pub enum LitOrArg {
/// it will look like this:
///
/// ```rust,ignore (just an example)
/// ConditionOptions {
/// FilterOptions {
/// self_types: ["u32", "{integral}"],
/// from_desugaring: Some("QuestionMark"),
/// cause: None,
Expand All @@ -446,7 +431,7 @@ pub enum LitOrArg {
/// }
/// ```
#[derive(Debug)]
pub struct ConditionOptions {
pub struct FilterOptions {
/// All the self types that may apply.
pub self_types: Vec<String>,
// The kind of compiler desugaring.
Expand All @@ -460,7 +445,7 @@ pub struct ConditionOptions {
pub generic_args: Vec<(Symbol, String)>,
}

impl ConditionOptions {
impl FilterOptions {
pub fn has_flag(&self, name: Flag) -> bool {
match name {
Flag::CrateLocal => self.crate_local,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use std::path::PathBuf;

use rustc_hir as hir;
use rustc_hir::attrs::diagnostic::{ConditionOptions, CustomDiagnostic, FormatArgs};
use rustc_hir::attrs::diagnostic::{CustomDiagnostic, FilterOptions, FormatArgs};
use rustc_hir::def_id::LocalDefId;
use rustc_hir::find_attr;
use rustc_middle::ty::print::PrintTraitRefExt;
Expand Down Expand Up @@ -40,11 +40,11 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
if trait_pred.polarity() != ty::PredicatePolarity::Positive {
return CustomDiagnostic::default();
}
let (condition_options, format_args) =
let (filter_options, format_args) =
self.on_unimplemented_components(trait_pred, obligation, long_ty_path);
if let Some(command) = find_attr!(self.tcx, trait_pred.def_id(), OnUnimplemented {directive, ..} => directive.as_deref()).flatten() {
command.eval(
Some(&condition_options),
Some(&filter_options),
&format_args,
)
} else {
Expand All @@ -57,7 +57,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
trait_pred: ty::PolyTraitPredicate<'tcx>,
obligation: &PredicateObligation<'tcx>,
long_ty_path: &mut Option<PathBuf>,
) -> (ConditionOptions, FormatArgs) {
) -> (FilterOptions, FormatArgs) {
let (def_id, args) = (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args);
let trait_pred = trait_pred.skip_binder();

Expand Down Expand Up @@ -219,14 +219,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
let this = self.tcx.def_path_str(trait_pred.trait_ref.def_id);
let this_sugared = trait_pred.trait_ref.print_trait_sugared().to_string();

let condition_options = ConditionOptions {
self_types,
from_desugaring,
cause,
crate_local,
direct,
generic_args,
};
let filter_options =
FilterOptions { self_types, from_desugaring, cause, crate_local, direct, generic_args };

// Unlike the generic_args earlier,
// this one is *not* collected under `with_no_trimmed_paths!`
Expand Down Expand Up @@ -256,6 +250,6 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
.collect();

let format_args = FormatArgs { this, this_sugared, generic_args, item_context };
(condition_options, format_args)
(filter_options, format_args)
}
}
2 changes: 1 addition & 1 deletion tests/ui/abi/bad-custom.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -194,8 +194,8 @@ LL | f
| - return type was inferred to be `unsafe extern "custom" fn()` here
|
= help: the trait `Fn()` is not implemented for `unsafe extern "custom" fn()`
= note: unsafe function cannot be called generically without an unsafe block
= note: wrap the `unsafe extern "custom" fn()` in a closure with no arguments: `|| { /* code */ }`
= note: unsafe function cannot be called generically without an unsafe block

error: items with the "custom" ABI can only be declared externally or defined via naked functions
--> $DIR/bad-custom.rs:25:1
Expand Down
6 changes: 3 additions & 3 deletions tests/ui/rfcs/rfc-2396-target_feature-11/fn-traits.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ LL | call(foo_unsafe);
| required by a bound introduced by this call
|
= help: the trait `Fn()` is not implemented for fn item `unsafe fn() {foo_unsafe}`
= note: unsafe function cannot be called generically without an unsafe block
= note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ }`
= note: unsafe function cannot be called generically without an unsafe block
= note: `#[target_feature]` functions do not implement the `Fn` traits
= note: try casting the function to a `fn` pointer or wrapping it in a closure
note: required by a bound in `call`
Expand All @@ -97,8 +97,8 @@ LL | call_mut(foo_unsafe);
| required by a bound introduced by this call
|
= help: the trait `FnMut()` is not implemented for fn item `unsafe fn() {foo_unsafe}`
= note: unsafe function cannot be called generically without an unsafe block
= note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ }`
= note: unsafe function cannot be called generically without an unsafe block
= note: `#[target_feature]` functions do not implement the `Fn` traits
= note: try casting the function to a `fn` pointer or wrapping it in a closure
note: required by a bound in `call_mut`
Expand All @@ -116,8 +116,8 @@ LL | call_once(foo_unsafe);
| required by a bound introduced by this call
|
= help: the trait `FnOnce()` is not implemented for fn item `unsafe fn() {foo_unsafe}`
= note: unsafe function cannot be called generically without an unsafe block
= note: wrap the `unsafe fn() {foo_unsafe}` in a closure with no arguments: `|| { /* code */ }`
= note: unsafe function cannot be called generically without an unsafe block
= note: `#[target_feature]` functions do not implement the `Fn` traits
= note: try casting the function to a `fn` pointer or wrapping it in a closure
note: required by a bound in `call_once`
Expand Down
Loading
Loading