Repository: rust-lang/rust
Pull request #140307: Refactor rustc_on_unimplemented's filter parser
Author: login: mejrs
Labels: name: T-compiler
description: Relevant to the compiler team, which will review and decide on the PR/issue.
name: S-waiting-on-bors
description: Status: Waiting on bors to run and complete tests. Bors will change the label on completion.
Created: 2025-04-25T18:08:31Z | Merged: 2025-05-05T08:35:12Z

Followup to https://github.com/rust-lang/rust/pull/139091; I plan on moving most of this code into `rustc_attr_parsing` at some point, but want to land this separately first.

I have taken care to preserve the original behavior as much as I could:
- All but one of the new error variants are replacements for the ones originally emitted by the cfg parsing machinery; so these errors are not "new".
- the `InvalidFlag` variant is new, this PR turns this (from being ignored and silently doing nothing) into an error:
    ```rust
    #[rustc_on_unimplemented(on(something, message = "y"))]
    //~^ ERROR invalid boolean flag
    //~^^ NOTE expected one of `crate_local`, `direct` or `from_desugaring`, not `something`
    trait InvalidFlag {}
    ```
    This does not occur anywhere except in this test. I couldn't find a way that I liked to keep allowing this or to do nothing, erroring was the cleanest solution.
- There are a bunch of FIXME throughout this and the previous PR, I plan on addressing those in follow up prs..


Finally, this gets rid of the "longest" dependency in rustc:
![image](https://github.com/user-attachments/assets/3c3eb3a0-b7b3-40d9-aada-a752e28c8678)



Diff:
diff --git a/Cargo.lock b/Cargo.lock
index 97a90a44a398e..a5b41b6edd5fc 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -4520,7 +4520,6 @@ dependencies = [
  "itertools",
  "rustc_abi",
  "rustc_ast",
- "rustc_attr_parsing",
  "rustc_data_structures",
  "rustc_errors",
  "rustc_fluent_macro",
diff --git a/compiler/rustc_trait_selection/Cargo.toml b/compiler/rustc_trait_selection/Cargo.toml
index 1c61e23362a83..e6de2a3978d16 100644
--- a/compiler/rustc_trait_selection/Cargo.toml
+++ b/compiler/rustc_trait_selection/Cargo.toml
@@ -8,7 +8,6 @@ edition = "2024"
 itertools = "0.12"
 rustc_abi = { path = "../rustc_abi" }
 rustc_ast = { path = "../rustc_ast" }
-rustc_attr_parsing = { path = "../rustc_attr_parsing" }
 rustc_data_structures = { path = "../rustc_data_structures" }
 rustc_errors = { path = "../rustc_errors" }
 rustc_fluent_macro = { path = "../rustc_fluent_macro" }
diff --git a/compiler/rustc_trait_selection/messages.ftl b/compiler/rustc_trait_selection/messages.ftl
index 74e38f525c8e6..cf6dd40718b3d 100644
--- a/compiler/rustc_trait_selection/messages.ftl
+++ b/compiler/rustc_trait_selection/messages.ftl
@@ -148,9 +148,6 @@ trait_selection_dtcs_has_req_note = the used `impl` has a `'static` requirement
 trait_selection_dtcs_introduces_requirement = calling this method introduces the `impl`'s `'static` requirement
 trait_selection_dtcs_suggestion = consider relaxing the implicit `'static` requirement
 
-trait_selection_empty_on_clause_in_rustc_on_unimplemented = empty `on`-clause in `#[rustc_on_unimplemented]`
-    .label = empty on-clause here
-
 trait_selection_explicit_lifetime_required_sugg_with_ident = add explicit lifetime `{$named}` to the type of `{$simple_ident}`
 
 trait_selection_explicit_lifetime_required_sugg_with_param_type = add explicit lifetime `{$named}` to type
@@ -187,9 +184,6 @@ trait_selection_inherent_projection_normalization_overflow = overflow evaluating
 trait_selection_invalid_format_specifier = invalid format specifier
     .help = no format specifier are supported in this position
 
-trait_selection_invalid_on_clause_in_rustc_on_unimplemented = invalid `on`-clause in `#[rustc_on_unimplemented]`
-    .label = invalid on-clause here
-
 trait_selection_label_bad = {$bad_kind ->
     *[other] cannot infer type
     [more_info] cannot infer {$prefix_kind ->
@@ -237,10 +231,6 @@ trait_selection_negative_positive_conflict = found both positive and negative im
     .positive_implementation_here = positive implementation here
     .positive_implementation_in_crate = positive implementation in crate `{$positive_impl_cname}`
 
-trait_selection_no_value_in_rustc_on_unimplemented = this attribute must have a valid value
-    .label = expected value here
-    .note = eg `#[rustc_on_unimplemented(message="foo")]`
-
 trait_selection_nothing = {""}
 
 trait_selection_oc_cant_coerce_force_inline =
@@ -339,6 +329,22 @@ trait_selection_ril_introduced_by = requirement introduced by this return type
 trait_selection_ril_introduced_here = `'static` requirement introduced here
 trait_selection_ril_static_introduced_by = "`'static` lifetime requirement introduced by the return type
 
+trait_selection_rustc_on_unimplemented_empty_on_clause = empty `on`-clause in `#[rustc_on_unimplemented]`
+    .label = empty `on`-clause here
+trait_selection_rustc_on_unimplemented_expected_identifier = expected an identifier inside this `on`-clause
+    .label = expected an identifier here, not `{$path}`
+trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not = expected a single predicate in `not(..)`
+    .label = unexpected quantity of predicates here
+trait_selection_rustc_on_unimplemented_invalid_flag = invalid flag in `on`-clause
+    .label = expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `{$invalid_flag}`
+trait_selection_rustc_on_unimplemented_invalid_predicate = this predicate is invalid
+    .label = expected one of `any`, `all` or `not` here, not `{$invalid_pred}`
+trait_selection_rustc_on_unimplemented_missing_value = this attribute must have a value
+    .label = expected value here
+    .note = e.g. `#[rustc_on_unimplemented(message="foo")]`
+trait_selection_rustc_on_unimplemented_unsupported_literal_in_on = literals inside `on`-clauses are not supported
+    .label = unexpected literal here
+
 trait_selection_source_kind_closure_return =
     try giving this closure an explicit return type
 
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs
index 4c4491269b757..d5ee6e2123a19 100644
--- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented.rs
@@ -4,6 +4,7 @@ use std::path::PathBuf;
 use rustc_ast::{LitKind, MetaItem, MetaItemInner, MetaItemKind, MetaItemLit};
 use rustc_errors::codes::*;
 use rustc_errors::{ErrorGuaranteed, struct_span_code_err};
+use rustc_hir as hir;
 use rustc_hir::def_id::{DefId, LocalDefId};
 use rustc_hir::{AttrArgs, Attribute};
 use rustc_macros::LintDiagnostic;
@@ -13,17 +14,16 @@ use rustc_middle::ty::{self, GenericArgsRef, GenericParamDef, GenericParamDefKin
 use rustc_session::lint::builtin::UNKNOWN_OR_MALFORMED_DIAGNOSTIC_ATTRIBUTES;
 use rustc_span::{Span, Symbol, sym};
 use tracing::{debug, info};
-use {rustc_attr_parsing as attr, rustc_hir as hir};
 
 use super::{ObligationCauseCode, PredicateObligation};
 use crate::error_reporting::TypeErrCtxt;
-use crate::error_reporting::traits::on_unimplemented_condition::{Condition, ConditionOptions};
+use crate::error_reporting::traits::on_unimplemented_condition::{
+    ConditionOptions, OnUnimplementedCondition,
+};
 use crate::error_reporting::traits::on_unimplemented_format::{
     Ctx, FormatArgs, FormatString, FormatWarning,
 };
-use crate::errors::{
-    EmptyOnClauseInOnUnimplemented, InvalidOnClauseInOnUnimplemented, NoValueInOnUnimplemented,
-};
+use crate::errors::{InvalidOnClause, NoValueInOnUnimplemented};
 use crate::infer::InferCtxtExt;
 
 impl<'tcx> TypeErrCtxt<'_, 'tcx> {
@@ -306,21 +306,21 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> {
 #[derive(Clone, Debug)]
 pub struct OnUnimplementedFormatString {
     /// Symbol of the format string, i.e. `"content"`
-    pub symbol: Symbol,
+    symbol: Symbol,
     ///The span of the format string, i.e. `"content"`
-    pub span: Span,
-    pub is_diagnostic_namespace_variant: bool,
+    span: Span,
+    is_diagnostic_namespace_variant: bool,
 }
 
 #[derive(Debug)]
 pub struct OnUnimplementedDirective {
-    pub condition: Option<Condition>,
-    pub subcommands: Vec<OnUnimplementedDirective>,
-    pub message: Option<(Span, OnUnimplementedFormatString)>,
-    pub label: Option<(Span, OnUnimplementedFormatString)>,
-    pub notes: Vec<OnUnimplementedFormatString>,
-    pub parent_label: Option<OnUnimplementedFormatString>,
-    pub append_const_msg: Option<AppendConstMessage>,
+    condition: Option<OnUnimplementedCondition>,
+    subcommands: Vec<OnUnimplementedDirective>,
+    message: Option<(Span, OnUnimplementedFormatString)>,
+    label: Option<(Span, OnUnimplementedFormatString)>,
+    notes: Vec<OnUnimplementedFormatString>,
+    parent_label: Option<OnUnimplementedFormatString>,
+    append_const_msg: Option<AppendConstMessage>,
 }
 
 /// For the `#[rustc_on_unimplemented]` attribute
@@ -427,18 +427,12 @@ impl<'tcx> OnUnimplementedDirective {
         } else {
             let cond = item_iter
                 .next()
-                .ok_or_else(|| tcx.dcx().emit_err(EmptyOnClauseInOnUnimplemented { span }))?
-                .meta_item_or_bool()
-                .ok_or_else(|| tcx.dcx().emit_err(InvalidOnClauseInOnUnimplemented { span }))?;
-            attr::eval_condition(cond, &tcx.sess, Some(tcx.features()), &mut |cfg| {
-                if let Some(value) = cfg.value
-                    && let Err(guar) = parse_value(value, cfg.span)
-                {
-                    errored = Some(guar);
-                }
-                true
-            });
-            Some(Condition { inner: cond.clone() })
+                .ok_or_else(|| tcx.dcx().emit_err(InvalidOnClause::Empty { span }))?;
+
+            match OnUnimplementedCondition::parse(cond) {
+                Ok(condition) => Some(condition),
+                Err(e) => return Err(tcx.dcx().emit_err(e)),
+            }
         };
 
         let mut message = None;
@@ -724,7 +718,7 @@ impl<'tcx> OnUnimplementedDirective {
         result
     }
 
-    pub fn evaluate(
+    pub(crate) fn evaluate(
         &self,
         tcx: TyCtxt<'tcx>,
         trait_ref: ty::TraitRef<'tcx>,
@@ -744,7 +738,7 @@ impl<'tcx> OnUnimplementedDirective {
         for command in self.subcommands.iter().chain(Some(self)).rev() {
             debug!(?command);
             if let Some(ref condition) = command.condition
-                && !condition.matches_predicate(tcx, condition_options)
+                && !condition.matches_predicate(condition_options)
             {
                 debug!("evaluate: skipping {:?} due to condition", command);
                 continue;
diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
index 116cfb01cb631..13753761f0923 100644
--- a/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
+++ b/compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
@@ -1,52 +1,251 @@
-use rustc_ast::MetaItemInner;
-use rustc_attr_parsing as attr;
-use rustc_middle::ty::{self, TyCtxt};
+use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit};
 use rustc_parse_format::{ParseMode, Parser, Piece, Position};
-use rustc_span::{DesugaringKind, Span, Symbol, kw, sym};
+use rustc_span::{DesugaringKind, Ident, Span, Symbol, kw, sym};
 
-/// A predicate in an attribute using on, all, any,
-/// similar to a cfg predicate.
+use crate::errors::InvalidOnClause;
+
+/// Represents the `on` filter in `#[rustc_on_unimplemented]`.
 #[derive(Debug)]
-pub struct Condition {
-    pub inner: MetaItemInner,
+pub(crate) struct OnUnimplementedCondition {
+    span: Span,
+    pred: Predicate,
 }
 
-impl Condition {
-    pub fn span(&self) -> Span {
-        self.inner.span()
+impl OnUnimplementedCondition {
+    pub(crate) fn span(&self) -> Span {
+        self.span
+    }
+
+    pub(crate) fn matches_predicate(&self, options: &ConditionOptions) -> bool {
+        self.pred.eval(&mut |p| match p {
+            FlagOrNv::Flag(b) => options.has_flag(*b),
+            FlagOrNv::NameValue(NameValue { name, value }) => {
+                let value = value.format(&options.generic_args);
+                options.contains(*name, value)
+            }
+        })
+    }
+
+    pub(crate) fn parse(input: &MetaItemInner) -> Result<Self, InvalidOnClause> {
+        let span = input.span();
+        let pred = Predicate::parse(input)?;
+        Ok(OnUnimplementedCondition { span, pred })
     }
+}
 
-    pub fn matches_predicate<'tcx>(&self, tcx: TyCtxt<'tcx>, options: &ConditionOptions) -> bool {
-        attr::eval_condition(&self.inner, tcx.sess, Some(tcx.features()), &mut |cfg| {
-            let value = cfg.value.map(|v| {
-                // `with_no_visible_paths` is also used when generating the options,
-                // so we need to match it here.
-                ty::print::with_no_visible_paths!({
-                    Parser::new(v.as_str(), None, None, false, ParseMode::Format)
-                        .map(|p| match p {
-                            Piece::Lit(s) => s.to_owned(),
-                            Piece::NextArgument(a) => match a.position {
-                                Position::ArgumentNamed(arg) => {
-                                    let s = Symbol::intern(arg);
-                                    match options.generic_args.iter().find(|(k, _)| *k == s) {
-                                        Some((_, val)) => val.to_string(),
-                                        None => format!("{{{arg}}}"),
-                                    }
-                                }
-                                Position::ArgumentImplicitlyIs(_) => String::from("{}"),
-                                Position::ArgumentIs(idx) => format!("{{{idx}}}"),
-                            },
-                        })
-                        .collect()
-                })
+/// Predicate(s) in `#[rustc_on_unimplemented]`'s `on` filter. See [`OnUnimplementedCondition`].
+///
+/// It is similar to the predicate in the `cfg` attribute,
+/// and may contain nested predicates.
+#[derive(Debug)]
+enum Predicate {
+    /// A condition like `on(crate_local)`.
+    Flag(Flag),
+    /// A match, like `on(Rhs = "Whatever")`.
+    Match(NameValue),
+    /// Negation, like `on(not($pred))`.
+    Not(Box<Predicate>),
+    /// True if all predicates are true, like `on(all($a, $b, $c))`.
+    All(Vec<Predicate>),
+    /// True if any predicate is true, like `on(any($a, $b, $c))`.
+    Any(Vec<Predicate>),
+}
+
+impl Predicate {
+    fn parse(input: &MetaItemInner) -> Result<Self, InvalidOnClause> {
+        let meta_item = match input {
+            MetaItemInner::MetaItem(meta_item) => meta_item,
+            MetaItemInner::Lit(lit) => {
+                return Err(InvalidOnClause::UnsupportedLiteral { span: lit.span });
+            }
+        };
+
+        let Some(predicate) = meta_item.ident() else {
+            return Err(InvalidOnClause::ExpectedIdentifier {
+                span: meta_item.path.span,
+                path: meta_item.path.clone(),
             });
+        };
 
-            options.contains(cfg.name, &value)
-        })
+        match meta_item.kind {
+            MetaItemKind::List(ref mis) => match predicate.name {
+                sym::any => Ok(Predicate::Any(Predicate::parse_sequence(mis)?)),
+                sym::all => Ok(Predicate::All(Predicate::parse_sequence(mis)?)),
+                sym::not => match &**mis {
+                    [one] => Ok(Predicate::Not(Box::new(Predicate::parse(one)?))),
+                    [first, .., last] => Err(InvalidOnClause::ExpectedOnePredInNot {
+                        span: first.span().to(last.span()),
+                    }),
+                    [] => Err(InvalidOnClause::ExpectedOnePredInNot { span: meta_item.span }),
+                },
+                invalid_pred => {
+                    Err(InvalidOnClause::InvalidPredicate { span: predicate.span, invalid_pred })
+                }
+            },
+            MetaItemKind::NameValue(MetaItemLit { symbol, .. }) => {
+                let name = Name::parse(predicate);
+                let value = FilterFormatString::parse(symbol);
+                let kv = NameValue { name, value };
+                Ok(Predicate::Match(kv))
+            }
+            MetaItemKind::Word => {
+                let flag = Flag::parse(predicate)?;
+                Ok(Predicate::Flag(flag))
+            }
+        }
+    }
+
+    fn parse_sequence(sequence: &[MetaItemInner]) -> Result<Vec<Self>, InvalidOnClause> {
+        sequence.iter().map(Predicate::parse).collect()
+    }
+
+    fn eval(&self, eval: &mut impl FnMut(FlagOrNv<'_>) -> bool) -> bool {
+        match self {
+            Predicate::Flag(flag) => eval(FlagOrNv::Flag(flag)),
+            Predicate::Match(nv) => eval(FlagOrNv::NameValue(nv)),
+            Predicate::Not(not) => !not.eval(eval),
+            Predicate::All(preds) => preds.into_iter().all(|pred| pred.eval(eval)),
+            Predicate::Any(preds) => preds.into_iter().any(|pred| pred.eval(eval)),
+        }
     }
 }
 
-/// Used with `Condition::matches_predicate` to test whether the condition applies
+/// Represents a `MetaWord` in an `on`-filter.
+#[derive(Debug, Clone, Copy)]
+enum Flag {
+    /// Whether the code causing the trait bound to not be fulfilled
+    /// is part of the user's crate.
+    CrateLocal,
+    /// Whether the obligation is user-specified rather than derived.
+    Direct,
+    /// Whether we are in some kind of desugaring like
+    /// `?` or `try { .. }`.
+    FromDesugaring,
+}
+
+impl Flag {
+    fn parse(Ident { name, span }: Ident) -> Result<Self, InvalidOnClause> {
+        match name {
+            sym::crate_local => Ok(Flag::CrateLocal),
+            sym::direct => Ok(Flag::Direct),
+            sym::from_desugaring => Ok(Flag::FromDesugaring),
+            invalid_flag => Err(InvalidOnClause::InvalidFlag { invalid_flag, span }),
+        }
+    }
+}
+
+/// A `MetaNameValueStr` in an `on`-filter.
+///
+/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`.
+#[derive(Debug, Clone)]
+struct NameValue {
+    name: Name,
+    /// Something like `"&str"` or `"alloc::string::String"`,
+    /// in which case it just contains a single string piece.
+    /// But if it is something like `"&[{A}]"` then it must be formatted later.
+    value: FilterFormatString,
+}
+
+/// The valid names of the `on` filter.
+#[derive(Debug, Clone, Copy)]
+enum Name {
+    Cause,
+    FromDesugaring,
+    SelfUpper,
+    GenericArg(Symbol),
+}
+
+impl Name {
+    fn parse(Ident { name, .. }: Ident) -> Self {
+        match name {
+            sym::_Self | kw::SelfUpper => Name::SelfUpper,
+            sym::from_desugaring => Name::FromDesugaring,
+            sym::cause => Name::Cause,
+            // FIXME(mejrs) Perhaps we should start checking that
+            // this actually is a valid generic parameter?
+            generic => Name::GenericArg(generic),
+        }
+    }
+}
+
+#[derive(Debug, Clone)]
+enum FlagOrNv<'p> {
+    Flag(&'p Flag),
+    NameValue(&'p NameValue),
+}
+
+/// Represents a value inside an `on` filter.
+///
+/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`.
+/// If it is a simple literal like this then `pieces` will be `[LitOrArg::Lit("value")]`.
+/// The `Arg` variant is used when it contains formatting like
+/// `#[rustc_on_unimplemented(on(Self = "&[{A}]", message = "hello"))]`.
+#[derive(Debug, Clone)]
+struct FilterFormatString {
+    pieces: Vec<LitOrArg>,
+}
+
+#[derive(Debug, Clone)]
+enum LitOrArg {
+    Lit(String),
+    Arg(String),
+}
+
+impl FilterFormatString {
+    fn parse(input: Symbol) -> Self {
+        let pieces = Parser::new(input.as_str(), None, None, false, ParseMode::Format)
+            .map(|p| match p {
+                Piece::Lit(s) => LitOrArg::Lit(s.to_owned()),
+                // We just ignore formatspecs here
+                Piece::NextArgument(a) => match a.position {
+                    // In `TypeErrCtxt::on_unimplemented_note` we substitute `"{integral}"` even
+                    // if the integer type has been resolved, to allow targeting all integers.
+                    // `"{integer}"` and `"{float}"` come from numerics that haven't been inferred yet,
+                    // from the `Display` impl of `InferTy` to be precise.
+                    //
+                    // Don't try to format these later!
+                    Position::ArgumentNamed(arg @ "integer" | arg @ "integral" | arg @ "float") => {
+                        LitOrArg::Lit(format!("{{{arg}}}"))
+                    }
+
+                    // FIXME(mejrs) We should check if these correspond to a generic of the trait.
+                    Position::ArgumentNamed(arg) => LitOrArg::Arg(arg.to_owned()),
+
+                    // FIXME(mejrs) These should really be warnings/errors
+                    Position::ArgumentImplicitlyIs(_) => LitOrArg::Lit(String::from("{}")),
+                    Position::ArgumentIs(idx) => LitOrArg::Lit(format!("{{{idx}}}")),
+                },
+            })
+            .collect();
+        Self { pieces }
+    }
+
+    fn format(&self, generic_args: &[(Symbol, String)]) -> String {
+        let mut ret = String::new();
+
+        for piece in &self.pieces {
+            match piece {
+                LitOrArg::Lit(s) => ret.push_str(s),
+                LitOrArg::Arg(arg) => {
+                    let s = Symbol::intern(arg);
+                    match generic_args.iter().find(|(k, _)| *k == s) {
+                        Some((_, val)) => ret.push_str(val),
+                        None => {
+                            // FIXME(mejrs) If we start checking as mentioned in
+                            // FilterFormatString::parse then this shouldn't happen
+                            let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}"));
+                        }
+                    }
+                }
+            }
+        }
+
+        ret
+    }
+}
+
+/// Used with `OnUnimplementedCondition::matches_predicate` to evaluate the
+/// [`OnUnimplementedCondition`].
 ///
 /// For example, given a
 /// ```rust,ignore (just an example)
@@ -85,36 +284,34 @@ impl Condition {
 /// }
 /// ```
 #[derive(Debug)]
-pub struct ConditionOptions {
+pub(crate) struct ConditionOptions {
     /// All the self types that may apply.
-    /// for example
-    pub self_types: Vec<String>,
+    pub(crate) self_types: Vec<String>,
     // The kind of compiler desugaring.
-    pub from_desugaring: Option<DesugaringKind>,
-    /// Match on a variant of [rustc_infer::traits::ObligationCauseCode]
-    pub cause: Option<String>,
-    pub crate_local: bool,
+    pub(crate) from_desugaring: Option<DesugaringKind>,
+    /// Match on a variant of [rustc_infer::traits::ObligationCauseCode].
+    pub(crate) cause: Option<String>,
+    pub(crate) crate_local: bool,
     /// Is the obligation "directly" user-specified, rather than derived?
-    pub direct: bool,
-    // A list of the generic arguments and their reified types
-    pub generic_args: Vec<(Symbol, String)>,
+    pub(crate) direct: bool,
+    // A list of the generic arguments and their reified types.
+    pub(crate) generic_args: Vec<(Symbol, String)>,
 }
 
 impl ConditionOptions {
-    pub fn contains(&self, key: Symbol, value: &Option<String>) -> bool {
-        match (key, value) {
-            (sym::_Self | kw::SelfUpper, Some(value)) => self.self_types.contains(&value),
-            // from_desugaring as a flag
-            (sym::from_desugaring, None) => self.from_desugaring.is_some(),
-            // from_desugaring as key == value
-            (sym::from_desugaring, Some(v)) if let Some(ds) = self.from_desugaring => ds.matches(v),
-            (sym::cause, Some(value)) => self.cause.as_deref() == Some(value),
-            (sym::crate_local, None) => self.crate_local,
-            (sym::direct, None) => self.direct,
-            (other, Some(value)) => {
-                self.generic_args.iter().any(|(k, v)| *k == other && v == value)
-            }
-            _ => false,
+    fn has_flag(&self, name: Flag) -> bool {
+        match name {
+            Flag::CrateLocal => self.crate_local,
+            Flag::Direct => self.direct,
+            Flag::FromDesugaring => self.from_desugaring.is_some(),
+        }
+    }
+    fn contains(&self, name: Name, value: String) -> bool {
+        match name {
+            Name::SelfUpper => self.self_types.contains(&value),
+            Name::FromDesugaring => self.from_desugaring.is_some_and(|ds| ds.matches(&value)),
+            Name::Cause => self.cause == Some(value),
+            Name::GenericArg(arg) => self.generic_args.contains(&(arg, value)),
         }
     }
 }
diff --git a/compiler/rustc_trait_selection/src/errors.rs b/compiler/rustc_trait_selection/src/errors.rs
index 756d9a57b935c..21d0531248104 100644
--- a/compiler/rustc_trait_selection/src/errors.rs
+++ b/compiler/rustc_trait_selection/src/errors.rs
@@ -1,5 +1,6 @@
 use std::path::PathBuf;
 
+use rustc_ast::Path;
 use rustc_data_structures::fx::{FxHashSet, FxIndexSet};
 use rustc_errors::codes::*;
 use rustc_errors::{
@@ -31,23 +32,50 @@ pub struct UnableToConstructConstantValue<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(trait_selection_empty_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct EmptyOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(trait_selection_invalid_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct InvalidOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
+pub enum InvalidOnClause {
+    #[diag(trait_selection_rustc_on_unimplemented_empty_on_clause, code = E0232)]
+    Empty {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not, code = E0232)]
+    ExpectedOnePredInNot {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_unsupported_literal_in_on, code = E0232)]
+    UnsupportedLiteral {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_expected_identifier, code = E0232)]
+    ExpectedIdentifier {
+        #[primary_span]
+        #[label]
+        span: Span,
+        path: Path,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_invalid_predicate, code = E0232)]
+    InvalidPredicate {
+        #[primary_span]
+        #[label]
+        span: Span,
+        invalid_pred: Symbol,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_invalid_flag, code = E0232)]
+    InvalidFlag {
+        #[primary_span]
+        #[label]
+        span: Span,
+        invalid_flag: Symbol,
+    },
 }
 
 #[derive(Diagnostic)]
-#[diag(trait_selection_no_value_in_rustc_on_unimplemented, code = E0232)]
+#[diag(trait_selection_rustc_on_unimplemented_missing_value, code = E0232)]
 #[note]
 pub struct NoValueInOnUnimplemented {
     #[primary_span]
diff --git a/tests/ui/on-unimplemented/bad-annotation.rs b/tests/ui/on-unimplemented/bad-annotation.rs
index f2b978657597a..25de597811027 100644
--- a/tests/ui/on-unimplemented/bad-annotation.rs
+++ b/tests/ui/on-unimplemented/bad-annotation.rs
@@ -1,64 +1,109 @@
-// ignore-tidy-linelength
-
+#![crate_type = "lib"]
 #![feature(rustc_attrs)]
-
 #![allow(unused)]
 
 #[rustc_on_unimplemented = "test error `{Self}` with `{Bar}` `{Baz}` `{Quux}`"]
-trait Foo<Bar, Baz, Quux>
-{}
+trait Foo<Bar, Baz, Quux> {}
 
-#[rustc_on_unimplemented="a collection of type `{Self}` cannot be built from an iterator over elements of type `{A}`"]
+#[rustc_on_unimplemented = "a collection of type `{Self}` cannot \
+ be built from an iterator over elements of type `{A}`"]
 trait MyFromIterator<A> {
     /// Builds a container with elements from an external iterator.
-    fn my_from_iter<T: Iterator<Item=A>>(iterator: T) -> Self;
+    fn my_from_iter<T: Iterator<Item = A>>(iterator: T) -> Self;
 }
 
 #[rustc_on_unimplemented]
 //~^ ERROR malformed `rustc_on_unimplemented` attribute
-trait BadAnnotation1
-{}
+trait NoContent {}
 
 #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"]
 //~^ ERROR cannot find parameter C on this trait
-trait BadAnnotation2<A,B>
-{}
+trait ParameterNotPresent<A, B> {}
 
 #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"]
 //~^ ERROR positional format arguments are not allowed here
-trait BadAnnotation3<A,B>
-{}
+trait NoPositionalArgs<A, B> {}
 
-#[rustc_on_unimplemented(lorem="")]
-//~^ ERROR this attribute must have a valid
-trait BadAnnotation4 {}
+#[rustc_on_unimplemented(lorem = "")]
+//~^ ERROR this attribute must have a value
+//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
+//~^^^ NOTE expected value here
+trait EmptyMessage {}
 
 #[rustc_on_unimplemented(lorem(ipsum(dolor)))]
-//~^ ERROR this attribute must have a valid
-trait BadAnnotation5 {}
-
-#[rustc_on_unimplemented(message="x", message="y")]
-//~^ ERROR this attribute must have a valid
-trait BadAnnotation6 {}
-
-#[rustc_on_unimplemented(message="x", on(desugared, message="y"))]
-//~^ ERROR this attribute must have a valid
-trait BadAnnotation7 {}
-
-#[rustc_on_unimplemented(on(), message="y")]
+//~^ ERROR this attribute must have a value
+//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
+//~^^^ NOTE expected value here
+trait Invalid {}
+
+#[rustc_on_unimplemented(message = "x", message = "y")]
+//~^ ERROR this attribute must have a value
+//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
+//~^^^ NOTE expected value here
+trait DuplicateMessage {}
+
+#[rustc_on_unimplemented(message = "x", on(desugared, message = "y"))]
+//~^ ERROR this attribute must have a value
+//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
+//~^^^ NOTE expected value here
+trait OnInWrongPosition {}
+
+#[rustc_on_unimplemented(on(), message = "y")]
 //~^ ERROR empty `on`-clause
-trait BadAnnotation8 {}
-
-#[rustc_on_unimplemented(on="x", message="y")]
-//~^ ERROR this attribute must have a valid
-trait BadAnnotation9 {}
-
-#[rustc_on_unimplemented(on(x="y"), message="y")]
-trait BadAnnotation10 {}
-
-#[rustc_on_unimplemented(on(desugared, on(desugared, message="x")), message="y")]
-//~^ ERROR this attribute must have a valid
-trait BadAnnotation11 {}
-
-pub fn main() {
-}
+//~^^ NOTE empty `on`-clause here
+trait EmptyOn {}
+
+#[rustc_on_unimplemented(on = "x", message = "y")]
+//~^ ERROR this attribute must have a value
+//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
+//~^^^ NOTE expected value here
+trait ExpectedPredicateInOn {}
+
+#[rustc_on_unimplemented(on(x = "y"), message = "y")]
+trait OnWithoutDirectives {}
+
+#[rustc_on_unimplemented(on(from_desugaring, on(from_desugaring, message = "x")), message = "y")]
+//~^ ERROR this attribute must have a value
+//~^^ NOTE e.g. `#[rustc_on_unimplemented(message="foo")]`
+//~^^^ NOTE expected value here
+trait NestedOn {}
+
+#[rustc_on_unimplemented(on("y", message = "y"))]
+//~^ ERROR literals inside `on`-clauses are not supported
+//~^^ NOTE unexpected literal here
+trait UnsupportedLiteral {}
+
+#[rustc_on_unimplemented(on(42, message = "y"))]
+//~^ ERROR literals inside `on`-clauses are not supported
+//~^^ NOTE unexpected literal here
+trait UnsupportedLiteral2 {}
+
+#[rustc_on_unimplemented(on(not(a, b), message = "y"))]
+//~^ ERROR expected a single predicate in `not(..)` [E0232]
+//~^^ NOTE unexpected quantity of predicates here
+trait ExpectedOnePattern {}
+
+#[rustc_on_unimplemented(on(not(), message = "y"))]
+//~^ ERROR expected a single predicate in `not(..)` [E0232]
+//~^^ NOTE unexpected quantity of predicates here
+trait ExpectedOnePattern2 {}
+
+#[rustc_on_unimplemented(on(thing::What, message = "y"))]
+//~^ ERROR expected an identifier inside this `on`-clause
+//~^^ NOTE expected an identifier here, not `thing::What`
+trait KeyMustBeIdentifier {}
+
+#[rustc_on_unimplemented(on(thing::What = "value", message = "y"))]
+//~^ ERROR  expected an identifier inside this `on`-clause
+//~^^ NOTE expected an identifier here, not `thing::What`
+trait KeyMustBeIdentifier2 {}
+
+#[rustc_on_unimplemented(on(aaaaaaaaaaaaaa(a, b), message = "y"))]
+//~^ ERROR this predicate is invalid
+//~^^ NOTE expected one of `any`, `all` or `not` here, not `aaaaaaaaaaaaaa`
+trait InvalidPredicate {}
+
+#[rustc_on_unimplemented(on(something, message = "y"))]
+//~^ ERROR invalid flag in `on`-clause
+//~^^ NOTE expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `something`
+trait InvalidFlag {}
diff --git a/tests/ui/on-unimplemented/bad-annotation.stderr b/tests/ui/on-unimplemented/bad-annotation.stderr
index afd737dc85e69..35b919c7b7853 100644
--- a/tests/ui/on-unimplemented/bad-annotation.stderr
+++ b/tests/ui/on-unimplemented/bad-annotation.stderr
@@ -1,5 +1,5 @@
 error: malformed `rustc_on_unimplemented` attribute input
-  --> $DIR/bad-annotation.rs:17:1
+  --> $DIR/bad-annotation.rs:15:1
    |
 LL | #[rustc_on_unimplemented]
    | ^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -12,72 +12,120 @@ LL | #[rustc_on_unimplemented(/*opt*/ message = "...", /*opt*/ label = "...", /*
    |                         ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
 
 error[E0230]: cannot find parameter C on this trait
-  --> $DIR/bad-annotation.rs:22:90
+  --> $DIR/bad-annotation.rs:19:90
    |
 LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{C}>`"]
    |                                                                                          ^
 
 error[E0231]: positional format arguments are not allowed here
-  --> $DIR/bad-annotation.rs:27:90
+  --> $DIR/bad-annotation.rs:23:90
    |
 LL | #[rustc_on_unimplemented = "Unimplemented trait error on `{Self}` with params `<{A},{B},{}>`"]
    |                                                                                          ^
 
-error[E0232]: this attribute must have a valid value
-  --> $DIR/bad-annotation.rs:32:26
+error[E0232]: this attribute must have a value
+  --> $DIR/bad-annotation.rs:27:26
    |
-LL | #[rustc_on_unimplemented(lorem="")]
-   |                          ^^^^^^^^ expected value here
+LL | #[rustc_on_unimplemented(lorem = "")]
+   |                          ^^^^^^^^^^ expected value here
    |
-   = note: eg `#[rustc_on_unimplemented(message="foo")]`
+   = note: e.g. `#[rustc_on_unimplemented(message="foo")]`
 
-error[E0232]: this attribute must have a valid value
-  --> $DIR/bad-annotation.rs:36:26
+error[E0232]: this attribute must have a value
+  --> $DIR/bad-annotation.rs:33:26
    |
 LL | #[rustc_on_unimplemented(lorem(ipsum(dolor)))]
    |                          ^^^^^^^^^^^^^^^^^^^ expected value here
    |
-   = note: eg `#[rustc_on_unimplemented(message="foo")]`
+   = note: e.g. `#[rustc_on_unimplemented(message="foo")]`
 
-error[E0232]: this attribute must have a valid value
-  --> $DIR/bad-annotation.rs:40:39
+error[E0232]: this attribute must have a value
+  --> $DIR/bad-annotation.rs:39:41
    |
-LL | #[rustc_on_unimplemented(message="x", message="y")]
-   |                                       ^^^^^^^^^^^ expected value here
+LL | #[rustc_on_unimplemented(message = "x", message = "y")]
+   |                                         ^^^^^^^^^^^^^ expected value here
    |
-   = note: eg `#[rustc_on_unimplemented(message="foo")]`
+   = note: e.g. `#[rustc_on_unimplemented(message="foo")]`
 
-error[E0232]: this attribute must have a valid value
-  --> $DIR/bad-annotation.rs:44:39
+error[E0232]: this attribute must have a value
+  --> $DIR/bad-annotation.rs:45:41
    |
-LL | #[rustc_on_unimplemented(message="x", on(desugared, message="y"))]
-   |                                       ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
+LL | #[rustc_on_unimplemented(message = "x", on(desugared, message = "y"))]
+   |                                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
    |
-   = note: eg `#[rustc_on_unimplemented(message="foo")]`
+   = note: e.g. `#[rustc_on_unimplemented(message="foo")]`
 
 error[E0232]: empty `on`-clause in `#[rustc_on_unimplemented]`
-  --> $DIR/bad-annotation.rs:48:26
+  --> $DIR/bad-annotation.rs:51:26
+   |
+LL | #[rustc_on_unimplemented(on(), message = "y")]
+   |                          ^^^^ empty `on`-clause here
+
+error[E0232]: this attribute must have a value
+  --> $DIR/bad-annotation.rs:56:26
+   |
+LL | #[rustc_on_unimplemented(on = "x", message = "y")]
+   |                          ^^^^^^^^ expected value here
+   |
+   = note: e.g. `#[rustc_on_unimplemented(message="foo")]`
+
+error[E0232]: this attribute must have a value
+  --> $DIR/bad-annotation.rs:65:46
+   |
+LL | #[rustc_on_unimplemented(on(from_desugaring, on(from_desugaring, message = "x")), message = "y")]
+   |                                              ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
+   |
+   = note: e.g. `#[rustc_on_unimplemented(message="foo")]`
+
+error[E0232]: literals inside `on`-clauses are not supported
+  --> $DIR/bad-annotation.rs:71:29
    |
-LL | #[rustc_on_unimplemented(on(), message="y")]
-   |                          ^^^^ empty on-clause here
+LL | #[rustc_on_unimplemented(on("y", message = "y"))]
+   |                             ^^^ unexpected literal here
 
-error[E0232]: this attribute must have a valid value
-  --> $DIR/bad-annotation.rs:52:26
+error[E0232]: literals inside `on`-clauses are not supported
+  --> $DIR/bad-annotation.rs:76:29
    |
-LL | #[rustc_on_unimplemented(on="x", message="y")]
-   |                          ^^^^^^ expected value here
+LL | #[rustc_on_unimplemented(on(42, message = "y"))]
+   |                             ^^ unexpected literal here
+
+error[E0232]: expected a single predicate in `not(..)`
+  --> $DIR/bad-annotation.rs:81:33
    |
-   = note: eg `#[rustc_on_unimplemented(message="foo")]`
+LL | #[rustc_on_unimplemented(on(not(a, b), message = "y"))]
+   |                                 ^^^^ unexpected quantity of predicates here
 
-error[E0232]: this attribute must have a valid value
-  --> $DIR/bad-annotation.rs:59:40
+error[E0232]: expected a single predicate in `not(..)`
+  --> $DIR/bad-annotation.rs:86:29
    |
-LL | #[rustc_on_unimplemented(on(desugared, on(desugared, message="x")), message="y")]
-   |                                        ^^^^^^^^^^^^^^^^^^^^^^^^^^ expected value here
+LL | #[rustc_on_unimplemented(on(not(), message = "y"))]
+   |                             ^^^^^ unexpected quantity of predicates here
+
+error[E0232]: expected an identifier inside this `on`-clause
+  --> $DIR/bad-annotation.rs:91:29
+   |
+LL | #[rustc_on_unimplemented(on(thing::What, message = "y"))]
+   |                             ^^^^^^^^^^^ expected an identifier here, not `thing::What`
+
+error[E0232]: expected an identifier inside this `on`-clause
+  --> $DIR/bad-annotation.rs:96:29
+   |
+LL | #[rustc_on_unimplemented(on(thing::What = "value", message = "y"))]
+   |                             ^^^^^^^^^^^ expected an identifier here, not `thing::What`
+
+error[E0232]: this predicate is invalid
+  --> $DIR/bad-annotation.rs:101:29
+   |
+LL | #[rustc_on_unimplemented(on(aaaaaaaaaaaaaa(a, b), message = "y"))]
+   |                             ^^^^^^^^^^^^^^ expected one of `any`, `all` or `not` here, not `aaaaaaaaaaaaaa`
+
+error[E0232]: invalid flag in `on`-clause
+  --> $DIR/bad-annotation.rs:106:29
    |
-   = note: eg `#[rustc_on_unimplemented(message="foo")]`
+LL | #[rustc_on_unimplemented(on(something, message = "y"))]
+   |                             ^^^^^^^^^ expected one of the `crate_local`, `direct` or `from_desugaring` flags, not `something`
 
-error: aborting due to 10 previous errors
+error: aborting due to 18 previous errors
 
 Some errors have detailed explanations: E0230, E0231, E0232.
 For more information about an error, try `rustc --explain E0230`.


Reviews:
user: login: nnethercote
body: Generally looks ok, a bunch of nitpicks below.
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T03:39:38Z
user: login: mejrs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T11:32:10Z
user: login: mejrs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-01T11:33:29Z
user: login: nnethercote
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T06:24:47Z
user: login: mejrs
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-05-02T20:10:11Z

Review comments:
diff_hunk: @@ -339,6 +329,22 @@ trait_selection_ril_introduced_by = requirement introduced by this return type
 trait_selection_ril_introduced_here = `'static` requirement introduced here
 trait_selection_ril_static_introduced_by = "`'static` lifetime requirement introduced by the return type
 
+trait_selection_rustc_on_unimplemented_empty_on_clause = empty `on`-clause in `#[rustc_on_unimplemented]`
+    .label = empty `on`-clause here
+trait_selection_rustc_on_unimplemented_expected_identifier = expected an ident inside this `on`-clause
+    .label = expected an ident here, not `{$predicate}`
+trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not = expected a single predicate in `not(..)`
+    .label = unexpected quantity of predicates here
+trait_selection_rustc_on_unimplemented_invalid_boolean = invalid boolean flag
+    .label = expected one of `crate_local`, `direct` or `from_desugaring`, not `{$invalid_flag}`
+trait_selection_rustc_on_unimplemented_invalid_predicate = this predicate is invalid
+    .label = expected one of `any`, `all` or `not` here, not `{$invalid_pred}`
+trait_selection_rustc_on_unimplemented_no_value = this attribute must have a valid value
+    .label = expected value here
+    .note = eg `#[rustc_on_unimplemented(message="foo")]`
path: compiler/rustc_trait_selection/messages.ftl
user: login: nnethercote
body: Should be "e.g.", not "eg".
created_at: 2025-05-01T02:09:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,52 +1,250 @@
-use rustc_ast::MetaItemInner;
-use rustc_attr_parsing as attr;
-use rustc_middle::ty::{self, TyCtxt};
+use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit};
 use rustc_parse_format::{ParseMode, Parser, Piece, Position};
-use rustc_span::{DesugaringKind, Span, Symbol, kw, sym};
+use rustc_span::{DesugaringKind, Ident, Span, Symbol, kw, sym};
 
-/// A predicate in an attribute using on, all, any,
-/// similar to a cfg predicate.
+use crate::errors::InvalidOnClause;
+
+/// Represents the `on` filter in `#[rustc_on_unimplemented]`
 #[derive(Debug)]
 pub struct Condition {
-    pub inner: MetaItemInner,
+    span: Span,
+    pred: Predicate,
 }
 
 impl Condition {
     pub fn span(&self) -> Span {
-        self.inner.span()
+        self.span
+    }
+
+    pub fn matches_predicate(&self, options: &ConditionOptions) -> bool {
+        self.pred.eval(&mut |p| match p {
+            FlagOrNv::Flag(b) => options.has_flag(*b),
+            FlagOrNv::NameValue(NameValue { name, value }) => {
+                let value = value.format(&options.generic_args);
+                options.contains(*name, value)
+            }
+        })
+    }
+
+    pub fn parse(input: &MetaItemInner) -> Result<Self, InvalidOnClause> {
+        let span = input.span();
+        let pred = Predicate::parse(input)?;
+        Ok(Condition { span, pred })
     }
+}
 
-    pub fn matches_predicate<'tcx>(&self, tcx: TyCtxt<'tcx>, options: &ConditionOptions) -> bool {
-        attr::eval_condition(&self.inner, tcx.sess, Some(tcx.features()), &mut |cfg| {
-            let value = cfg.value.map(|v| {
-                // `with_no_visible_paths` is also used when generating the options,
-                // so we need to match it here.
-                ty::print::with_no_visible_paths!({
-                    Parser::new(v.as_str(), None, None, false, ParseMode::Format)
-                        .map(|p| match p {
-                            Piece::Lit(s) => s.to_owned(),
-                            Piece::NextArgument(a) => match a.position {
-                                Position::ArgumentNamed(arg) => {
-                                    let s = Symbol::intern(arg);
-                                    match options.generic_args.iter().find(|(k, _)| *k == s) {
-                                        Some((_, val)) => val.to_string(),
-                                        None => format!("{{{arg}}}"),
-                                    }
-                                }
-                                Position::ArgumentImplicitlyIs(_) => String::from("{}"),
-                                Position::ArgumentIs(idx) => format!("{{{idx}}}"),
-                            },
-                        })
-                        .collect()
-                })
+/// A predicate in a `on` filter. See [`Condition`].
path: compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
user: login: nnethercote
body: s/a/an/
created_at: 2025-05-01T03:22:56Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,52 +1,250 @@
-use rustc_ast::MetaItemInner;
-use rustc_attr_parsing as attr;
-use rustc_middle::ty::{self, TyCtxt};
+use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit};
 use rustc_parse_format::{ParseMode, Parser, Piece, Position};
-use rustc_span::{DesugaringKind, Span, Symbol, kw, sym};
+use rustc_span::{DesugaringKind, Ident, Span, Symbol, kw, sym};
 
-/// A predicate in an attribute using on, all, any,
-/// similar to a cfg predicate.
+use crate::errors::InvalidOnClause;
+
+/// Represents the `on` filter in `#[rustc_on_unimplemented]`
 #[derive(Debug)]
 pub struct Condition {
-    pub inner: MetaItemInner,
+    span: Span,
+    pred: Predicate,
 }
 
 impl Condition {
     pub fn span(&self) -> Span {
-        self.inner.span()
+        self.span
+    }
+
+    pub fn matches_predicate(&self, options: &ConditionOptions) -> bool {
+        self.pred.eval(&mut |p| match p {
+            FlagOrNv::Flag(b) => options.has_flag(*b),
+            FlagOrNv::NameValue(NameValue { name, value }) => {
+                let value = value.format(&options.generic_args);
+                options.contains(*name, value)
+            }
+        })
+    }
+
+    pub fn parse(input: &MetaItemInner) -> Result<Self, InvalidOnClause> {
+        let span = input.span();
+        let pred = Predicate::parse(input)?;
+        Ok(Condition { span, pred })
     }
+}
 
-    pub fn matches_predicate<'tcx>(&self, tcx: TyCtxt<'tcx>, options: &ConditionOptions) -> bool {
-        attr::eval_condition(&self.inner, tcx.sess, Some(tcx.features()), &mut |cfg| {
-            let value = cfg.value.map(|v| {
-                // `with_no_visible_paths` is also used when generating the options,
-                // so we need to match it here.
-                ty::print::with_no_visible_paths!({
-                    Parser::new(v.as_str(), None, None, false, ParseMode::Format)
-                        .map(|p| match p {
-                            Piece::Lit(s) => s.to_owned(),
-                            Piece::NextArgument(a) => match a.position {
-                                Position::ArgumentNamed(arg) => {
-                                    let s = Symbol::intern(arg);
-                                    match options.generic_args.iter().find(|(k, _)| *k == s) {
-                                        Some((_, val)) => val.to_string(),
-                                        None => format!("{{{arg}}}"),
-                                    }
-                                }
-                                Position::ArgumentImplicitlyIs(_) => String::from("{}"),
-                                Position::ArgumentIs(idx) => format!("{{{idx}}}"),
-                            },
-                        })
-                        .collect()
-                })
+/// A predicate in a `on` filter. See [`Condition`].
+///
+/// It is similar to the predicate in the `cfg` attribute,
+/// and may contain nested predicates.
+#[derive(Debug)]
+enum Predicate {
+    /// A condition like `on(crate_local)`
path: compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
user: login: nnethercote
body: '.' at end of the comment, here and on the fields below.
created_at: 2025-05-01T03:23:40Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,23 +32,50 @@ pub struct UnableToConstructConstantValue<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(trait_selection_empty_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct EmptyOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(trait_selection_invalid_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct InvalidOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
+pub enum InvalidOnClause {
+    #[diag(trait_selection_rustc_on_unimplemented_empty_on_clause, code = E0232)]
+    Empty {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not, code = E0232)]
+    ExpectedOnePredInNot {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_on_clause_literal, code = E0232)]
+    UnSupportedLiteral {
path: compiler/rustc_trait_selection/src/errors.rs
user: login: nnethercote
body: s/UnSupported/Unsupported/
created_at: 2025-05-01T03:24:55Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,23 +32,50 @@ pub struct UnableToConstructConstantValue<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(trait_selection_empty_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct EmptyOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(trait_selection_invalid_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct InvalidOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
+pub enum InvalidOnClause {
+    #[diag(trait_selection_rustc_on_unimplemented_empty_on_clause, code = E0232)]
+    Empty {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not, code = E0232)]
+    ExpectedOnePredInNot {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_on_clause_literal, code = E0232)]
+    UnSupportedLiteral {
path: compiler/rustc_trait_selection/src/errors.rs
user: login: nnethercote
body: Also, the variant name doesn't really match the message name: "unsupported" vs "unimplemented". It would be better if they were close to each other. Likewise for a couple of the other variants.
created_at: 2025-05-01T03:26:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,52 +1,250 @@
-use rustc_ast::MetaItemInner;
-use rustc_attr_parsing as attr;
-use rustc_middle::ty::{self, TyCtxt};
+use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit};
 use rustc_parse_format::{ParseMode, Parser, Piece, Position};
-use rustc_span::{DesugaringKind, Span, Symbol, kw, sym};
+use rustc_span::{DesugaringKind, Ident, Span, Symbol, kw, sym};
 
-/// A predicate in an attribute using on, all, any,
-/// similar to a cfg predicate.
+use crate::errors::InvalidOnClause;
+
+/// Represents the `on` filter in `#[rustc_on_unimplemented]`
 #[derive(Debug)]
 pub struct Condition {
-    pub inner: MetaItemInner,
+    span: Span,
path: compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
user: login: nnethercote
body: Could this be `pub(crate)` or `pub(super)` instead of `pub`? Likewise for a few other things below.
created_at: 2025-05-01T03:38:30Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,52 +1,250 @@
-use rustc_ast::MetaItemInner;
-use rustc_attr_parsing as attr;
-use rustc_middle::ty::{self, TyCtxt};
+use rustc_ast::{MetaItemInner, MetaItemKind, MetaItemLit};
 use rustc_parse_format::{ParseMode, Parser, Piece, Position};
-use rustc_span::{DesugaringKind, Span, Symbol, kw, sym};
+use rustc_span::{DesugaringKind, Ident, Span, Symbol, kw, sym};
 
-/// A predicate in an attribute using on, all, any,
-/// similar to a cfg predicate.
+use crate::errors::InvalidOnClause;
+
+/// Represents the `on` filter in `#[rustc_on_unimplemented]`
 #[derive(Debug)]
 pub struct Condition {
-    pub inner: MetaItemInner,
+    span: Span,
path: compiler/rustc_trait_selection/src/error_reporting/traits/on_unimplemented_condition.rs
user: login: nnethercote
body: Also, `Condition` is a very generic name. Could it be made more specific, e.g. `OnUnimplementedCondition`?
created_at: 2025-05-01T03:39:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,23 +32,50 @@ pub struct UnableToConstructConstantValue<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(trait_selection_empty_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct EmptyOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(trait_selection_invalid_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct InvalidOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
+pub enum InvalidOnClause {
+    #[diag(trait_selection_rustc_on_unimplemented_empty_on_clause, code = E0232)]
+    Empty {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not, code = E0232)]
+    ExpectedOnePredInNot {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_on_clause_literal, code = E0232)]
+    UnSupportedLiteral {
path: compiler/rustc_trait_selection/src/errors.rs
user: login: mejrs
body: I'm not sure what you mean. The `unimplemented` in the flt name references the name of the attribute, not what went wrong. But I have edited it to `...unsupported_literal_in_on`
created_at: 2025-05-01T11:32:10Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,23 +32,50 @@ pub struct UnableToConstructConstantValue<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(trait_selection_empty_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct EmptyOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(trait_selection_invalid_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct InvalidOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
+pub enum InvalidOnClause {
+    #[diag(trait_selection_rustc_on_unimplemented_empty_on_clause, code = E0232)]
+    Empty {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not, code = E0232)]
+    ExpectedOnePredInNot {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_on_clause_literal, code = E0232)]
+    UnSupportedLiteral {
path: compiler/rustc_trait_selection/src/errors.rs
user: login: mejrs
body: Maybe consider having `trait_selection_rou_*` rather than `trait_selection_rustc_on_unimplemented_*`? I errored on the side of not abbreviating.
created_at: 2025-05-01T11:33:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,23 +32,50 @@ pub struct UnableToConstructConstantValue<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(trait_selection_empty_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct EmptyOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(trait_selection_invalid_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct InvalidOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
+pub enum InvalidOnClause {
+    #[diag(trait_selection_rustc_on_unimplemented_empty_on_clause, code = E0232)]
+    Empty {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not, code = E0232)]
+    ExpectedOnePredInNot {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_on_clause_literal, code = E0232)]
+    UnSupportedLiteral {
path: compiler/rustc_trait_selection/src/errors.rs
user: login: nnethercote
body: Generally the name of the error type and the name of the error message are similar, as is the text of the error. Here are some case unlike that. For example:
```
trait_selection_rustc_on_unimplemented_invalid_boolean
"invalid boolean flag"
InvalidFlag
```
One uses "boolean", one uses "flag", one uses "boolean flag". More consistency would help.
created_at: 2025-05-02T06:24:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,23 +32,50 @@ pub struct UnableToConstructConstantValue<'a> {
 }
 
 #[derive(Diagnostic)]
-#[diag(trait_selection_empty_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct EmptyOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
-}
-
-#[derive(Diagnostic)]
-#[diag(trait_selection_invalid_on_clause_in_rustc_on_unimplemented, code = E0232)]
-pub struct InvalidOnClauseInOnUnimplemented {
-    #[primary_span]
-    #[label]
-    pub span: Span,
+pub enum InvalidOnClause {
+    #[diag(trait_selection_rustc_on_unimplemented_empty_on_clause, code = E0232)]
+    Empty {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_expected_one_predicate_in_not, code = E0232)]
+    ExpectedOnePredInNot {
+        #[primary_span]
+        #[label]
+        span: Span,
+    },
+    #[diag(trait_selection_rustc_on_unimplemented_on_clause_literal, code = E0232)]
+    UnSupportedLiteral {
path: compiler/rustc_trait_selection/src/errors.rs
user: login: mejrs
body: I hadn't really thought about that, thank you. I've made some changes to the naming.
created_at: 2025-05-02T20:10:11Z
author_association: CONTRIBUTOR

PR comments:
user: login: rustbot
created_at: 2025-04-25T18:08:37Z
body: r? @estebank

rustbot has assigned @estebank.
They will have a look at your PR within the next two weeks and either review your PR or reassign to another reviewer.

Use `r?` to explicitly pick a reviewer
author_association: COLLABORATOR
user: login: rustbot
created_at: 2025-04-25T18:08:39Z
body: These commits modify the `Cargo.lock` file. Unintentional changes to `Cargo.lock` can be introduced when switching branches and rebasing PRs.

If this was unintentional then you should revert the changes before this PR is merged.
Otherwise, you can ignore this comment.

author_association: COLLABORATOR
user: login: mejrs
created_at: 2025-04-30T23:01:12Z
body: r? compiler
author_association: CONTRIBUTOR
user: login: nnethercote
created_at: 2025-05-05T04:09:38Z
body: @bors r+
author_association: CONTRIBUTOR
user: login: bors
created_at: 2025-05-05T04:09:41Z
body: :pushpin: Commit 4671081bdbc73d35a465a150937b6d0ecd26087f has been approved by `nnethercote`

It is now in the [queue](https://bors.rust-lang.org/homu/queue/rust) for this repository.

<!-- @bors r=nnethercote 4671081bdbc73d35a465a150937b6d0ecd26087f -->
<!-- homu: {"type":"Approved","sha":"4671081bdbc73d35a465a150937b6d0ecd26087f","approver":"nnethercote","queue":"https://bors.rust-lang.org/homu/queue/rust"} -->
author_association: COLLABORATOR