Skip to content
Open
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
2 changes: 2 additions & 0 deletions compiler/rustc_ast/src/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ use rustc_span::{
use thin_vec::{ThinVec, thin_vec};

use crate::attr::data_structures::CfgEntry;
use crate::attr::diagnostic::Directive;
pub use crate::format::*;
use crate::token::{self, CommentKind, Delimiter};
use crate::tokenstream::{DelimSpan, LazyAttrTokenStream, TokenStream};
Expand Down Expand Up @@ -3798,6 +3799,7 @@ pub struct Trait {
pub bounds: GenericBounds,
#[visitable(extra = AssocCtxt::Trait)]
pub items: ThinVec<Box<AssocItem>>,
pub on_unimplemented: Option<Directive>,
}

#[derive(Clone, Encodable, Decodable, Debug, Walkable)]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,27 @@
//! Contains the data structures used by the diagnostic attribute family.
use std::fmt;
use std::fmt::Debug;
use std::fmt::{self, Debug};

pub use rustc_ast::attr::data_structures::*;
use rustc_macros::{Decodable, Encodable, PrintAttribute, StableHash};
use rustc_macros::{Decodable, Encodable, StableHash, Walkable};
use rustc_span::def_id::DefId;
use rustc_span::{DesugaringKind, Span, Symbol, kw};
use thin_vec::ThinVec;
use tracing::debug;

use crate::attrs::PrintAttribute;
use crate::{NodeId, Path};

#[derive(Clone, Default, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Default, Debug, StableHash, Encodable, Decodable, Walkable)]
pub struct Directive {
pub is_rustc_attr: bool,
/// 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)>,
#[visitable(ignore)]
Copy link
Copy Markdown
Contributor Author

@mejrs mejrs May 12, 2026

Choose a reason for hiding this comment

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

I'm not sure about #[visitable(ignore)] here and elsewhere. Can I just put these on everything that doesn't have something "ast-y" like NodeId in it?

View changes since the review

pub message: Option<(Span, FormatString)>,
#[visitable(ignore)]
pub label: Option<(Span, FormatString)>,
#[visitable(ignore)]
pub notes: ThinVec<FormatString>,
#[visitable(ignore)]
pub parent_label: Option<FormatString>,
}

Expand Down Expand Up @@ -50,6 +53,16 @@ impl Directive {
}
}

pub fn resolve_predicates(&mut self, resolve: &mut impl FnMut(NodeId, &Path) -> Option<DefId>) {
for (Filter { pred, .. }, nested_dir) in &mut self.filters {
debug_assert!(
nested_dir.filters.is_empty(),
"can't have filters beyond the root directive"
);
pred.resolve_predicates(resolve);
}
}

pub fn eval(
&self,
filter_options: Option<&FilterOptions>,
Expand Down Expand Up @@ -109,7 +122,7 @@ impl CustomDiagnostic {

/// 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)]
#[derive(Clone, Debug, StableHash, Encodable, Decodable)]
pub struct FormatString {
pub input: Symbol,
pub span: Span,
Expand Down Expand Up @@ -211,13 +224,13 @@ pub struct FormatArgs {
pub generic_args: Vec<(Symbol, String)> = Vec::new(),
}

#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Debug, StableHash, Encodable, Decodable)]
pub enum Piece {
Lit(Symbol),
Arg(FormatArg),
}

#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Debug, StableHash, Encodable, Decodable)]
pub enum FormatArg {
// A generic parameter, like `{T}` if we're on the `From<T>` trait.
GenericParam {
Expand All @@ -237,19 +250,31 @@ pub enum FormatArg {
}

/// Represents the `on` filter in `#[rustc_on_unimplemented]`.
#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Debug, StableHash, Encodable, Decodable, Walkable)]
pub struct Filter {
pub span: Span,
pub pred: Predicate,
}

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 }) => {
FlagOrNv::NameValue(NameValue::String { name, value }) => {
let value = value.format(&options.generic_args);
options.contains(*name, value)
}
FlagOrNv::NameValue(NameValue::Path { .. }) => {
unreachable!("should have been removed during ast lowering")
}
FlagOrNv::NameValue(NameValue::DefId { name, def_id }) => {
if let Some(def_id) = def_id {
options.contains_defid(*name, *def_id)
} else {
// we've already errored during ast lowering
false
}
}
})
}

Expand All @@ -262,10 +287,10 @@ impl Filter {
///
/// It is similar to the predicate in the `cfg` attribute,
/// and may contain nested predicates.
#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Debug, StableHash, Encodable, Decodable, Walkable)]
pub enum Predicate {
/// A condition like `on(crate_local)`.
Flag(Flag),
Flag(#[visitable(ignore)] Flag),
/// A match, like `on(Rhs = "Whatever")`.
Match(NameValue),
/// Negation, like `on(not($pred))`.
Expand Down Expand Up @@ -297,10 +322,45 @@ impl Predicate {
}
}
}

pub fn visit_predicates(&self, visit: &mut impl FnMut(NodeId, &Path)) {
match self {
Predicate::Flag(_) | Predicate::Match(NameValue::String { .. }) => {}
Predicate::Match(NameValue::Path { name: _, value, id }) => {
visit(*id, value);
}
Predicate::Match(NameValue::DefId { .. }) => {
unreachable!()
}
Predicate::Not(not) => not.visit_predicates(visit),
Predicate::All(preds) | Predicate::Any(preds) => {
preds.iter().for_each(|pred| pred.visit_predicates(visit))
}
}
}

pub fn resolve_predicates(&mut self, resolve: &mut impl FnMut(NodeId, &Path) -> Option<DefId>) {
match self {
Predicate::Flag(_) | Predicate::Match(NameValue::String { .. }) => {}
Predicate::Match(NameValue::Path { name, value, id }) => {
let def_id = resolve(*id, value);
*self = Predicate::Match(NameValue::DefId { name: *name, def_id });
}
Predicate::Match(NameValue::DefId { .. }) => {
if cfg!(debug_assertions) {
unreachable!("predicate was resolved twice")
}
}
Predicate::Not(not) => not.resolve_predicates(resolve),
Predicate::All(preds) | Predicate::Any(preds) => {
preds.iter_mut().for_each(|pred| pred.resolve_predicates(resolve))
}
}
}
}

/// Represents a `MetaWord` in an `on`-filter.
#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable)]
pub enum Flag {
/// Whether the code causing the trait bound to not be fulfilled
/// is part of the user's crate.
Expand All @@ -315,26 +375,44 @@ pub enum Flag {
/// A `MetaNameValueStr` in an `on`-filter.
///
/// For example, `#[rustc_on_unimplemented(on(name = "value", message = "hello"))]`.
#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
pub struct NameValue {
pub name: Name,
#[derive(Clone, Debug, StableHash, Encodable, Decodable, Walkable)]
pub enum NameValue {
/// 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.
pub value: FilterFormatString,
String {
#[visitable(ignore)]
name: Name,
#[visitable(ignore)]
value: FilterFormatString,
},
Path {
#[visitable(ignore)]
name: Name,
id: NodeId,
value: Path,
},
DefId {
#[visitable(ignore)]
name: Name,
#[visitable(ignore)]
def_id: Option<DefId>,
},
Comment on lines +389 to +400
Copy link
Copy Markdown
Contributor Author

@mejrs mejrs May 12, 2026

Choose a reason for hiding this comment

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

These are switched during ast lowering, so we never have a Path variant inside a hir::Attribute. An alternative is to make NameValue generic over these two but that introduces a lot of generics everywhere. I tried that first and it was quite messy.

View changes since the review

}

impl NameValue {
pub fn visit_params(&self, span: Span, visit: &mut impl FnMut(Symbol, Span)) {
if let Name::GenericArg(arg) = self.name {
visit(arg, span);
if let NameValue::String { name, value } = self {
if let Name::GenericArg(arg) = name {
visit(*arg, span);
}
value.visit_params(span, visit);
}
self.value.visit_params(span, visit);
}
}

/// The valid names of the `on` filter.
#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable)]
pub enum Name {
Cause,
FromDesugaring,
Expand All @@ -354,20 +432,20 @@ pub enum FlagOrNv<'p> {
/// 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(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Debug, StableHash, Encodable, Decodable)]
pub struct FilterFormatString {
pub pieces: ThinVec<LitOrArg>,
}

impl FilterFormatString {
fn format(&self, generic_args: &[(Symbol, String)]) -> String {
fn format(&self, generic_args: &[(Symbol, String, Option<DefId>)]) -> String {
let mut ret = String::new();

for piece in &self.pieces {
match piece {
LitOrArg::Lit(s) => ret.push_str(s.as_str()),
LitOrArg::Arg(s) => match generic_args.iter().find(|(k, _)| k == s) {
Some((_, val)) => ret.push_str(val),
LitOrArg::Arg(s) => match generic_args.iter().find(|(k, _, _)| k == s) {
Some((_, val, _)) => ret.push_str(val),
None => {
let _ = std::fmt::write(&mut ret, format_args!("{{{s}}}"));
}
Expand All @@ -386,7 +464,7 @@ impl FilterFormatString {
}
}

#[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)]
#[derive(Clone, Debug, StableHash, Encodable, Decodable)]
pub enum LitOrArg {
Lit(Symbol),
Arg(Symbol),
Expand Down Expand Up @@ -419,21 +497,21 @@ pub enum LitOrArg {
///
/// ```rust,ignore (just an example)
/// FilterOptions {
/// self_types: ["u32", "{integral}"],
/// self_types: [("u32", None), ("{integral}", None), ("Type", Some(<defid>))],
/// from_desugaring: Some("QuestionMark"),
/// cause: None,
/// crate_local: false,
/// direct: true,
/// generic_args: [("Self","u32"),
/// ("R", "core::option::Option<core::convert::Infallible>"),
/// ("R", "core::option::Option<T>" ),
/// generic_args: [("Self","u32", <defid>),
/// ("R", "core::option::Option<core::convert::Infallible>", Some(<defid>)),
/// ("R", "core::option::Option<T>", Some(<defid>)),
/// ],
/// }
/// ```
#[derive(Debug)]
pub struct FilterOptions {
/// All the self types that may apply.
pub self_types: Vec<String>,
pub self_types: Vec<(String, Option<DefId>)>,
// The kind of compiler desugaring.
pub from_desugaring: Option<DesugaringKind>,
/// Match on a variant of rustc_infer's `ObligationCauseCode`.
Expand All @@ -442,23 +520,45 @@ pub struct FilterOptions {
/// 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 generic_args: Vec<(Symbol, String, Option<DefId>)>,
}

impl FilterOptions {
pub fn has_flag(&self, name: Flag) -> bool {
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(),
}
}
pub fn contains(&self, name: Name, value: String) -> bool {
fn contains(&self, name: Name, value: String) -> bool {
match name {
Name::SelfUpper => self.self_types.contains(&value),
Name::SelfUpper => {
self.self_types.iter().any(|(type_name, _this_def_id)| *type_name == 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)),
Name::GenericArg(generic) => self
.generic_args
.iter()
.any(|(this_name, typename, _)| *this_name == generic && *typename == value),
}
}

fn contains_defid(&self, name: Name, def_id: DefId) -> bool {
match name {
Name::SelfUpper => {
self.self_types.iter().any(|(_type_name, this_def_id)| *this_def_id == Some(def_id))
}

Name::GenericArg(generic) => {
self.generic_args.iter().any(|(this_name, _type_name, this_def_id)| {
*this_name == generic && *this_def_id == Some(def_id)
})
}
Name::FromDesugaring | Name::Cause => {
unreachable!("these can only refer to strings and are never resolved")
}
}
}
}
1 change: 1 addition & 0 deletions compiler/rustc_ast/src/attr/mod.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Functions dealing with attributes and meta items.

pub mod data_structures;
pub mod diagnostic;
pub mod version;

use std::fmt::Debug;
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// tidy-alphabetical-start
#![doc(test(attr(deny(warnings), allow(internal_features))))]
#![feature(associated_type_defaults)]
#![feature(default_field_values)]
#![feature(deref_patterns)]
#![feature(iter_order_by)]
#![feature(macro_metavar_expr)]
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_ast/src/mut_visit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ use smallvec::{SmallVec, smallvec};
use thin_vec::ThinVec;

use crate::ast::*;
use crate::attr::diagnostic::{Directive, Filter, NameValue, Predicate};
use crate::tokenstream::*;
use crate::visit::{AssocCtxt, BoundKind, FnCtxt, LifetimeCtxt, VisitorResult, try_visit};

Expand Down
Loading
Loading