diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 52b54655d1ca1..8a5df7a78711c 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -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}; @@ -3798,6 +3799,7 @@ pub struct Trait { pub bounds: GenericBounds, #[visitable(extra = AssocCtxt::Trait)] pub items: ThinVec>, + pub on_unimplemented: Option, } #[derive(Clone, Encodable, Decodable, Debug, Walkable)] diff --git a/compiler/rustc_hir/src/attrs/diagnostic.rs b/compiler/rustc_ast/src/attr/diagnostic.rs similarity index 71% rename from compiler/rustc_hir/src/attrs/diagnostic.rs rename to compiler/rustc_ast/src/attr/diagnostic.rs index 7cbb0ea45b969..d14361590e668 100644 --- a/compiler/rustc_hir/src/attrs/diagnostic.rs +++ b/compiler/rustc_ast/src/attr/diagnostic.rs @@ -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)] pub message: Option<(Span, FormatString)>, + #[visitable(ignore)] pub label: Option<(Span, FormatString)>, + #[visitable(ignore)] pub notes: ThinVec, + #[visitable(ignore)] pub parent_label: Option, } @@ -50,6 +53,16 @@ impl Directive { } } + pub fn resolve_predicates(&mut self, resolve: &mut impl FnMut(NodeId, &Path) -> Option) { + 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>, @@ -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, @@ -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` trait. GenericParam { @@ -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 + } + } }) } @@ -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))`. @@ -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) { + 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. @@ -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, + }, } 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, @@ -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, } impl FilterFormatString { - fn format(&self, generic_args: &[(Symbol, String)]) -> String { + fn format(&self, generic_args: &[(Symbol, String, Option)]) -> 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}}}")); } @@ -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), @@ -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())], /// from_desugaring: Some("QuestionMark"), /// cause: None, /// crate_local: false, /// direct: true, -/// generic_args: [("Self","u32"), -/// ("R", "core::option::Option"), -/// ("R", "core::option::Option" ), +/// generic_args: [("Self","u32", ), +/// ("R", "core::option::Option", Some()), +/// ("R", "core::option::Option", Some()), /// ], /// } /// ``` #[derive(Debug)] pub struct FilterOptions { /// All the self types that may apply. - pub self_types: Vec, + pub self_types: Vec<(String, Option)>, // The kind of compiler desugaring. pub from_desugaring: Option, /// Match on a variant of rustc_infer's `ObligationCauseCode`. @@ -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)>, } 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") + } } } } diff --git a/compiler/rustc_ast/src/attr/mod.rs b/compiler/rustc_ast/src/attr/mod.rs index 88556aa58c773..14aecf90bd8f2 100644 --- a/compiler/rustc_ast/src/attr/mod.rs +++ b/compiler/rustc_ast/src/attr/mod.rs @@ -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; diff --git a/compiler/rustc_ast/src/lib.rs b/compiler/rustc_ast/src/lib.rs index 3b01eb6eefa7d..4ecbafd6cb114 100644 --- a/compiler/rustc_ast/src/lib.rs +++ b/compiler/rustc_ast/src/lib.rs @@ -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)] diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index 881b6ff107b56..5ea8d4be1b1a4 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -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}; diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index ed8c3787bfec4..6da7d06b63d0e 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -19,6 +19,7 @@ use rustc_span::{Ident, Span, Spanned, Symbol}; use thin_vec::ThinVec; use crate::ast::*; +use crate::attr::diagnostic::{Directive, Filter, NameValue, Predicate}; use crate::tokenstream::DelimSpan; #[derive(Copy, Clone, Debug, PartialEq)] @@ -394,6 +395,8 @@ macro_rules! common_visitor_and_walkers { ThinVec>, ThinVec, ThinVec, + ThinVec, + ThinVec<(Filter, Directive)>, ); // This macro generates `impl Visitable` and `impl MutVisitable` that forward to `Walkable` @@ -494,6 +497,10 @@ macro_rules! common_visitor_and_walkers { YieldKind, EiiDecl, EiiImpl, + Directive, + Filter, + Predicate, + NameValue, ); /// Each method of this trait is a hook to be potentially diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index 249f8e579eee9..17288df6dcb4b 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1,13 +1,17 @@ +use std::convert::identity; + use rustc_abi::ExternAbi; use rustc_ast::visit::AssocCtxt; use rustc_ast::*; use rustc_data_structures::fx::FxIndexMap; use rustc_errors::{E0570, ErrorGuaranteed, struct_span_code_err}; +use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::{AttributeKind, EiiImplResolution}; use rustc_hir::def::{DefKind, PerNS, Res}; use rustc_hir::def_id::{CRATE_DEF_ID, LocalDefId}; use rustc_hir::{ - self as hir, HirId, ImplItemImplKind, LifetimeSource, PredicateOrigin, Target, find_attr, + self as hir, AttrPath, HirId, ImplItemImplKind, LifetimeSource, PredicateOrigin, Target, + find_attr, }; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::span_bug; @@ -205,6 +209,38 @@ impl<'hir> LoweringContext<'_, 'hir> { } } + fn lower_on_unimplemented(&mut self, directive: &Directive) -> Directive { + let mut directive = directive.clone(); + directive.resolve_predicates(&mut |id, path| { + let path = AttrPath::from_ast(path, identity); + let res = self.get_partial_res(id); + let Some(res) = res else { + self.dcx().span_err(path.span, format!("cannot find type `{path}`")); + return None; + }; + let res = if let Some(res) = res.full_res() + && !matches!(res, Res::Err) + { + res + } else { + self.dcx().span_err(path.span, format!("cannot find type `{path}`")); + return None; + }; + let Res::Def(DefKind::Struct | DefKind::Union | DefKind::Enum, def_id) = res else { + let article = res.article(); + let descr = res.descr(); + self.dcx().span_err( + path.span, + format!("`{path}` refers to {article} {descr}, not a struct, enum or union"), + ); + return None; + }; + + Some(def_id) + }); + directive + } + fn generate_extra_attrs_for_item_kind( &mut self, id: NodeId, @@ -225,7 +261,12 @@ impl<'hir> LoweringContext<'_, 'hir> { .lower_eii_decl(id, *name, target) .map(|decl| vec![hir::Attribute::Parsed(AttributeKind::EiiDeclaration(decl))]) .unwrap_or_default(), - + ItemKind::Trait(Trait { on_unimplemented: Some(ou), .. }) => { + let new_ou = self.lower_on_unimplemented(ou); + vec![hir::Attribute::Parsed(AttributeKind::OnUnimplemented { + directive: Some(Box::new(new_ou)), + })] + } ItemKind::ExternCrate(..) | ItemKind::Use(..) | ItemKind::Const(..) @@ -546,6 +587,7 @@ impl<'hir> LoweringContext<'_, 'hir> { generics, bounds, items, + on_unimplemented: _, }) => { let constness = self.lower_constness(*constness); let impl_restriction = self.lower_impl_restriction(impl_restriction); diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index b3a8f5d8cac30..1ce4fc8433ada 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -379,6 +379,7 @@ impl<'a> State<'a> { generics, bounds, items, + on_unimplemented, }) => { let (cb, ib) = self.head(""); self.print_visibility(&item.vis); @@ -397,6 +398,10 @@ impl<'a> State<'a> { self.word(" "); self.bopen(ib); self.print_inner_attributes(&item.attrs); + if on_unimplemented.is_some() { + // FIXME make this nicer + self.word("#[diagnostic::rustc_on_unimplemented(...)]") + } for trait_item in items { self.print_assoc_item(trait_item); } diff --git a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs index 9b4ffc7ea27f6..2e695f8151d6a 100644 --- a/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/diagnostic/mod.rs @@ -35,6 +35,11 @@ pub(crate) mod on_unmatch_args; pub(crate) enum Mode { /// `#[rustc_on_unimplemented]` RustcOnUnimplemented, + /// `#[diagnostic::rustc_on_unimplemented]` + /// + /// A version of `rustc_on_unimplemented` that compares types by defId + /// rather than stringly at error reporting time. + ResolvingOnUnimplemented, /// `#[diagnostic::on_unimplemented]` DiagnosticOnUnimplemented, /// `#[diagnostic::on_const]` @@ -51,6 +56,7 @@ impl Mode { fn as_str(&self) -> &'static str { match self { Self::RustcOnUnimplemented => "rustc_on_unimplemented", + Self::ResolvingOnUnimplemented => "diagnostic::rustc_on_unimplemented", Self::DiagnosticOnUnimplemented => "diagnostic::on_unimplemented", Self::DiagnosticOnConst => "diagnostic::on_const", Self::DiagnosticOnMove => "diagnostic::on_move", @@ -63,7 +69,7 @@ impl Mode { const DEFAULT: &str = "at least one of the `message`, `note` and `label` options are expected"; match self { - Self::RustcOnUnimplemented => { + Self::RustcOnUnimplemented | Self::ResolvingOnUnimplemented => { "see " } Self::DiagnosticOnUnimplemented => DEFAULT, @@ -77,7 +83,7 @@ impl Mode { fn allowed_options(&self) -> &'static str { const DEFAULT: &str = "only `message`, `note` and `label` are allowed as options"; match self { - Self::RustcOnUnimplemented => { + Self::RustcOnUnimplemented | Self::ResolvingOnUnimplemented => { "see " } Self::DiagnosticOnUnimplemented => DEFAULT, @@ -90,7 +96,7 @@ impl Mode { fn allowed_format_arguments(&self) -> &'static str { match self { - Self::RustcOnUnimplemented => { + Self::RustcOnUnimplemented | Self::ResolvingOnUnimplemented => { "see for allowed format arguments" } Self::DiagnosticOnUnimplemented => { @@ -112,6 +118,15 @@ impl Mode { } } +pub fn parse_rustc_on_unimplemented( + cx: &mut AcceptContext<'_, '_>, + args: &ArgParser, +) -> Option { + let items = parse_list(cx, args, Mode::ResolvingOnUnimplemented)?; + + parse_directive_items(cx, Mode::ResolvingOnUnimplemented, items.mixed(), true) +} + fn merge_directives( cx: &mut AcceptContext<'_, '_>, first: &mut Option<(Span, Directive)>, @@ -317,7 +332,7 @@ fn parse_directive_items<'p>( let value = or_malformed!(value?); notes.push(parse_format(value)) } - (Mode::RustcOnUnimplemented, sym::parent_label) => { + (Mode::RustcOnUnimplemented | Mode::ResolvingOnUnimplemented, sym::parent_label) => { let value = or_malformed!(value?); if parent_label.is_none() { parent_label = Some(parse_format(value)); @@ -325,7 +340,7 @@ fn parse_directive_items<'p>( duplicate!(name, span) } } - (Mode::RustcOnUnimplemented, sym::on) => { + (Mode::RustcOnUnimplemented | Mode::ResolvingOnUnimplemented, sym::on) => { if is_root { let items = or_malformed!(item.args().as_list()?); let mut iter = items.mixed(); @@ -337,7 +352,8 @@ fn parse_directive_items<'p>( } }; - let filter = parse_filter(filter); + let filter = + parse_filter(filter, matches!(mode, Mode::ResolvingOnUnimplemented)); if items.len() < 2 { // Something like `#[rustc_on_unimplemented(on(.., /* nothing */))]` @@ -418,12 +434,16 @@ fn parse_arg( match arg.position { // Something like "hello {name}" Position::ArgumentNamed(name) => match (mode, Symbol::intern(name)) { - (Mode::RustcOnUnimplemented, sym::ItemContext) => FormatArg::ItemContext, + (Mode::RustcOnUnimplemented | Mode::ResolvingOnUnimplemented, sym::ItemContext) => { + FormatArg::ItemContext + } // Like `{This}`, but sugared. // FIXME(mejrs) maybe rename/rework this or something // if we want to apply this to other attrs? - (Mode::RustcOnUnimplemented, sym::Trait) => FormatArg::Trait, + (Mode::RustcOnUnimplemented | Mode::ResolvingOnUnimplemented, sym::Trait) => { + FormatArg::Trait + } // Some diagnostic attributes can use `{This}` to refer to the annotated item. // For those that don't, we continue and maybe use it as a generic parameter. @@ -432,6 +452,7 @@ fn parse_arg( // that requires lang approval which is best kept for a standalone PR. ( Mode::RustcOnUnimplemented + | Mode::ResolvingOnUnimplemented | Mode::DiagnosticOnUnknown | Mode::DiagnosticOnMove | Mode::DiagnosticOnUnmatchArgs, @@ -445,6 +466,7 @@ fn parse_arg( // - For everything else it doesn't make sense. ( Mode::RustcOnUnimplemented + | Mode::ResolvingOnUnimplemented | Mode::DiagnosticOnUnimplemented | Mode::DiagnosticOnMove | Mode::DiagnosticOnConst, @@ -458,6 +480,7 @@ fn parse_arg( // We lint against that in `check_attr.rs` though. ( Mode::RustcOnUnimplemented + | Mode::ResolvingOnUnimplemented | Mode::DiagnosticOnUnimplemented | Mode::DiagnosticOnMove | Mode::DiagnosticOnConst, @@ -509,13 +532,19 @@ fn slice_span(input: Span, Range { start, end }: Range, is_source_literal if is_source_literal { input.from_inner(InnerSpan { start, end }) } else { input } } -pub(crate) fn parse_filter(input: &MetaItemOrLitParser) -> Result { +pub(crate) fn parse_filter( + input: &MetaItemOrLitParser, + create_node_ids: bool, +) -> Result { let span = input.span(); - let pred = parse_predicate(input)?; + let pred = parse_predicate(input, create_node_ids)?; Ok(Filter { span, pred }) } -fn parse_predicate(input: &MetaItemOrLitParser) -> Result { +fn parse_predicate( + input: &MetaItemOrLitParser, + create_node_ids: bool, +) -> Result { let Some(meta_item) = input.meta_item() else { return Err(InvalidOnClause::UnsupportedLiteral { span: input.span() }); }; @@ -529,11 +558,11 @@ fn parse_predicate(input: &MetaItemOrLitParser) -> Result match predicate.name { - sym::any => Ok(Predicate::Any(parse_predicate_sequence(mis)?)), - sym::all => Ok(Predicate::All(parse_predicate_sequence(mis)?)), + sym::any => Ok(Predicate::Any(parse_predicate_sequence(mis, create_node_ids)?)), + sym::all => Ok(Predicate::All(parse_predicate_sequence(mis, create_node_ids)?)), sym::not => { if let Some(single) = mis.as_single() { - Ok(Predicate::Not(Box::new(parse_predicate(single)?))) + Ok(Predicate::Not(Box::new(parse_predicate(single, create_node_ids)?))) } else { Err(InvalidOnClause::ExpectedOnePredInNot { span: mis.span }) } @@ -547,8 +576,26 @@ fn parse_predicate(input: &MetaItemOrLitParser) -> Result" and the like are not - that'd be nice to have. + if rustc_lexer::is_ident(value.name.as_str()) { + NameValue::Path { + name, + value: rustc_ast::Path::from_ident(value), + id: rustc_ast::DUMMY_NODE_ID, + } + } else { + return Err(InvalidOnClause::ExpectedIdentifier { + span: value.span, + path: AttrPath { segments: [value.name].into(), span: value.span }, + }); + } + } else { + let value = parse_filter_format(value.name); + NameValue::String { name, value } + }; Ok(Predicate::Match(kv)) } ArgParser::NoArgs => { @@ -560,8 +607,9 @@ fn parse_predicate(input: &MetaItemOrLitParser) -> Result Result, InvalidOnClause> { - sequence.mixed().map(parse_predicate).collect() + sequence.mixed().map(|p| parse_predicate(p, create_node_ids)).collect() } fn parse_flag(Ident { name, span }: Ident) -> Result { diff --git a/compiler/rustc_attr_parsing/src/lib.rs b/compiler/rustc_attr_parsing/src/lib.rs index 24e10e19ca976..911d506e0c5b8 100644 --- a/compiler/rustc_attr_parsing/src/lib.rs +++ b/compiler/rustc_attr_parsing/src/lib.rs @@ -118,6 +118,7 @@ pub use attributes::cfg::{ CFG_TEMPLATE, EvalConfigResult, eval_config_entry, parse_cfg, parse_cfg_attr, parse_cfg_entry, }; pub use attributes::cfg_select::*; +pub use attributes::diagnostic::parse_rustc_on_unimplemented; pub use attributes::util::{is_builtin_attr, parse_version}; pub use context::{OmitDoc, ShouldEmit}; pub use interface::{AttributeParser, EmitAttribute}; diff --git a/compiler/rustc_builtin_macros/src/diagnostic.rs b/compiler/rustc_builtin_macros/src/diagnostic.rs new file mode 100644 index 0000000000000..119b921e11cb0 --- /dev/null +++ b/compiler/rustc_builtin_macros/src/diagnostic.rs @@ -0,0 +1,69 @@ +#![allow(warnings)] + +use std::convert::identity; + +use rustc_ast::{AttrArgs, AttrStyle, Item, ItemKind, Trait, ast}; +use rustc_attr_parsing::parser::{AllowExprMetavar, ArgParser}; +use rustc_attr_parsing::{AttributeParser, AttributeSafety, ParsedDescription, ShouldEmit}; +use rustc_expand::base::{Annotatable, ExtCtxt}; +use rustc_feature::template; +use rustc_hir::{AttrPath, Target}; +use rustc_parse::parser::Recovery; +use rustc_span::{Span, sym}; + +pub(crate) mod rustc_on_unimplemented { + use super::*; + pub(crate) fn expand(ecx: &mut ExtCtxt<'_>, attr: &ast::Attribute, item: &mut Annotatable) { + let attr = attr.get_normal_item(); + let span = attr.span(); + let Some(args) = ArgParser::from_attr_args( + &attr.args.unparsed_ref().unwrap(), + &[sym::diagnostic, sym::rustc_on_unimplemented], + &ecx.sess.psess, + ShouldEmit::ErrorsAndLints { recovery: Recovery::Forbidden }, + AllowExprMetavar::No, + ) else { + // Lints/errors are/will be emitted by ArgParser. + return; + }; + + match item { + Annotatable::Item(Item { + span: target_span, + kind: ItemKind::Trait(Trait { on_unimplemented, .. }), + .. + }) => { + if on_unimplemented.is_some() { + // FIXME(mejrs) coalescing multiple rustc_on_unimplemented attrs isn't + // (and was never) supported - might be nice to have at some point + ecx.dcx().span_err( + span, + "using multiple `#[diagnostic::rustc_on_unimplemented]` is not supported", + ); + } + *on_unimplemented = AttributeParser::parse_single_args( + ecx.sess, + span, + span, + AttrStyle::Inner, + AttrPath::from_ast(&attr.path, identity), + None, + AttributeSafety::Normal, + ParsedDescription::Macro, + *target_span, + ecx.current_expansion.lint_node_id, + Target::Trait, + Some(ecx.ecfg.features), + ShouldEmit::ErrorsAndLints { recovery: Recovery::Forbidden }, + &args, + rustc_attr_parsing::parse_rustc_on_unimplemented, + &template!(List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#]), + ); + } + _ => { + ecx.dcx() + .span_err(item.span(), "`#[diagnostic::rustc_on_unimplemented]` is only supported on trait definitions"); + } + } + } +} diff --git a/compiler/rustc_builtin_macros/src/lib.rs b/compiler/rustc_builtin_macros/src/lib.rs index d5d577d39d28b..d1093a6f2d6f8 100644 --- a/compiler/rustc_builtin_macros/src/lib.rs +++ b/compiler/rustc_builtin_macros/src/lib.rs @@ -33,6 +33,7 @@ mod concat_bytes; mod define_opaque; mod derive; mod deriving; +mod diagnostic; mod edition_panic; mod eii; mod env; @@ -143,4 +144,9 @@ pub fn register_builtin_macros(resolver: &mut dyn ResolverExpand) { register(sym::contracts_requires, requires); let ensures = SyntaxExtensionKind::Attr(Arc::new(contracts::ExpandEnsures)); register(sym::contracts_ensures, ensures); + + resolver.insert_inert_attr( + &[sym::diagnostic, sym::rustc_on_unimplemented], + SyntaxExtensionKind::InertAttr(Arc::new(diagnostic::rustc_on_unimplemented::expand)), + ); } diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 7f1b58dd1de08..a0ba8258310bd 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -409,6 +409,19 @@ pub trait GlobDelegationExpander { fn expand(&self, ecx: &mut ExtCtxt<'_>) -> ExpandResult)>, ()>; } +pub trait AstAnnotate { + fn annotate(&self, ecx: &mut ExtCtxt<'_>, attr: &ast::Attribute, item: &mut Annotatable); +} + +impl AstAnnotate for F +where + F: Fn(&mut ExtCtxt<'_>, &ast::Attribute, &mut Annotatable), +{ + fn annotate(&self, ecx: &mut ExtCtxt<'_>, attr: &ast::Attribute, item: &mut Annotatable) { + self(ecx, attr, item) + } +} + fn make_stmts_default(expr: Option>) -> Option> { expr.map(|e| { smallvec![ast::Stmt { id: ast::DUMMY_NODE_ID, span: e.span, kind: ast::StmtKind::Expr(e) }] @@ -787,6 +800,16 @@ pub enum SyntaxExtensionKind { /// /// This is for delegated function implementations, and has nothing to do with glob imports. GlobDelegation(Arc), + + /// An AST-based "inert" attribute. + /// + /// These represent otherwise inert attributes. Instead of creating or modifying items like + /// a macro would, they instead attach state to these items which is carried through + /// ast/hir lowering and then emitted like an actual inert attribute. + InertAttr( + /// An expander with signature (&mut AST). + Arc, + ), } impl SyntaxExtensionKind { @@ -857,7 +880,8 @@ impl SyntaxExtension { | SyntaxExtensionKind::GlobDelegation(..) => MacroKinds::BANG, SyntaxExtensionKind::Attr(..) | SyntaxExtensionKind::LegacyAttr(..) - | SyntaxExtensionKind::NonMacroAttr => MacroKinds::ATTR, + | SyntaxExtensionKind::NonMacroAttr + | SyntaxExtensionKind::InertAttr(..) => MacroKinds::ATTR, SyntaxExtensionKind::Derive(..) | SyntaxExtensionKind::LegacyDerive(..) => { MacroKinds::DERIVE } @@ -1180,6 +1204,8 @@ pub trait ResolverExpand { /// Mark the scope as having a compile error so that error for lookup in this scope /// should be suppressed fn mark_scope_with_compile_error(&mut self, parent_node: NodeId); + + fn insert_inert_attr(&mut self, path: &'static [Symbol], kind: SyntaxExtensionKind); } pub trait LintStoreExpand { diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 741c34e0304af..1188422751dcf 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -897,6 +897,10 @@ impl<'a, 'b> MacroExpander<'a, 'b> { self.cx.expanded_inert_attrs.mark(&attr); item.visit_attrs(|attrs| attrs.insert(pos, attr)); fragment_kind.expect_from_annotatables(iter::once(item)) + } else if let SyntaxExtensionKind::InertAttr(expander) = ext { + let mut item = item; + expander.annotate(self.cx, &attr, &mut item); + fragment_kind.expect_from_annotatables([item]) } else { unreachable!(); } diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index 5068501a0e2d1..a845bdcd496d6 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -7,6 +7,7 @@ #![feature(proc_macro_internals)] #![feature(try_blocks)] #![feature(yeet_expr)] +#![recursion_limit = "256"] // For rustdoc // tidy-alphabetical-end mod build; diff --git a/compiler/rustc_hir/src/attrs/mod.rs b/compiler/rustc_hir/src/attrs/mod.rs index b3c1deaa48fa8..5c53efd3e1613 100644 --- a/compiler/rustc_hir/src/attrs/mod.rs +++ b/compiler/rustc_hir/src/attrs/mod.rs @@ -7,9 +7,9 @@ pub use data_structures::*; pub use encode_cross_crate::EncodeCrossCrate; pub use pretty_printing::PrintAttribute; +pub use rustc_ast::attr::diagnostic; mod data_structures; -pub mod diagnostic; mod encode_cross_crate; mod pretty_printing; diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index 9d14f9de3078d..671fccb7ecf4a 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -17,6 +17,7 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use rustc_target::spec::SanitizerSet; use thin_vec::ThinVec; +use crate::attrs::diagnostic::Directive; use crate::limit::Limit; /// This trait is used to print attributes in `rustc_hir_pretty`. @@ -209,4 +210,5 @@ print_debug!( CfgEntry, DiffActivity, DiffMode, + Directive, ); diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 68fda3b86ddcb..27725ba61c863 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -1178,6 +1178,7 @@ impl<'a> Parser<'a> { generics, bounds, items, + on_unimplemented: None, }))) } } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index e8bd3abdc621e..189f06793895a 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -12,6 +12,7 @@ use std::debug_assert_matches; use std::mem::{replace, swap, take}; use std::ops::{ControlFlow, Range}; +use rustc_ast::attr::diagnostic::Directive; use rustc_ast::visit::{ AssocCtxt, BoundKind, FnCtxt, FnKind, Visitor, try_visit, visit_opt, walk_list, }; @@ -2859,10 +2860,22 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { self.diag_metadata.current_impl_items = None; } - ItemKind::Trait(Trait { generics, bounds, items, impl_restriction, .. }) => { + ItemKind::Trait(Trait { + impl_restriction, + constness: _, + safety: _, + is_auto: _, + ident: _, + generics, + bounds, + items, + on_unimplemented, + }) => { // resolve paths for `impl` restrictions self.resolve_impl_restriction_path(impl_restriction); + self.resolve_directive_paths(on_unimplemented); + // Create a new rib for the trait-wide type parameters. self.with_generic_param_rib( &generics.params, @@ -4473,6 +4486,16 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> { } } + fn resolve_directive_paths(&mut self, directive: &'ast Option) { + if let Some(directive) = directive { + for (filter, _) in &directive.filters { + filter.pred.visit_predicates(&mut |id, path| { + self.smart_resolve_path(id, &None, path, PathSource::Type); + }) + } + } + } + // High-level and context dependent path resolution routine. // Resolves the path and records the resolution into definition map. // If resolution fails tries several techniques to find likely diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 32bc59de711ed..3021c17b3d477 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1557,6 +1557,9 @@ pub struct Resolver<'ra, 'tcx> { // that were encountered during resolution. These names are used to generate item names // for APITs, so we don't want to leak details of resolution into these names. impl_trait_names: FxHashMap = default::fx_hash_map(), + + /// Stores "inert" attributes that must annotate the item they're on. + inert_attrs: FxHashMap<&'static [Symbol], &'ra Arc> = default::fx_hash_map() } /// This provides memory for the rest of the crate. The `'ra` lifetime that is diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 206e32f61b485..cc428ea6de843 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -553,6 +553,11 @@ impl<'ra, 'tcx> ResolverExpand for Resolver<'ra, 'tcx> { fn insert_impl_trait_name(&mut self, id: NodeId, name: Symbol) { self.impl_trait_names.insert(id, name); } + + fn insert_inert_attr(&mut self, path: &'static [Symbol], kind: SyntaxExtensionKind) { + let ext = self.arenas.alloc_macro(SyntaxExtension::default(kind, self.tcx.sess.edition())); + self.inert_attrs.insert(path, ext); + } } impl<'ra, 'tcx> Resolver<'ra, 'tcx> { @@ -616,7 +621,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { self.dcx().emit_err(errors::AttributesStartingWithRustcAreReserved { span: segment.ident.span, }); - } else { + } else if !self.tcx.features().rustc_attrs() { self.dcx().emit_err(errors::AttributesContainingRustcAreReserved { span: segment.ident.span, }); @@ -708,6 +713,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { (sym::on_const, Some(sym::diagnostic_on_const)), (sym::on_unknown, Some(sym::diagnostic_on_unknown)), (sym::on_unmatch_args, Some(sym::diagnostic_on_unmatch_args)), + (sym::rustc_on_unimplemented, Some(sym::rustc_attrs)), ]; if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) @@ -820,7 +826,7 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; self.multi_segment_macro_resolutions.borrow_mut(&self).push(( - path, + path.clone(), path_span, kind, *parent_scope, @@ -866,20 +872,27 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> { }; let res = res?; - let ext = match deleg_impl { - Some((impl_def_id, star_span)) => match res { - Res::Def(DefKind::Trait, def_id) => { - let edition = self.tcx.sess.edition(); - Some(self.arenas.alloc_macro(SyntaxExtension::glob_delegation( - def_id, - impl_def_id, - star_span, - edition, - ))) + let ext = 'ext: { + if let Some((impl_def_id, star_span)) = deleg_impl + && let Res::Def(DefKind::Trait, def_id) = res + { + let edition = self.tcx.sess.edition(); + break 'ext Some(self.arenas.alloc_macro(SyntaxExtension::glob_delegation( + def_id, + impl_def_id, + star_span, + edition, + ))); + } + + if res == Res::NonMacroAttr(NonMacroAttrKind::Tool) { + let path: Vec = path.iter().map(|seg| seg.ident.name).collect(); + if let Some(ext) = self.inert_attrs.get(&*path) { + break 'ext Some(ext); } - _ => None, - }, - None => self.get_macro(res), + } + + self.get_macro(res) }; Ok((ext, res)) } 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 b15f528b7261e..96f709f0abd3d 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 @@ -2,8 +2,9 @@ use std::path::PathBuf; use rustc_hir as hir; use rustc_hir::attrs::diagnostic::{CustomDiagnostic, FilterOptions, FormatArgs}; -use rustc_hir::def_id::LocalDefId; +use rustc_hir::def_id::{DefId, LocalDefId}; use rustc_hir::find_attr; +use rustc_middle::query::QueryKey; use rustc_middle::ty::print::PrintTraitRefExt; use rustc_middle::ty::{self, GenericParamDef, GenericParamDefKind}; use rustc_span::Symbol; @@ -61,8 +62,8 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { let (def_id, args) = (trait_pred.def_id(), trait_pred.skip_binder().trait_ref.args); let trait_pred = trait_pred.skip_binder(); - let mut self_types = vec![]; - let mut generic_args: Vec<(Symbol, String)> = vec![]; + let mut self_types: Vec<(String, Option)> = vec![]; + let mut generic_args: Vec<(Symbol, String, Option)> = vec![]; let mut crate_local = false; // FIXME(-Zlower-impl-trait-in-trait-to-assoc-ty): HIR is not present for RPITITs, // but I guess we could synthesize one here. We don't see any errors that rely on @@ -93,37 +94,30 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { ty::print::with_no_trimmed_paths!(ty::print::with_no_visible_paths!({ let generics = self.tcx.generics_of(def_id); let self_ty = trait_pred.self_ty(); - self_types.push(self_ty.to_string()); + self_types.push((self_ty.to_string(), self_ty.key_as_def_id())); if let Some(def) = self_ty.ty_adt_def() { // We also want to be able to select self's original // signature with no type arguments resolved - self_types.push( - self.tcx.type_of(def.did()).instantiate_identity().skip_norm_wip().to_string(), - ); + let ty = self.tcx.type_of(def.did()).instantiate_identity().skip_norm_wip(); + self_types.push((ty.to_string(), ty.key_as_def_id())); } - for GenericParamDef { name, kind, index, .. } in generics.own_params.iter() { + for GenericParamDef { name, def_id, kind, index, .. } in generics.own_params.iter() { let value = match kind { GenericParamDefKind::Type { .. } | GenericParamDefKind::Const { .. } => { args[*index as usize].to_string() } GenericParamDefKind::Lifetime => continue, }; - generic_args.push((*name, value)); + generic_args.push((*name, value, Some(*def_id))); if let GenericParamDefKind::Type { .. } = kind { let param_ty = args[*index as usize].expect_ty(); if let Some(def) = param_ty.ty_adt_def() { // We also want to be able to select the parameter's // original signature with no type arguments resolved - generic_args.push(( - *name, - self.tcx - .type_of(def.did()) - .instantiate_identity() - .skip_norm_wip() - .to_string(), - )); + let ty = self.tcx.type_of(def.did()).instantiate_identity().skip_norm_wip(); + generic_args.push((*name, ty.to_string(), ty.key_as_def_id())); } } } @@ -132,16 +126,16 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { if adt.did().is_local() { crate_local = true; } - self_types.push(format!("{{{}}}", adt.descr())) + self_types.push((format!("{{{}}}", adt.descr()), Some(adt.did()))); } // Allow targeting all integers using `{integral}`, even if the exact type was resolved if self_ty.is_integral() { - self_types.push("{integral}".to_owned()); + self_types.push(("{integral}".to_owned(), None)); } if self_ty.is_array_slice() { - self_types.push("&[]".to_owned()); + self_types.push(("&[]".to_owned(), None)); } if self_ty.is_fn() { @@ -156,53 +150,59 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { hir::Safety::Unsafe => "unsafe fn", } }; - self_types.push(shortname.to_owned()); + self_types.push((shortname.to_owned(), None)); } // Slices give us `[]`, `[{ty}]` if let ty::Slice(aty) = self_ty.kind() { - self_types.push("[]".to_owned()); + self_types.push(("[]".to_owned(), None)); if let Some(def) = aty.ty_adt_def() { // We also want to be able to select the slice's type's original // signature with no type arguments resolved - self_types.push(format!( - "[{}]", - self.tcx.type_of(def.did()).instantiate_identity().skip_norm_wip() + self_types.push(( + format!( + "[{}]", + self.tcx.type_of(def.did()).instantiate_identity().skip_norm_wip() + ), + None, )); } if aty.is_integral() { - self_types.push("[{integral}]".to_string()); + self_types.push(("[{integral}]".to_string(), None)); } } // Arrays give us `[]`, `[{ty}; _]` and `[{ty}; N]` if let ty::Array(aty, len) = self_ty.kind() { - self_types.push("[]".to_string()); + self_types.push(("[]".to_string(), None)); let len = len.try_to_target_usize(self.tcx); - self_types.push(format!("[{aty}; _]")); + self_types.push((format!("[{aty}; _]"), None)); if let Some(n) = len { - self_types.push(format!("[{aty}; {n}]")); + self_types.push((format!("[{aty}; {n}]"), None)); } if let Some(def) = aty.ty_adt_def() { // We also want to be able to select the array's type's original // signature with no type arguments resolved let def_ty = self.tcx.type_of(def.did()).instantiate_identity().skip_norm_wip(); - self_types.push(format!("[{def_ty}; _]")); + self_types.push((format!("[{def_ty}; _]"), None)); if let Some(n) = len { - self_types.push(format!("[{def_ty}; {n}]")); + self_types.push((format!("[{def_ty}; {n}]"), None)); } } if aty.is_integral() { - self_types.push("[{integral}; _]".to_string()); + self_types.push(("[{integral}; _]".to_string(), None)); if let Some(n) = len { - self_types.push(format!("[{{integral}}; {n}]")); + self_types.push((format!("[{{integral}}; {n}]"), None)); } } } if let ty::Dynamic(traits, _) = self_ty.kind() { for t in traits.iter() { if let ty::ExistentialPredicate::Trait(trait_ref) = t.skip_binder() { - self_types.push(self.tcx.def_path_str(trait_ref.def_id)); + self_types.push(( + self.tcx.def_path_str(trait_ref.def_id), + Some(trait_ref.def_id), + )); } } } @@ -212,7 +212,7 @@ impl<'tcx> TypeErrCtxt<'_, 'tcx> { && let ty::Slice(sty) = ref_ty.kind() && sty.is_integral() { - self_types.push("&[{integral}]".to_owned()); + self_types.push(("&[{integral}]".to_owned(), None)); } })); diff --git a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs index 7b408471574f9..eb66481c3c15c 100644 --- a/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs +++ b/src/tools/clippy/clippy_utils/src/ast_utils/mod.rs @@ -457,6 +457,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { generics: lg, bounds: lb, items: lis, + on_unimplemented: _, }), Trait(box ast::Trait { impl_restriction: riprt, @@ -467,6 +468,7 @@ pub fn eq_item_kind(l: &ItemKind, r: &ItemKind) -> bool { generics: rg, bounds: rb, items: ris, + on_unimplemented: _, }), ) => { eq_impl_restriction(liprt, riprt) diff --git a/src/tools/rustfmt/src/items.rs b/src/tools/rustfmt/src/items.rs index 32f71703e019f..b5422058e175a 100644 --- a/src/tools/rustfmt/src/items.rs +++ b/src/tools/rustfmt/src/items.rs @@ -1163,6 +1163,7 @@ pub(crate) fn format_trait( ref generics, ref bounds, ref items, + on_unimplemented: _, } = *trait_; let mut result = String::with_capacity(128); diff --git a/tests/ui/on-unimplemented/resolving/identical_names_do_not_trigger.rs b/tests/ui/on-unimplemented/resolving/identical_names_do_not_trigger.rs new file mode 100644 index 0000000000000..b6c91124a1fee --- /dev/null +++ b/tests/ui/on-unimplemented/resolving/identical_names_do_not_trigger.rs @@ -0,0 +1,21 @@ +#![feature(rustc_attrs)] + +mod module { + pub struct Foo; + + #[diagnostic::rustc_on_unimplemented( + on(Self = "Foo", message = "the specialized message"), + message = "the regular message" + )] + pub trait FromIterator: Sized { + fn from_iter>(iter: T) -> Self; + } +} + +struct Foo; + +fn main() { + let iter = 0..42_8; + let x: Foo = module::FromIterator::from_iter(iter); + //~^ ERROR the regular message +} diff --git a/tests/ui/on-unimplemented/resolving/identical_names_do_not_trigger.stderr b/tests/ui/on-unimplemented/resolving/identical_names_do_not_trigger.stderr new file mode 100644 index 0000000000000..ef4e50c6dad4c --- /dev/null +++ b/tests/ui/on-unimplemented/resolving/identical_names_do_not_trigger.stderr @@ -0,0 +1,20 @@ +error[E0277]: the regular message + --> $DIR/identical_names_do_not_trigger.rs:19:18 + | +LL | let x: Foo = module::FromIterator::from_iter(iter); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `module::FromIterator<{integer}>` is not implemented for `Foo` + --> $DIR/identical_names_do_not_trigger.rs:15:1 + | +LL | struct Foo; + | ^^^^^^^^^^ +help: this trait has no implementations, consider adding one + --> $DIR/identical_names_do_not_trigger.rs:10:5 + | +LL | pub trait FromIterator: Sized { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/on-unimplemented/resolving/not_supported.rs b/tests/ui/on-unimplemented/resolving/not_supported.rs new file mode 100644 index 0000000000000..a5f8090e81d38 --- /dev/null +++ b/tests/ui/on-unimplemented/resolving/not_supported.rs @@ -0,0 +1,43 @@ +#![feature(rustc_attrs)] +#![crate_type = "lib"] + +#[diagnostic::rustc_on_unimplemented(message = "Foo")] +pub struct Struct; +//~^ ERROR `#[diagnostic::rustc_on_unimplemented]` is only supported on trait definitions + +#[diagnostic::rustc_on_unimplemented(on(Self = "IDontExist", message = "Foo"))] +//~^ ERROR cannot find type `IDontExist` in this scope [E0425] +//~| ERROR cannot find type `IDontExist` +pub trait Trait {} + +mod module { + pub struct Baz; +} + +#[diagnostic::rustc_on_unimplemented(on(Self = "module::Baz", message = "Foo"))] +//~^ ERROR expected an identifier inside this `on`-clause +pub trait MultiSegmentNotSupported {} + +pub struct Buz{ + pub a: A +} + +#[diagnostic::rustc_on_unimplemented(on(Self = "Buz", message = "Foo"))] +//~^ ERROR expected an identifier inside this `on`-clause +pub trait GenericNotSupported {} + +#[diagnostic::rustc_on_unimplemented(on(Self = "u8", message = "Foo"))] +//~^ ERROR `u8` refers to a builtin type, not a struct, enum or union +pub trait PrimTyNotSupported {} + + +macro_rules! produce_trait { + ($name: ident) =>{ + pub trait $name {} + } +} + +// FWIW this has never worked for any diagnostic attribute +#[diagnostic::rustc_on_unimplemented(message = "baz")] +produce_trait!(X); +//~^ERROR `#[diagnostic::rustc_on_unimplemented]` is only supported on trait definitions diff --git a/tests/ui/on-unimplemented/resolving/not_supported.stderr b/tests/ui/on-unimplemented/resolving/not_supported.stderr new file mode 100644 index 0000000000000..73b81cf969962 --- /dev/null +++ b/tests/ui/on-unimplemented/resolving/not_supported.stderr @@ -0,0 +1,46 @@ +error: `#[diagnostic::rustc_on_unimplemented]` is only supported on trait definitions + --> $DIR/not_supported.rs:5:1 + | +LL | pub struct Struct; + | ^^^^^^^^^^^^^^^^^^ + +error[E0232]: expected an identifier inside this `on`-clause + --> $DIR/not_supported.rs:17:48 + | +LL | #[diagnostic::rustc_on_unimplemented(on(Self = "module::Baz", message = "Foo"))] + | ^^^^^^^^^^^^^ expected an identifier here, not `module::Baz` + +error[E0232]: expected an identifier inside this `on`-clause + --> $DIR/not_supported.rs:25:48 + | +LL | #[diagnostic::rustc_on_unimplemented(on(Self = "Buz", message = "Foo"))] + | ^^^^^^^^^ expected an identifier here, not `Buz` + +error: `#[diagnostic::rustc_on_unimplemented]` is only supported on trait definitions + --> $DIR/not_supported.rs:42:1 + | +LL | produce_trait!(X); + | ^^^^^^^^^^^^^^^^^^ + +error[E0425]: cannot find type `IDontExist` in this scope + --> $DIR/not_supported.rs:8:48 + | +LL | #[diagnostic::rustc_on_unimplemented(on(Self = "IDontExist", message = "Foo"))] + | ^^^^^^^^^^^^ not found in this scope + +error: cannot find type `IDontExist` + --> $DIR/not_supported.rs:8:48 + | +LL | #[diagnostic::rustc_on_unimplemented(on(Self = "IDontExist", message = "Foo"))] + | ^^^^^^^^^^^^ + +error: `u8` refers to a builtin type, not a struct, enum or union + --> $DIR/not_supported.rs:29:48 + | +LL | #[diagnostic::rustc_on_unimplemented(on(Self = "u8", message = "Foo"))] + | ^^^^ + +error: aborting due to 7 previous errors + +Some errors have detailed explanations: E0232, E0425. +For more information about an error, try `rustc --explain E0232`. diff --git a/tests/ui/on-unimplemented/resolving/resolving_in_rustc_on_unimplemented.rs b/tests/ui/on-unimplemented/resolving/resolving_in_rustc_on_unimplemented.rs new file mode 100644 index 0000000000000..89f1afccbefa5 --- /dev/null +++ b/tests/ui/on-unimplemented/resolving/resolving_in_rustc_on_unimplemented.rs @@ -0,0 +1,16 @@ +#![feature(rustc_attrs)] + +pub struct Foo; + +#[diagnostic::rustc_on_unimplemented( + on(Self = "Foo", message = "the specialized message"), + message = "the message" +)] +pub trait FromIterator: Sized { + fn from_iter>(iter: T) -> Self; +} +fn main() { + let iter = 0..42_8; + let x: Foo = FromIterator::from_iter(iter); + //~^ ERROR the specialized message +} diff --git a/tests/ui/on-unimplemented/resolving/resolving_in_rustc_on_unimplemented.stderr b/tests/ui/on-unimplemented/resolving/resolving_in_rustc_on_unimplemented.stderr new file mode 100644 index 0000000000000..c33fe12915715 --- /dev/null +++ b/tests/ui/on-unimplemented/resolving/resolving_in_rustc_on_unimplemented.stderr @@ -0,0 +1,20 @@ +error[E0277]: the specialized message + --> $DIR/resolving_in_rustc_on_unimplemented.rs:14:18 + | +LL | let x: Foo = FromIterator::from_iter(iter); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ unsatisfied trait bound + | +help: the trait `FromIterator<{integer}>` is not implemented for `Foo` + --> $DIR/resolving_in_rustc_on_unimplemented.rs:3:1 + | +LL | pub struct Foo; + | ^^^^^^^^^^^^^^ +help: this trait has no implementations, consider adding one + --> $DIR/resolving_in_rustc_on_unimplemented.rs:9:1 + | +LL | pub trait FromIterator: Sized { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/unpretty/diagnostic-attr.stdout b/tests/ui/unpretty/diagnostic-attr.stdout index b5e0269410fb7..90858218c971a 100644 --- a/tests/ui/unpretty/diagnostic-attr.stdout +++ b/tests/ui/unpretty/diagnostic-attr.stdout @@ -6,15 +6,7 @@ use ::std::prelude::rust_2015::*; //@ check-pass //@ edition: 2015 -#[attr = OnUnimplemented {directive: Directive {is_rustc_attr: false, -filters: [], -message: FormatString {input: "My Message for `ImportantTrait<{A}>` implemented for `{Self}`", -pieces: [Lit("My Message for `ImportantTrait<"), -Arg(GenericParam {generic_param: "A"}), Lit(">` implemented for `"), -Arg(SelfUpper), Lit("`")]}, label: FormatString {input: "My Label", -pieces: [Lit("My Label")]}, notes: [FormatString {input: "Note 1", -pieces: [Lit("Note 1")]}, FormatString {input: "Note 2", -pieces: [Lit("Note 2")]}]}}] +#[attr = OnUnimplemented {directive: Directive { is_rustc_attr: false, filters: [], message: Some(($DIR/diagnostic-attr.rs:7:5: 7:78 (#0), FormatString { input: "My Message for `ImportantTrait<{A}>` implemented for `{Self}`", span: $DIR/diagnostic-attr.rs:7:15: 7:78 (#0), pieces: [Lit("My Message for `ImportantTrait<"), Arg(GenericParam { generic_param: "A", span: $DIR/diagnostic-attr.rs:7:48: 7:49 (#0) }), Lit(">` implemented for `"), Arg(SelfUpper), Lit("`")] })), label: Some(($DIR/diagnostic-attr.rs:8:5: 8:23 (#0), FormatString { input: "My Label", span: $DIR/diagnostic-attr.rs:8:13: 8:23 (#0), pieces: [Lit("My Label")] })), notes: [FormatString { input: "Note 1", span: $DIR/diagnostic-attr.rs:9:12: 9:20 (#0), pieces: [Lit("Note 1")] }, FormatString { input: "Note 2", span: $DIR/diagnostic-attr.rs:10:12: 10:20 (#0), pieces: [Lit("Note 2")] }], parent_label: None }}] trait ImportantTrait { } #[attr = DoNotRecommend] @@ -22,11 +14,5 @@ impl ImportantTrait for T where T: Clone { } struct X; -#[attr = OnConst {directive: Directive {is_rustc_attr: false, filters: [], -message: FormatString {input: "My Message for `ImportantTrait` implemented for `{Self}`", -pieces: [Lit("My Message for `ImportantTrait` implemented for `"), -Arg(SelfUpper), Lit("`")]}, label: FormatString {input: "My Label", -pieces: [Lit("My Label")]}, notes: [FormatString {input: "Note 1", -pieces: [Lit("Note 1")]}, FormatString {input: "Note 2", -pieces: [Lit("Note 2")]}]}}] +#[attr = OnConst {directive: Directive { is_rustc_attr: false, filters: [], message: Some(($DIR/diagnostic-attr.rs:20:5: 20:77 (#0), FormatString { input: "My Message for `ImportantTrait` implemented for `{Self}`", span: $DIR/diagnostic-attr.rs:20:15: 20:77 (#0), pieces: [Lit("My Message for `ImportantTrait` implemented for `"), Arg(SelfUpper), Lit("`")] })), label: Some(($DIR/diagnostic-attr.rs:21:5: 21:23 (#0), FormatString { input: "My Label", span: $DIR/diagnostic-attr.rs:21:13: 21:23 (#0), pieces: [Lit("My Label")] })), notes: [FormatString { input: "Note 1", span: $DIR/diagnostic-attr.rs:22:12: 22:20 (#0), pieces: [Lit("Note 1")] }, FormatString { input: "Note 2", span: $DIR/diagnostic-attr.rs:23:12: 23:20 (#0), pieces: [Lit("Note 2")] }], parent_label: None }}] impl ImportantTrait for X { } diff --git a/triagebot.toml b/triagebot.toml index ce8ef564c5f21..577d9ee9d8a4c 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -1457,7 +1457,7 @@ cc = ["@rust-lang/wg-const-eval"] [mentions."compiler/rustc_attr_parsing/src/attributes/diagnostic"] message = "Some changes occurred to diagnostic attributes." cc = ["@mejrs"] -[mentions."compiler/rustc_hir/src/attrs/diagnostic.rs"] +[mentions."compiler/rustc_ast/src/attr/diagnostic.rs"] message = "Some changes occurred to diagnostic attributes." cc = ["@mejrs"]