| //! Contains the data structures used by the diagnostic attribute family. |
| use std::fmt; |
| use std::fmt::Debug; |
| |
| pub use rustc_ast::attr::data_structures::*; |
| use rustc_macros::{Decodable, Encodable, PrintAttribute, StableHash}; |
| use rustc_span::{DesugaringKind, Span, Symbol, kw}; |
| use thin_vec::ThinVec; |
| use tracing::debug; |
| |
| use crate::attrs::PrintAttribute; |
| |
| #[derive(Clone, Default, Debug, StableHash, Encodable, Decodable, PrintAttribute)] |
| 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)>, |
| pub message: Option<(Span, FormatString)>, |
| pub label: Option<(Span, FormatString)>, |
| pub notes: ThinVec<FormatString>, |
| pub parent_label: Option<FormatString>, |
| } |
| |
| impl Directive { |
| /// Visit all the generic arguments used in the attribute, to see whether they are actually a |
| /// generic of the item. If not then `visit` must issue a diagnostic. |
| /// |
| /// 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)) { |
| for (filter, subcommand) in &self.filters { |
| filter.visit_params(visit); |
| subcommand.visit_params(visit); |
| } |
| |
| if let Some((_, message)) = &self.message { |
| message.visit_params(visit); |
| } |
| if let Some((_, label)) = &self.label { |
| label.visit_params(visit); |
| } |
| |
| for note in &self.notes { |
| note.visit_params(visit); |
| } |
| |
| if let Some(parent_label) = &self.parent_label { |
| parent_label.visit_params(visit); |
| } |
| } |
| |
| pub fn eval( |
| &self, |
| filter_options: Option<&FilterOptions>, |
| args: &FormatArgs, |
| ) -> CustomDiagnostic { |
| let this = &args.this; |
| debug!( |
| "Directive::eval({self:?}, this={this}, options={filter_options:?}, args ={args:?})" |
| ); |
| |
| 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 `filter_options`" |
| ); |
| }; |
| ret.update(self, args); |
| ret |
| } |
| } |
| |
| /// A custom diagnostic, created from a diagnostic attribute. |
| #[derive(Default, Debug)] |
| pub struct CustomDiagnostic { |
| pub message: Option<String>, |
| pub label: Option<String>, |
| pub notes: Vec<String>, |
| pub parent_label: Option<String>, |
| } |
| |
| impl CustomDiagnostic { |
| pub 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)] |
| pub struct FormatString { |
| pub input: Symbol, |
| pub span: Span, |
| pub pieces: ThinVec<Piece>, |
| } |
| |
| impl FormatString { |
| /// Formats the format string. |
| /// |
| /// This is a private method, use `Directive::eval` instead. A diagnostic attribute being used |
| /// should issue a `tracing` event, which `Directive::eval` does. |
| fn format(&self, args: &FormatArgs) -> String { |
| let mut ret = String::new(); |
| for piece in &self.pieces { |
| match piece { |
| Piece::Lit(s) | Piece::Arg(FormatArg::AsIs(s)) => ret.push_str(s.as_str()), |
| |
| // `A` if we have `trait Trait<A> {}` and `note = "i'm the actual type of {A}"` |
| Piece::Arg(FormatArg::GenericParam { generic_param, .. }) => { |
| match args.generic_args.iter().find(|(p, _)| p == generic_param) { |
| Some((_, val)) => ret.push_str(val.as_str()), |
| |
| None => { |
| // Apparently this was not actually a generic parameter, so lets write |
| // what the user wrote. |
| let _ = fmt::write(&mut ret, format_args!("{{{generic_param}}}")); |
| } |
| } |
| } |
| // `{Self}` |
| Piece::Arg(FormatArg::SelfUpper) => { |
| let slf = match args.generic_args.iter().find(|(p, _)| *p == kw::SelfUpper) { |
| Some((_, val)) => val.to_string(), |
| None => "Self".to_string(), |
| }; |
| ret.push_str(&slf); |
| } |
| Piece::Arg(FormatArg::This) => ret.push_str(&args.this), |
| |
| // only for on_type_error |
| Piece::Arg(FormatArg::Found) => ret.push_str(&args.found), |
| Piece::Arg(FormatArg::Expected) => ret.push_str(&args.expected), |
| |
| // only for on_unknown |
| Piece::Arg(FormatArg::Unresolved) => ret.push_str(&args.unresolved), |
| |
| // It's only `rustc_onunimplemented` from here |
| Piece::Arg(FormatArg::ThisPath) => ret.push_str(&args.this_path), |
| Piece::Arg(FormatArg::ThisResolved) => { |
| let _ = fmt::write(&mut ret, format_args!("{}", &args.this_resolved)); |
| } |
| Piece::Arg(FormatArg::ItemContext) => ret.push_str(args.item_context), |
| } |
| } |
| ret |
| } |
| |
| fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { |
| for piece in &self.pieces { |
| if let Piece::Arg(FormatArg::GenericParam { generic_param, span }) = piece { |
| visit(*generic_param, *span); |
| } |
| } |
| } |
| } |
| |
| /// Arguments to fill a [FormatString] with. |
| /// |
| /// For example, given a |
| /// ```rust,ignore (just an example) |
| /// |
| /// #[rustc_on_unimplemented( |
| /// on(all(from_desugaring = "QuestionMark"), |
| /// message = "the `?` operator can only be used in {ItemContext} \ |
| /// that returns `Result` or `Option` \ |
| /// (or another type that implements `{FromResidual}`)", |
| /// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`", |
| /// parent_label = "this function should return `Result` or `Option` to accept `?`" |
| /// ), |
| /// )] |
| /// pub trait FromResidual<R = <Self as Try>::Residual> { |
| /// ... |
| /// } |
| /// |
| /// async fn an_async_function() -> u32 { |
| /// let x: Option<u32> = None; |
| /// x?; //~ ERROR the `?` operator |
| /// 22 |
| /// } |
| /// ``` |
| /// it will look like this: |
| /// |
| /// ```rust,ignore (just an example) |
| /// FormatArgs { |
| /// this: "FromResidual", |
| /// this_resolved: "FromResidual<Option<Infallible>>", |
| /// item_context: "an async function", |
| /// generic_args: [("Self", "u32"), ("R", "Option<Infallible>")], |
| /// } |
| /// ``` |
| #[derive(Debug)] |
| pub struct FormatArgs { |
| /// The name of the item the attribute is on. |
| pub this: String, |
| pub this_resolved: String = String::new(), |
| pub this_path: String = String::new(), |
| pub found: String = String::new(), |
| pub expected: String = String::new(), |
| pub unresolved: String = String::new(), |
| pub item_context: &'static str = "", |
| pub generic_args: Vec<(Symbol, String)> = Vec::new(), |
| } |
| |
| #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] |
| pub enum Piece { |
| Lit(Symbol), |
| Arg(FormatArg), |
| } |
| |
| #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] |
| pub enum FormatArg { |
| // A generic parameter, like `{T}` if we're on the `From<T>` trait. |
| GenericParam { |
| generic_param: Symbol, |
| span: Span, |
| }, |
| // `{Self}` |
| SelfUpper, |
| /// `{This}` and `{This:name}`. |
| This, |
| /// The sugared form: `{This:sugared}`. |
| ThisResolved, |
| /// The full path: `{This:path}`. |
| ThisPath, |
| /// what we're in, like a function, method, closure etc. |
| ItemContext, |
| /// What the user typed, if it doesn't match anything we can use. |
| AsIs(Symbol), |
| /// {Found} in diagnostic::on_type_error |
| Found, |
| /// {Expected} in diagnostic::on_type_error |
| Expected, |
| /// {Unresolved} in diagnostic::on_unknown |
| Unresolved, |
| } |
| |
| /// Represents the `on` filter in `#[rustc_on_unimplemented]`. |
| #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] |
| 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 }) => { |
| let value = value.format(&options.generic_args); |
| options.contains(*name, value) |
| } |
| }) |
| } |
| |
| pub fn visit_params(&self, visit: &mut impl FnMut(Symbol, Span)) { |
| self.pred.visit_params(self.span, visit); |
| } |
| } |
| |
| /// 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. |
| #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] |
| pub 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(ThinVec<Predicate>), |
| /// True if any predicate is true, like `on(any($a, $b, $c))`. |
| Any(ThinVec<Predicate>), |
| } |
| |
| impl Predicate { |
| pub 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)), |
| } |
| } |
| |
| pub fn visit_params(&self, span: Span, visit: &mut impl FnMut(Symbol, Span)) { |
| match self { |
| Predicate::Flag(_) => {} |
| Predicate::Match(nv) => nv.visit_params(span, visit), |
| Predicate::Not(not) => not.visit_params(span, visit), |
| Predicate::All(preds) | Predicate::Any(preds) => { |
| preds.iter().for_each(|pred| pred.visit_params(span, visit)) |
| } |
| } |
| } |
| } |
| |
| /// Represents a `MetaWord` in an `on`-filter. |
| #[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)] |
| pub 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, |
| } |
| |
| /// 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, |
| /// 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, |
| } |
| |
| 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); |
| } |
| self.value.visit_params(span, visit); |
| } |
| } |
| |
| /// The valid names of the `on` filter. |
| #[derive(Clone, Copy, Debug, StableHash, Encodable, Decodable, PrintAttribute)] |
| pub enum Name { |
| Cause, |
| FromDesugaring, |
| SelfUpper, |
| GenericArg(Symbol), |
| } |
| |
| #[derive(Debug, Clone)] |
| pub 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(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] |
| pub struct FilterFormatString { |
| pub pieces: ThinVec<LitOrArg>, |
| } |
| |
| impl FilterFormatString { |
| 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.as_str()), |
| 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}}}")); |
| } |
| }, |
| } |
| } |
| |
| ret |
| } |
| pub fn visit_params(&self, span: Span, visit: &mut impl FnMut(Symbol, Span)) { |
| for piece in &self.pieces { |
| if let LitOrArg::Arg(arg) = piece { |
| visit(*arg, span); |
| } |
| } |
| } |
| } |
| |
| #[derive(Clone, Debug, StableHash, Encodable, Decodable, PrintAttribute)] |
| pub enum LitOrArg { |
| Lit(Symbol), |
| Arg(Symbol), |
| } |
| |
| /// Used with `Filter::matches_predicate` to evaluate the [`Filter`]. |
| /// |
| /// For example, given a |
| /// ```rust,ignore (just an example) |
| /// #[rustc_on_unimplemented( |
| /// on(all(from_desugaring = "QuestionMark"), |
| /// message = "the `?` operator can only be used in {ItemContext} \ |
| /// that returns `Result` or `Option` \ |
| /// (or another type that implements `{FromResidual}`)", |
| /// label = "cannot use the `?` operator in {ItemContext} that returns `{Self}`", |
| /// parent_label = "this function should return `Result` or `Option` to accept `?`" |
| /// ), |
| /// )] |
| /// pub trait FromResidual<R = <Self as Try>::Residual> { |
| /// ... |
| /// } |
| /// |
| /// async fn an_async_function() -> u32 { |
| /// let x: Option<u32> = None; |
| /// x?; //~ ERROR the `?` operator |
| /// 22 |
| /// } |
| /// ``` |
| /// it will look like this: |
| /// |
| /// ```rust,ignore (just an example) |
| /// FilterOptions { |
| /// self_types: ["u32", "{integral}"], |
| /// 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>" ), |
| /// ], |
| /// } |
| /// ``` |
| #[derive(Debug)] |
| pub struct FilterOptions { |
| /// All the self types that may apply. |
| pub self_types: Vec<String>, |
| // The kind of compiler desugaring. |
| pub from_desugaring: Option<DesugaringKind>, |
| /// Match on a variant of rustc_infer's `ObligationCauseCode`. |
| pub cause: Option<String>, |
| pub 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)>, |
| } |
| |
| impl FilterOptions { |
| pub 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 { |
| 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)), |
| } |
| } |
| } |