From 30107e89e69816fd3dcfa904fc97b5a95db9ed87 Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Thu, 9 Apr 2026 18:22:12 +0200 Subject: [PATCH 1/5] Revert #154808 because it is based on #152369 This reverts commit 0c94559d488c86ef1bf757420a053ab96937f403, reversing changes made to 33528612babe2a44618b88949bcb17ee16baf6fa. --- .../rustc_attr_parsing/src/validate_attr.rs | 61 +- compiler/rustc_expand/src/errors.rs | 18 + compiler/rustc_expand/src/expand.rs | 21 +- compiler/rustc_expand/src/module.rs | 17 +- compiler/rustc_feature/src/builtin_attrs.rs | 1236 ++++++++++++++--- compiler/rustc_feature/src/lib.rs | 7 +- compiler/rustc_metadata/src/rmeta/encoder.rs | 4 + compiler/rustc_middle/src/ty/mod.rs | 9 +- compiler/rustc_passes/src/check_attr.rs | 124 +- compiler/rustc_passes/src/errors.rs | 24 + 10 files changed, 1252 insertions(+), 269 deletions(-) diff --git a/compiler/rustc_attr_parsing/src/validate_attr.rs b/compiler/rustc_attr_parsing/src/validate_attr.rs index f5ff312f3adec..eb3c4796f02d9 100644 --- a/compiler/rustc_attr_parsing/src/validate_attr.rs +++ b/compiler/rustc_attr_parsing/src/validate_attr.rs @@ -1,14 +1,15 @@ //! Meta-syntax validation logic of attributes for post-expansion. use std::convert::identity; +use std::slice; use rustc_ast::token::Delimiter; use rustc_ast::tokenstream::DelimSpan; use rustc_ast::{ self as ast, AttrArgs, Attribute, DelimArgs, MetaItem, MetaItemInner, MetaItemKind, Safety, }; -use rustc_errors::{Applicability, PResult}; -use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP}; +use rustc_errors::{Applicability, FatalError, PResult}; +use rustc_feature::{AttributeTemplate, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute}; use rustc_hir::AttrPath; use rustc_hir::lints::AttributeLintKind; use rustc_parse::parse_in; @@ -17,23 +18,43 @@ use rustc_session::lint::builtin::ILL_FORMED_ATTRIBUTE_INPUT; use rustc_session::parse::ParseSess; use rustc_span::{Span, Symbol, sym}; -use crate::session_diagnostics as errors; +use crate::{AttributeParser, Late, session_diagnostics as errors}; pub fn check_attr(psess: &ParseSess, attr: &Attribute) { - // Built-in attributes are parsed in their respective attribute parsers, so can be ignored here - if attr.is_doc_comment() - || attr.name().is_some_and(|name| BUILTIN_ATTRIBUTE_MAP.contains_key(&name)) + if attr.is_doc_comment() || attr.has_name(sym::cfg_trace) || attr.has_name(sym::cfg_attr_trace) { return; } - let attr_item = attr.get_normal_item(); - if let AttrArgs::Eq { .. } = attr_item.args.unparsed_ref().unwrap() { - // All key-value attributes are restricted to meta-item syntax. - match parse_meta(psess, attr) { - Ok(_) => {} - Err(err) => { - err.emit(); + let builtin_attr_info = attr.name().and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name)); + + // Check input tokens for built-in and key-value attributes. + match builtin_attr_info { + // `rustc_dummy` doesn't have any restrictions specific to built-in attributes. + Some(BuiltinAttribute { name, template, .. }) => { + if AttributeParser::::is_parsed_attribute(slice::from_ref(&name)) { + return; + } + match parse_meta(psess, attr) { + // Don't check safety again, we just did that + Ok(meta) => { + check_builtin_meta_item(psess, &meta, attr.style, *name, *template, false) + } + Err(err) => { + err.emit(); + } + } + } + _ => { + let attr_item = attr.get_normal_item(); + if let AttrArgs::Eq { .. } = attr_item.args.unparsed_ref().unwrap() { + // All key-value attributes are restricted to meta-item syntax. + match parse_meta(psess, attr) { + Ok(_) => {} + Err(err) => { + err.emit(); + } + } } } } @@ -148,7 +169,7 @@ pub fn check_builtin_meta_item( } } -pub fn emit_malformed_attribute( +fn emit_malformed_attribute( psess: &ParseSess, style: ast::AttrStyle, span: Span, @@ -210,3 +231,15 @@ pub fn emit_malformed_attribute( err.emit(); } } + +pub fn emit_fatal_malformed_builtin_attribute( + psess: &ParseSess, + attr: &Attribute, + name: Symbol, +) -> ! { + let template = BUILTIN_ATTRIBUTE_MAP.get(&name).expect("builtin attr defined").template; + emit_malformed_attribute(psess, attr.style, attr.span, name, template); + // This is fatal, otherwise it will likely cause a cascade of other errors + // (and an error here is expected to be very rare). + FatalError.raise() +} diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index cee333e0a59fd..6c5732f497f8a 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -603,3 +603,21 @@ pub(crate) struct TrailingMacro { pub is_trailing: bool, pub name: Ident, } + +#[derive(Diagnostic)] +#[diag("unused attribute `{$attr_name}`")] +pub(crate) struct UnusedBuiltinAttribute { + #[note( + "the built-in attribute `{$attr_name}` will be ignored, since it's applied to the macro invocation `{$macro_name}`" + )] + pub invoc_span: Span, + pub attr_name: Symbol, + pub macro_name: String, + #[suggestion( + "remove the attribute", + code = "", + applicability = "machine-applicable", + style = "tool-only" + )] + pub attr_span: Span, +} diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 5e7c15ba1d7b8..91d6611d719cb 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -15,8 +15,8 @@ use rustc_ast::{ use rustc_ast_pretty::pprust; use rustc_attr_parsing::parser::AllowExprMetavar; use rustc_attr_parsing::{ - AttributeParser, CFG_TEMPLATE, EvalConfigResult, ShouldEmit, eval_config_entry, parse_cfg, - validate_attr, + AttributeParser, CFG_TEMPLATE, Early, EvalConfigResult, ShouldEmit, eval_config_entry, + parse_cfg, validate_attr, }; use rustc_data_structures::flat_map_in_place::FlatMapInPlace; use rustc_data_structures::stack::ensure_sufficient_stack; @@ -30,7 +30,7 @@ use rustc_parse::parser::{ RecoverColon, RecoverComma, Recovery, token_descr, }; use rustc_session::Session; -use rustc_session::lint::builtin::UNUSED_DOC_COMMENTS; +use rustc_session::lint::builtin::{UNUSED_ATTRIBUTES, UNUSED_DOC_COMMENTS}; use rustc_session::parse::feature_err; use rustc_span::hygiene::SyntaxContext; use rustc_span::{ErrorGuaranteed, FileName, Ident, LocalExpnId, Span, Symbol, sym}; @@ -2274,6 +2274,21 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { self.cx.current_expansion.lint_node_id, crate::errors::MacroCallUnusedDocComment { span: attr.span }, ); + } else if rustc_attr_parsing::is_builtin_attr(attr) + && !AttributeParser::::is_parsed_attribute(&attr.path()) + { + let attr_name = attr.name().unwrap(); + self.cx.sess.psess.buffer_lint( + UNUSED_ATTRIBUTES, + attr.span, + self.cx.current_expansion.lint_node_id, + crate::errors::UnusedBuiltinAttribute { + attr_name, + macro_name: pprust::path_to_string(&call.path), + invoc_span: call.path.span, + attr_span: attr.span, + }, + ); } } } diff --git a/compiler/rustc_expand/src/module.rs b/compiler/rustc_expand/src/module.rs index 803803ec3f6cb..79ab3cab22ce2 100644 --- a/compiler/rustc_expand/src/module.rs +++ b/compiler/rustc_expand/src/module.rs @@ -2,14 +2,12 @@ use std::iter::once; use std::path::{self, Path, PathBuf}; use rustc_ast::{AttrVec, Attribute, Inline, Item, ModSpans}; -use rustc_attr_parsing::validate_attr::emit_malformed_attribute; +use rustc_attr_parsing::validate_attr; use rustc_errors::{Diag, ErrorGuaranteed}; -use rustc_feature::template; use rustc_parse::lexer::StripTokens; use rustc_parse::{exp, new_parser_from_file, unwrap_or_emit_fatal}; use rustc_session::Session; use rustc_session::parse::ParseSess; -use rustc_span::fatal_error::FatalError; use rustc_span::{Ident, Span, sym}; use thin_vec::ThinVec; @@ -186,7 +184,6 @@ pub(crate) fn mod_file_path_from_attr( attrs: &[Attribute], dir_path: &Path, ) -> Option { - // FIXME(154781) use a parsed attribute here // Extract path string from first `#[path = "path_string"]` attribute. let first_path = attrs.iter().find(|at| at.has_name(sym::path))?; let Some(path_sym) = first_path.value_str() else { @@ -198,17 +195,7 @@ pub(crate) fn mod_file_path_from_attr( // Usually bad forms are checked during semantic analysis via // `TyCtxt::check_mod_attrs`), but by the time that runs the macro // is expanded, and it doesn't give an error. - emit_malformed_attribute( - &sess.psess, - first_path.style, - first_path.span, - sym::path, - template!( - NameValueStr: "file", - "https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute" - ), - ); - FatalError.raise() + validate_attr::emit_fatal_malformed_builtin_attribute(&sess.psess, first_path, sym::path); }; let path_str = path_sym.as_str(); diff --git a/compiler/rustc_feature/src/builtin_attrs.rs b/compiler/rustc_feature/src/builtin_attrs.rs index b8b9226cc6021..acbcba90fbcc0 100644 --- a/compiler/rustc_feature/src/builtin_attrs.rs +++ b/compiler/rustc_feature/src/builtin_attrs.rs @@ -2,9 +2,12 @@ use std::sync::LazyLock; +use AttributeDuplicates::*; use AttributeGate::*; +use AttributeType::*; use rustc_data_structures::fx::FxHashMap; use rustc_hir::AttrStyle; +use rustc_hir::attrs::EncodeCrossCrate; use rustc_span::edition::Edition; use rustc_span::{Symbol, sym}; @@ -70,10 +73,21 @@ pub fn find_gated_cfg(pred: impl Fn(Symbol) -> bool) -> Option<&'static GatedCfg // move that documentation into the relevant place in the other docs, and // remove the chapter on the flag. +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum AttributeType { + /// Normal, builtin attribute that is consumed + /// by the compiler before the unused_attribute check + Normal, + + /// Builtin attribute that is only allowed at the crate level + CrateLevel, +} + #[derive(Copy, Clone, PartialEq, Debug)] pub enum AttributeSafety { /// Normal attribute that does not need `#[unsafe(...)]` Normal, + /// Unsafe attribute that requires safety obligations to be discharged. /// /// An error is emitted when `#[unsafe(...)]` is omitted, except when the attribute's edition @@ -167,6 +181,57 @@ impl AttributeTemplate { } } +/// How to handle multiple duplicate attributes on the same item. +#[derive(Clone, Copy, Default)] +pub enum AttributeDuplicates { + /// Duplicates of this attribute are allowed. + /// + /// This should only be used with attributes where duplicates have semantic + /// meaning, or some kind of "additive" behavior. For example, `#[warn(..)]` + /// can be specified multiple times, and it combines all the entries. Or use + /// this if there is validation done elsewhere. + #[default] + DuplicatesOk, + /// Duplicates after the first attribute will be an unused_attribute warning. + /// + /// This is usually used for "word" attributes, where they are used as a + /// boolean marker, like `#[used]`. It is not necessarily wrong that there + /// are duplicates, but the others should probably be removed. + WarnFollowing, + /// Same as `WarnFollowing`, but only issues warnings for word-style attributes. + /// + /// This is only for special cases, for example multiple `#[macro_use]` can + /// be warned, but multiple `#[macro_use(...)]` should not because the list + /// form has different meaning from the word form. + WarnFollowingWordOnly, + /// Duplicates after the first attribute will be an error. + /// + /// This should be used where duplicates would be ignored, but carry extra + /// meaning that could cause confusion. For example, `#[stable(since="1.0")] + /// #[stable(since="2.0")]`, which version should be used for `stable`? + ErrorFollowing, + /// Duplicates preceding the last instance of the attribute will be an error. + /// + /// This is the same as `ErrorFollowing`, except the last attribute is the + /// one that is "used". This is typically used in cases like codegen + /// attributes which usually only honor the last attribute. + ErrorPreceding, + /// Duplicates after the first attribute will be an unused_attribute warning + /// with a note that this will be an error in the future. + /// + /// This should be used for attributes that should be `ErrorFollowing`, but + /// because older versions of rustc silently accepted (and ignored) the + /// attributes, this is used to transition. + FutureWarnFollowing, + /// Duplicates preceding the last instance of the attribute will be a + /// warning, with a note that this will be an error in the future. + /// + /// This is the same as `FutureWarnFollowing`, except the last attribute is + /// the one that is "used". Ideally these can eventually migrate to + /// `ErrorPreceding`. + FutureWarnPreceding, +} + /// A convenience macro for constructing attribute templates. /// E.g., `template!(Word, List: "description")` means that the attribute /// supports forms `#[attr]` and `#[attr(description)]`. @@ -203,31 +268,50 @@ macro_rules! template { } macro_rules! ungated { - (unsafe($edition:ident) $attr:ident $(,)?) => { + (unsafe($edition:ident) $attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, + encode_cross_crate: $encode_cross_crate, + type_: $typ, safety: AttributeSafety::Unsafe { unsafe_since: Some(Edition::$edition) }, + template: $tpl, gate: Ungated, + duplicates: $duplicates, } }; - (unsafe $attr:ident $(,)?) => { + (unsafe $attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, + encode_cross_crate: $encode_cross_crate, + type_: $typ, safety: AttributeSafety::Unsafe { unsafe_since: None }, + template: $tpl, gate: Ungated, + duplicates: $duplicates, } }; - ($attr:ident $(,)?) => { - BuiltinAttribute { name: sym::$attr, safety: AttributeSafety::Normal, gate: Ungated } + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr $(,)?) => { + BuiltinAttribute { + name: sym::$attr, + encode_cross_crate: $encode_cross_crate, + type_: $typ, + safety: AttributeSafety::Normal, + template: $tpl, + gate: Ungated, + duplicates: $duplicates, + } }; } macro_rules! gated { - (unsafe $attr:ident, $gate:ident, $message:expr $(,)?) => { + (unsafe $attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $gate:ident, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, + encode_cross_crate: $encode_cross_crate, + type_: $typ, safety: AttributeSafety::Unsafe { unsafe_since: None }, - + template: $tpl, + duplicates: $duplicates, gate: Gated { feature: sym::$gate, message: $message, @@ -236,11 +320,14 @@ macro_rules! gated { }, } }; - (unsafe $attr:ident, $message:expr $(,)?) => { + (unsafe $attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, + encode_cross_crate: $encode_cross_crate, + type_: $typ, safety: AttributeSafety::Unsafe { unsafe_since: None }, - + template: $tpl, + duplicates: $duplicates, gate: Gated { feature: sym::$attr, message: $message, @@ -249,11 +336,14 @@ macro_rules! gated { }, } }; - ($attr:ident, $gate:ident, $message:expr $(,)?) => { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $gate:ident, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, + encode_cross_crate: $encode_cross_crate, + type_: $typ, safety: AttributeSafety::Normal, - + template: $tpl, + duplicates: $duplicates, gate: Gated { feature: sym::$gate, message: $message, @@ -262,11 +352,14 @@ macro_rules! gated { }, } }; - ($attr:ident, $message:expr $(,)?) => { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $message:expr $(,)?) => { BuiltinAttribute { name: sym::$attr, + encode_cross_crate: $encode_cross_crate, + type_: $typ, safety: AttributeSafety::Normal, - + template: $tpl, + duplicates: $duplicates, gate: Gated { feature: sym::$attr, message: $message, @@ -278,8 +371,13 @@ macro_rules! gated { } macro_rules! rustc_attr { - (TEST, $attr:ident $(,)?) => { - rustc_attr!( $attr, + (TEST, $attr:ident, $typ:expr, $tpl:expr, $duplicate:expr, $encode_cross_crate:expr $(,)?) => { + rustc_attr!( + $attr, + $typ, + $tpl, + $duplicate, + $encode_cross_crate, concat!( "the `#[", stringify!($attr), @@ -287,10 +385,14 @@ macro_rules! rustc_attr { ), ) }; - ($attr:ident $(, $notes:expr)* $(,)?) => { + ($attr:ident, $typ:expr, $tpl:expr, $duplicates:expr, $encode_cross_crate:expr, $($notes:expr),* $(,)?) => { BuiltinAttribute { name: sym::$attr, + encode_cross_crate: $encode_cross_crate, + type_: $typ, safety: AttributeSafety::Normal, + template: $tpl, + duplicates: $duplicates, gate: Gated { feature: sym::rustc_attrs, message: "use of an internal attribute", @@ -314,7 +416,15 @@ macro_rules! experimental { pub struct BuiltinAttribute { pub name: Symbol, + /// Whether this attribute is encode cross crate. + /// + /// If so, it is encoded in the crate metadata. + /// Otherwise, it can only be used in the local crate. + pub encode_cross_crate: EncodeCrossCrate, + pub type_: AttributeType, pub safety: AttributeSafety, + pub template: AttributeTemplate, + pub duplicates: AttributeDuplicates, pub gate: AttributeGate, } @@ -326,100 +436,379 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // ========================================================================== // Conditional compilation: - ungated!(cfg), - ungated!(cfg_attr), + ungated!( + cfg, Normal, + template!( + List: &["predicate"], + "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg-attribute" + ), + DuplicatesOk, EncodeCrossCrate::No + ), + ungated!( + cfg_attr, Normal, + template!( + List: &["predicate, attr1, attr2, ..."], + "https://doc.rust-lang.org/reference/conditional-compilation.html#the-cfg_attr-attribute" + ), + DuplicatesOk, EncodeCrossCrate::No + ), // Testing: - ungated!(ignore), - ungated!(should_panic), + ungated!( + ignore, Normal, + template!( + Word, + NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/testing.html#the-ignore-attribute" + ), + WarnFollowing, EncodeCrossCrate::No, + ), + ungated!( + should_panic, Normal, + template!( + Word, + List: &[r#"expected = "reason""#], + NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/testing.html#the-should_panic-attribute" + ), + FutureWarnFollowing, EncodeCrossCrate::No, + ), // Macros: - ungated!(automatically_derived), - ungated!(macro_use), - ungated!(macro_escape), // Deprecated synonym for `macro_use`. - ungated!(macro_export), - ungated!(proc_macro), - ungated!(proc_macro_derive), - ungated!(proc_macro_attribute), + ungated!( + automatically_derived, Normal, + template!( + Word, + "https://doc.rust-lang.org/reference/attributes/derive.html#the-automatically_derived-attribute" + ), + WarnFollowing, EncodeCrossCrate::Yes + ), + ungated!( + macro_use, Normal, + template!( + Word, + List: &["name1, name2, ..."], + "https://doc.rust-lang.org/reference/macros-by-example.html#the-macro_use-attribute" + ), + WarnFollowingWordOnly, EncodeCrossCrate::No, + ), + ungated!(macro_escape, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No), // Deprecated synonym for `macro_use`. + ungated!( + macro_export, Normal, + template!( + Word, + List: &["local_inner_macros"], + "https://doc.rust-lang.org/reference/macros-by-example.html#path-based-scope" + ), + WarnFollowing, EncodeCrossCrate::Yes + ), + ungated!( + proc_macro, Normal, + template!( + Word, + "https://doc.rust-lang.org/reference/procedural-macros.html#function-like-procedural-macros"), + ErrorFollowing, EncodeCrossCrate::No + ), + ungated!( + proc_macro_derive, Normal, + template!( + List: &["TraitName", "TraitName, attributes(name1, name2, ...)"], + "https://doc.rust-lang.org/reference/procedural-macros.html#derive-macros" + ), + ErrorFollowing, EncodeCrossCrate::No, + ), + ungated!( + proc_macro_attribute, Normal, + template!(Word, "https://doc.rust-lang.org/reference/procedural-macros.html#attribute-macros"), + ErrorFollowing, EncodeCrossCrate::No + ), // Lints: - ungated!(warn), - ungated!(allow), - ungated!(expect), - ungated!(forbid), - ungated!(deny), - ungated!(must_use), - gated!(must_not_suspend, experimental!(must_not_suspend)), - ungated!(deprecated), + ungated!( + warn, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), + DuplicatesOk, EncodeCrossCrate::No, + ), + ungated!( + allow, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), + DuplicatesOk, EncodeCrossCrate::No, + ), + ungated!( + expect, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), + DuplicatesOk, EncodeCrossCrate::No, + ), + ungated!( + forbid, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), + DuplicatesOk, EncodeCrossCrate::No + ), + ungated!( + deny, Normal, + template!( + List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" + ), + DuplicatesOk, EncodeCrossCrate::No + ), + ungated!( + must_use, Normal, + template!( + Word, + NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-must_use-attribute" + ), + FutureWarnFollowing, EncodeCrossCrate::Yes + ), + gated!( + must_not_suspend, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing, + EncodeCrossCrate::Yes, experimental!(must_not_suspend) + ), + ungated!( + deprecated, Normal, + template!( + Word, + List: &[r#"/*opt*/ since = "version", /*opt*/ note = "reason""#], + NameValueStr: "reason", + "https://doc.rust-lang.org/reference/attributes/diagnostics.html#the-deprecated-attribute" + ), + ErrorFollowing, EncodeCrossCrate::Yes + ), // Crate properties: - ungated!(crate_name), - ungated!(crate_type), + ungated!( + crate_name, CrateLevel, + template!( + NameValueStr: "name", + "https://doc.rust-lang.org/reference/crates-and-source-files.html#the-crate_name-attribute" + ), + FutureWarnFollowing, EncodeCrossCrate::No, + ), + ungated!( + crate_type, CrateLevel, + template!( + NameValueStr: ["bin", "lib", "dylib", "cdylib", "rlib", "staticlib", "sdylib", "proc-macro"], + "https://doc.rust-lang.org/reference/linkage.html" + ), + DuplicatesOk, EncodeCrossCrate::No, + ), // ABI, linking, symbols, and FFI - ungated!(link), - ungated!(link_name), - ungated!(no_link), - ungated!(repr), + ungated!( + link, Normal, + template!(List: &[ + r#"name = "...""#, + r#"name = "...", kind = "dylib|static|...""#, + r#"name = "...", wasm_import_module = "...""#, + r#"name = "...", import_name_type = "decorated|noprefix|undecorated""#, + r#"name = "...", kind = "dylib|static|...", wasm_import_module = "...", import_name_type = "decorated|noprefix|undecorated""#, + ], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link-attribute"), + DuplicatesOk, EncodeCrossCrate::No, + ), + ungated!( + link_name, Normal, + template!(NameValueStr: "name", "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_name-attribute"), + FutureWarnPreceding, EncodeCrossCrate::Yes + ), + ungated!( + no_link, Normal, + template!(Word, "https://doc.rust-lang.org/reference/items/extern-crates.html#the-no_link-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + repr, Normal, + template!( + List: &["C", "Rust", "transparent", "align(...)", "packed(...)", ""], + "https://doc.rust-lang.org/reference/type-layout.html#representations" + ), + DuplicatesOk, EncodeCrossCrate::No + ), // FIXME(#82232, #143834): temporarily renamed to mitigate `#[align]` nameres ambiguity - gated!(rustc_align,fn_align, experimental!(rustc_align)), - gated!(rustc_align_static,static_align, experimental!(rustc_align_static)), - ungated!(unsafe(Edition2024) export_name), - ungated!(unsafe(Edition2024) link_section), - ungated!(unsafe(Edition2024) no_mangle), - ungated!(used), - ungated!(link_ordinal), - ungated!(unsafe naked), + gated!(rustc_align, Normal, template!(List: &["alignment"]), DuplicatesOk, EncodeCrossCrate::No, fn_align, experimental!(rustc_align)), + gated!(rustc_align_static, Normal, template!(List: &["alignment"]), DuplicatesOk, EncodeCrossCrate::No, static_align, experimental!(rustc_align_static)), + ungated!( + unsafe(Edition2024) export_name, Normal, + template!(NameValueStr: "name", "https://doc.rust-lang.org/reference/abi.html#the-export_name-attribute"), + FutureWarnPreceding, EncodeCrossCrate::No + ), + ungated!( + unsafe(Edition2024) link_section, Normal, + template!(NameValueStr: "name", "https://doc.rust-lang.org/reference/abi.html#the-link_section-attribute"), + FutureWarnPreceding, EncodeCrossCrate::No + ), + ungated!( + unsafe(Edition2024) no_mangle, Normal, + template!(Word, "https://doc.rust-lang.org/reference/abi.html#the-no_mangle-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + used, Normal, + template!(Word, List: &["compiler", "linker"], "https://doc.rust-lang.org/reference/abi.html#the-used-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + link_ordinal, Normal, + template!(List: &["ordinal"], "https://doc.rust-lang.org/reference/items/external-blocks.html#the-link_ordinal-attribute"), + ErrorPreceding, EncodeCrossCrate::Yes + ), + ungated!( + unsafe naked, Normal, + template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-naked-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), // See `TyAndLayout::pass_indirectly_in_non_rustic_abis` for details. - rustc_attr!(rustc_pass_indirectly_in_non_rustic_abis, "types marked with `#[rustc_pass_indirectly_in_non_rustic_abis]` are always passed indirectly by non-Rustic ABIs"), + rustc_attr!( + rustc_pass_indirectly_in_non_rustic_abis, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::No, + "types marked with `#[rustc_pass_indirectly_in_non_rustic_abis]` are always passed indirectly by non-Rustic ABIs" + ), // Limits: - ungated!(recursion_limit), - ungated!(type_length_limit), + ungated!( + recursion_limit, CrateLevel, + template!(NameValueStr: "N", "https://doc.rust-lang.org/reference/attributes/limits.html#the-recursion_limit-attribute"), + FutureWarnFollowing, EncodeCrossCrate::No + ), + ungated!( + type_length_limit, CrateLevel, + template!(NameValueStr: "N", "https://doc.rust-lang.org/reference/attributes/limits.html#the-type_length_limit-attribute"), + FutureWarnFollowing, EncodeCrossCrate::No + ), gated!( - move_size_limit, large_assignments, experimental!(move_size_limit) + move_size_limit, CrateLevel, template!(NameValueStr: "N"), ErrorFollowing, + EncodeCrossCrate::No, large_assignments, experimental!(move_size_limit) ), // Entry point: - ungated!(no_main), + ungated!( + no_main, CrateLevel, + template!(Word, "https://doc.rust-lang.org/reference/crates-and-source-files.html#the-no_main-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), // Modules, prelude, and resolution: - ungated!(path), - ungated!(no_std), - ungated!(no_implicit_prelude), - ungated!(non_exhaustive), + ungated!( + path, Normal, + template!(NameValueStr: "file", "https://doc.rust-lang.org/reference/items/modules.html#the-path-attribute"), + FutureWarnFollowing, EncodeCrossCrate::No + ), + ungated!( + no_std, CrateLevel, + template!(Word, "https://doc.rust-lang.org/reference/names/preludes.html#the-no_std-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + no_implicit_prelude, Normal, + template!(Word, "https://doc.rust-lang.org/reference/names/preludes.html#the-no_implicit_prelude-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + non_exhaustive, Normal, + template!(Word, "https://doc.rust-lang.org/reference/attributes/type_system.html#the-non_exhaustive-attribute"), + WarnFollowing, EncodeCrossCrate::Yes + ), // Runtime - ungated!(windows_subsystem), - ungated!(// RFC 2070 - panic_handler + ungated!( + windows_subsystem, CrateLevel, + template!(NameValueStr: ["windows", "console"], "https://doc.rust-lang.org/reference/runtime.html#the-windows_subsystem-attribute"), + FutureWarnFollowing, EncodeCrossCrate::No + ), + ungated!( // RFC 2070 + panic_handler, Normal, + template!(Word, "https://doc.rust-lang.org/reference/panic.html#the-panic_handler-attribute"), + WarnFollowing, EncodeCrossCrate::Yes ), // Code generation: - ungated!(inline), - ungated!(cold), - ungated!(no_builtins), - ungated!(target_feature), - ungated!(track_caller), - ungated!(instruction_set), + ungated!( + inline, Normal, + template!( + Word, + List: &["always", "never"], + "https://doc.rust-lang.org/reference/attributes/codegen.html#the-inline-attribute" + ), + FutureWarnFollowing, EncodeCrossCrate::No + ), + ungated!( + cold, Normal, + template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-cold-attribute"), + WarnFollowing, EncodeCrossCrate::No + ), + ungated!( + no_builtins, CrateLevel, + template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-no_builtins-attribute"), + WarnFollowing, EncodeCrossCrate::Yes + ), + ungated!( + target_feature, Normal, + template!(List: &[r#"enable = "name""#], "https://doc.rust-lang.org/reference/attributes/codegen.html#the-target_feature-attribute"), + DuplicatesOk, EncodeCrossCrate::No, + ), + ungated!( + track_caller, Normal, + template!(Word, "https://doc.rust-lang.org/reference/attributes/codegen.html#the-track_caller-attribute"), + WarnFollowing, EncodeCrossCrate::Yes + ), + ungated!( + instruction_set, Normal, + template!(List: &["set"], "https://doc.rust-lang.org/reference/attributes/codegen.html#the-instruction_set-attribute"), + ErrorPreceding, EncodeCrossCrate::No + ), gated!( - unsafe force_target_feature, - effective_target_features, experimental!(force_target_feature) + unsafe force_target_feature, Normal, template!(List: &[r#"enable = "name""#]), + DuplicatesOk, EncodeCrossCrate::No, effective_target_features, experimental!(force_target_feature) ), gated!( - sanitize, - sanitize, experimental!(sanitize) + sanitize, Normal, template!(List: &[r#"address = "on|off""#, r#"kernel_address = "on|off""#, r#"cfi = "on|off""#, r#"hwaddress = "on|off""#, r#"kernel_hwaddress = "on|off""#, r#"kcfi = "on|off""#, r#"memory = "on|off""#, r#"memtag = "on|off""#, r#"shadow_call_stack = "on|off""#, r#"thread = "on|off""#]), ErrorPreceding, + EncodeCrossCrate::No, sanitize, experimental!(sanitize), ), gated!( - coverage, + coverage, Normal, template!(OneOf: &[sym::off, sym::on]), + ErrorPreceding, EncodeCrossCrate::No, coverage_attribute, experimental!(coverage) ), - ungated!(doc), + ungated!( + doc, Normal, + template!( + List: &["hidden", "inline"], + NameValueStr: "string", + "https://doc.rust-lang.org/rustdoc/write-documentation/the-doc-attribute.html" + ), + DuplicatesOk, EncodeCrossCrate::Yes + ), // Debugging - ungated!(debugger_visualizer), - ungated!(collapse_debuginfo), + ungated!( + debugger_visualizer, Normal, + template!( + List: &[r#"natvis_file = "...", gdb_script_file = "...""#], + "https://doc.rust-lang.org/reference/attributes/debugger.html#the-debugger_visualizer-attribute" + ), + DuplicatesOk, EncodeCrossCrate::No + ), + ungated!( + collapse_debuginfo, Normal, + template!( + List: &["no", "external", "yes"], + "https://doc.rust-lang.org/reference/attributes/debugger.html#the-collapse_debuginfo-attribute" + ), + ErrorFollowing, EncodeCrossCrate::Yes + ), // ========================================================================== // Unstable attributes: @@ -427,61 +816,71 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Linking: gated!( - export_stable, experimental!(export_stable) + export_stable, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, experimental!(export_stable) ), // Testing: gated!( - test_runner, custom_test_frameworks, - "custom test frameworks are an unstable feature" + test_runner, CrateLevel, template!(List: &["path"]), ErrorFollowing, + EncodeCrossCrate::Yes, custom_test_frameworks, + "custom test frameworks are an unstable feature", ), gated!( - reexport_test_harness_main, custom_test_frameworks, - "custom test frameworks are an unstable feature" + reexport_test_harness_main, CrateLevel, template!(NameValueStr: "name"), ErrorFollowing, + EncodeCrossCrate::No, custom_test_frameworks, + "custom test frameworks are an unstable feature", ), // RFC #1268 gated!( - marker,marker_trait_attr, experimental!(marker) + marker, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, + marker_trait_attr, experimental!(marker) ), gated!( - thread_local,"`#[thread_local]` is an experimental feature, and does not currently handle destructors" + thread_local, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, + "`#[thread_local]` is an experimental feature, and does not currently handle destructors", ), gated!( - no_core, experimental!(no_core) + no_core, CrateLevel, template!(Word), WarnFollowing, + EncodeCrossCrate::No, experimental!(no_core) ), // RFC 2412 gated!( - optimize, - optimize_attribute, experimental!(optimize) + optimize, Normal, template!(List: &["none", "size", "speed"]), ErrorPreceding, + EncodeCrossCrate::No, optimize_attribute, experimental!(optimize) ), gated!( - unsafe ffi_pure, experimental!(ffi_pure) + unsafe ffi_pure, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, experimental!(ffi_pure) ), gated!( - unsafe ffi_const, experimental!(ffi_const) + unsafe ffi_const, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, experimental!(ffi_const) ), gated!( - register_tool, experimental!(register_tool) + register_tool, CrateLevel, template!(List: &["tool1, tool2, ..."]), DuplicatesOk, + EncodeCrossCrate::No, experimental!(register_tool), ), // `#[cfi_encoding = ""]` gated!( - cfi_encoding, - experimental!(cfi_encoding) + cfi_encoding, Normal, template!(NameValueStr: "encoding"), ErrorPreceding, + EncodeCrossCrate::Yes, experimental!(cfi_encoding) ), // `#[coroutine]` attribute to be applied to closures to make them coroutines instead gated!( - coroutine,coroutines, experimental!(coroutine) + coroutine, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::No, coroutines, experimental!(coroutine) ), // RFC 3543 // `#[patchable_function_entry(prefix_nops = m, entry_nops = n)]` gated!( - patchable_function_entry, - experimental!(patchable_function_entry) + patchable_function_entry, Normal, template!(List: &["prefix_nops = m, entry_nops = n"]), ErrorPreceding, + EncodeCrossCrate::Yes, experimental!(patchable_function_entry) ), // The `#[loop_match]` and `#[const_continue]` attributes are part of the @@ -489,10 +888,12 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // // - https://github.com/rust-lang/rust/issues/132306 gated!( - const_continue,loop_match, experimental!(const_continue) + const_continue, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::No, loop_match, experimental!(const_continue) ), gated!( - loop_match,loop_match, experimental!(loop_match) + loop_match, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::No, loop_match, experimental!(loop_match) ), // The `#[pin_v2]` attribute is part of the `pin_ergonomics` experiment @@ -500,40 +901,74 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // // - https://github.com/rust-lang/rust/issues/130494 gated!( - pin_v2,pin_ergonomics, experimental!(pin_v2), + pin_v2, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::Yes, pin_ergonomics, experimental!(pin_v2), ), // ========================================================================== // Internal attributes: Stability, deprecation, and unsafe: // ========================================================================== - ungated!(feature), + ungated!( + feature, CrateLevel, + template!(List: &["name1, name2, ..."]), DuplicatesOk, EncodeCrossCrate::No, + ), // DuplicatesOk since it has its own validation - ungated!(stable), - ungated!(unstable), - ungated!(unstable_feature_bound), - ungated!(rustc_const_unstable), - ungated!(rustc_const_stable), - ungated!(rustc_default_body_unstable), - gated!( - allow_internal_unstable, + ungated!( + stable, Normal, + template!(List: &[r#"feature = "name", since = "version""#]), DuplicatesOk, EncodeCrossCrate::No, + ), + ungated!( + unstable, Normal, + template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), DuplicatesOk, + EncodeCrossCrate::Yes + ), + ungated!( + unstable_feature_bound, Normal, template!(Word, List: &["feat1, feat2, ..."]), + DuplicatesOk, EncodeCrossCrate::No, + ), + ungated!( + rustc_const_unstable, Normal, template!(List: &[r#"feature = "name""#]), + DuplicatesOk, EncodeCrossCrate::Yes + ), + ungated!( + rustc_const_stable, Normal, + template!(List: &[r#"feature = "name""#]), DuplicatesOk, EncodeCrossCrate::No, + ), + ungated!( + rustc_default_body_unstable, Normal, + template!(List: &[r#"feature = "name", reason = "...", issue = "N""#]), + DuplicatesOk, EncodeCrossCrate::No + ), + gated!( + allow_internal_unstable, Normal, template!(Word, List: &["feat1, feat2, ..."]), + DuplicatesOk, EncodeCrossCrate::Yes, "allow_internal_unstable side-steps feature gating and stability checks", ), gated!( - allow_internal_unsafe, "allow_internal_unsafe side-steps the unsafe_code lint", + allow_internal_unsafe, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, "allow_internal_unsafe side-steps the unsafe_code lint", ), gated!( - rustc_eii_foreign_item, - eii_internals, + rustc_eii_foreign_item, Normal, template!(Word), + ErrorFollowing, EncodeCrossCrate::Yes, eii_internals, "used internally to mark types with a `transparent` representation when it is guaranteed by the documentation", ), - rustc_attr!(rustc_allowed_through_unstable_modules, + rustc_attr!( + rustc_allowed_through_unstable_modules, Normal, template!(NameValueStr: "deprecation message"), + WarnFollowing, EncodeCrossCrate::No, "rustc_allowed_through_unstable_modules special cases accidental stabilizations of stable items \ through unstable paths" ), - rustc_attr!(rustc_deprecated_safe_2024,"`#[rustc_deprecated_safe_2024]` is used to declare functions unsafe across the edition 2024 boundary", + rustc_attr!( + rustc_deprecated_safe_2024, Normal, template!(List: &[r#"audit_that = "...""#]), + ErrorFollowing, EncodeCrossCrate::Yes, + "`#[rustc_deprecated_safe_2024]` is used to declare functions unsafe across the edition 2024 boundary", ), - rustc_attr!(rustc_pub_transparent,"used internally to mark types with a `transparent` representation when it is guaranteed by the documentation", + rustc_attr!( + rustc_pub_transparent, Normal, template!(Word), + ErrorFollowing, EncodeCrossCrate::Yes, + "used internally to mark types with a `transparent` representation when it is guaranteed by the documentation", ), @@ -541,13 +976,25 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Internal attributes: Type system related: // ========================================================================== - gated!(fundamental, experimental!(fundamental)), + gated!(fundamental, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes, experimental!(fundamental)), gated!( - may_dangle, dropck_eyepatch, - "`may_dangle` has unstable semantics and may be removed in the future" + may_dangle, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, dropck_eyepatch, + "`may_dangle` has unstable semantics and may be removed in the future", ), - rustc_attr!(rustc_never_type_options, + rustc_attr!( + rustc_never_type_options, + Normal, + template!(List: &[ + "", + r#"fallback = "unit""#, + r#"fallback = "niko""#, + r#"fallback = "never""#, + r#"fallback = "no""#, + ]), + ErrorFollowing, + EncodeCrossCrate::No, "`rustc_never_type_options` is used to experiment with never type fallback and work on \ never type stabilization" ), @@ -556,33 +1003,57 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // Internal attributes: Runtime related: // ========================================================================== - rustc_attr!(rustc_allocator), - rustc_attr!(rustc_nounwind), - rustc_attr!(rustc_reallocator), - rustc_attr!(rustc_deallocator), - rustc_attr!(rustc_allocator_zeroed), - rustc_attr!(rustc_allocator_zeroed_variant), + rustc_attr!( + rustc_allocator, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, + ), + rustc_attr!( + rustc_nounwind, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, + ), + rustc_attr!( + rustc_reallocator, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, + ), + rustc_attr!( + rustc_deallocator, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, + ), + rustc_attr!( + rustc_allocator_zeroed, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, + ), + rustc_attr!( + rustc_allocator_zeroed_variant, Normal, template!(NameValueStr: "function"), ErrorPreceding, + EncodeCrossCrate::Yes, + ), gated!( - default_lib_allocator, allocator_internals, experimental!(default_lib_allocator), + default_lib_allocator, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, allocator_internals, experimental!(default_lib_allocator), ), gated!( - needs_allocator, allocator_internals, experimental!(needs_allocator), + needs_allocator, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, allocator_internals, experimental!(needs_allocator), ), gated!( - panic_runtime, experimental!(panic_runtime) + panic_runtime, CrateLevel, template!(Word), WarnFollowing, + EncodeCrossCrate::No, experimental!(panic_runtime) ), gated!( - needs_panic_runtime, experimental!(needs_panic_runtime) + needs_panic_runtime, CrateLevel, template!(Word), WarnFollowing, + EncodeCrossCrate::No, experimental!(needs_panic_runtime) ), gated!( - compiler_builtins, + compiler_builtins, CrateLevel, template!(Word), WarnFollowing, + EncodeCrossCrate::No, "the `#[compiler_builtins]` attribute is used to identify the `compiler_builtins` crate \ - which contains compiler-rt intrinsics and will never be stable" + which contains compiler-rt intrinsics and will never be stable", ), gated!( - profiler_runtime, + profiler_runtime, CrateLevel, template!(Word), WarnFollowing, + EncodeCrossCrate::No, "the `#[profiler_runtime]` attribute is used to identify the `profiler_builtins` crate \ - which contains the profiler runtime and will never be stable" + which contains the profiler runtime and will never be stable", ), // ========================================================================== @@ -590,123 +1061,277 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ // ========================================================================== gated!( - linkage, - "the `linkage` attribute is experimental and not portable across platforms" + linkage, Normal, template!(NameValueStr: [ + "available_externally", + "common", + "extern_weak", + "external", + "internal", + "linkonce", + "linkonce_odr", + "weak", + "weak_odr", + ], "https://doc.rust-lang.org/reference/linkage.html"), + ErrorPreceding, EncodeCrossCrate::No, + "the `linkage` attribute is experimental and not portable across platforms", + ), + rustc_attr!( + rustc_std_internal_symbol, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, + ), + rustc_attr!( + rustc_objc_class, Normal, template!(NameValueStr: "ClassName"), ErrorPreceding, + EncodeCrossCrate::No, + ), + rustc_attr!( + rustc_objc_selector, Normal, template!(NameValueStr: "methodName"), ErrorPreceding, + EncodeCrossCrate::No, ), - rustc_attr!(rustc_std_internal_symbol), - rustc_attr!(rustc_objc_class), - rustc_attr!(rustc_objc_selector), // ========================================================================== // Internal attributes, Macro related: // ========================================================================== - rustc_attr!(rustc_builtin_macro), - rustc_attr!(rustc_proc_macro_decls), - rustc_attr!(rustc_macro_transparency, - "used internally for testing macro hygiene" + rustc_attr!( + rustc_builtin_macro, Normal, + template!(Word, List: &["name", "name, /*opt*/ attributes(name1, name2, ...)"]), ErrorFollowing, + EncodeCrossCrate::Yes, + ), + rustc_attr!( + rustc_proc_macro_decls, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, + ), + rustc_attr!( + rustc_macro_transparency, Normal, + template!(NameValueStr: ["transparent", "semiopaque", "opaque"]), ErrorFollowing, + EncodeCrossCrate::Yes, "used internally for testing macro hygiene", + ), + rustc_attr!( + rustc_autodiff, Normal, + template!(Word, List: &[r#""...""#]), DuplicatesOk, + EncodeCrossCrate::Yes, + ), + rustc_attr!( + rustc_offload_kernel, Normal, + template!(Word), DuplicatesOk, + EncodeCrossCrate::Yes, ), - rustc_attr!(rustc_autodiff), - rustc_attr!(rustc_offload_kernel), // Traces that are left when `cfg` and `cfg_attr` attributes are expanded. // The attributes are not gated, to avoid stability errors, but they cannot be used in stable // or unstable code directly because `sym::cfg_(attr_)trace` are not valid identifiers, they // can only be generated by the compiler. - ungated!(cfg_trace + ungated!( + cfg_trace, Normal, template!(Word /* irrelevant */), DuplicatesOk, + EncodeCrossCrate::Yes ), - ungated!(cfg_attr_trace + ungated!( + cfg_attr_trace, Normal, template!(Word /* irrelevant */), DuplicatesOk, + EncodeCrossCrate::No ), // ========================================================================== // Internal attributes, Diagnostics related: // ========================================================================== - rustc_attr!(rustc_on_unimplemented,"see `#[diagnostic::on_unimplemented]` for the stable equivalent of this attribute" + rustc_attr!( + rustc_on_unimplemented, Normal, + template!( + List: &[r#"/*opt*/ message = "...", /*opt*/ label = "...", /*opt*/ note = "...""#], + NameValueStr: "message" + ), + ErrorFollowing, EncodeCrossCrate::Yes, + "see `#[diagnostic::on_unimplemented]` for the stable equivalent of this attribute" + ), + rustc_attr!( + rustc_confusables, Normal, + template!(List: &[r#""name1", "name2", ..."#]), + ErrorFollowing, EncodeCrossCrate::Yes, ), - rustc_attr!(rustc_confusables), // Enumerates "identity-like" conversion methods to suggest on type mismatch. - rustc_attr!(rustc_conversion_suggestion), + rustc_attr!( + rustc_conversion_suggestion, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::Yes, + ), // Prevents field reads in the marked trait or method to be considered // during dead code analysis. - rustc_attr!(rustc_trivial_field_reads), + rustc_attr!( + rustc_trivial_field_reads, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::Yes, + ), // Used by the `rustc::potential_query_instability` lint to warn methods which // might not be stable during incremental compilation. - rustc_attr!(rustc_lint_query_instability), + rustc_attr!( + rustc_lint_query_instability, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::Yes, + ), // Used by the `rustc::untracked_query_information` lint to warn methods which // might not be stable during incremental compilation. - rustc_attr!(rustc_lint_untracked_query_information), + rustc_attr!( + rustc_lint_untracked_query_information, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::Yes, + ), // Used by the `rustc::bad_opt_access` lint to identify `DebuggingOptions` and `CodegenOptions` // types (as well as any others in future). - rustc_attr!(rustc_lint_opt_ty), + rustc_attr!( + rustc_lint_opt_ty, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::Yes, + ), // Used by the `rustc::bad_opt_access` lint on fields // types (as well as any others in future). - rustc_attr!(rustc_lint_opt_deny_field_access), + rustc_attr!( + rustc_lint_opt_deny_field_access, Normal, template!(List: &["message"]), + WarnFollowing, EncodeCrossCrate::Yes, + ), // ========================================================================== // Internal attributes, Const related: // ========================================================================== - rustc_attr!(rustc_promotable), - rustc_attr!(rustc_legacy_const_generics), + rustc_attr!( + rustc_promotable, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, ), + rustc_attr!( + rustc_legacy_const_generics, Normal, template!(List: &["N"]), ErrorFollowing, + EncodeCrossCrate::Yes, + ), // Do not const-check this function's body. It will always get replaced during CTFE via `hook_special_const_fn`. - rustc_attr!(rustc_do_not_const_check, "`#[rustc_do_not_const_check]` skips const-check for this function's body"), - rustc_attr!(rustc_const_stable_indirect, - "this is an internal implementation detail"), - rustc_attr!(rustc_intrinsic_const_stable_indirect, - "this is an internal implementation detail"), - rustc_attr!(rustc_allow_const_fn_unstable, - "rustc_allow_const_fn_unstable side-steps feature gating and stability checks" + rustc_attr!( + rustc_do_not_const_check, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::Yes, "`#[rustc_do_not_const_check]` skips const-check for this function's body", + ), + rustc_attr!( + rustc_const_stable_indirect, Normal, + template!(Word), + WarnFollowing, + EncodeCrossCrate::No, + "this is an internal implementation detail", + ), + rustc_attr!( + rustc_intrinsic_const_stable_indirect, Normal, + template!(Word), WarnFollowing, EncodeCrossCrate::No, "this is an internal implementation detail", + ), + rustc_attr!( + rustc_allow_const_fn_unstable, Normal, + template!(Word, List: &["feat1, feat2, ..."]), DuplicatesOk, EncodeCrossCrate::No, + "rustc_allow_const_fn_unstable side-steps feature gating and stability checks" ), // ========================================================================== // Internal attributes, Layout related: // ========================================================================== - rustc_attr!(rustc_layout_scalar_valid_range_start, "the `#[rustc_layout_scalar_valid_range_start]` attribute is just used to enable \ - niche optimizations in the standard library"), - rustc_attr!(rustc_layout_scalar_valid_range_end, "the `#[rustc_layout_scalar_valid_range_end]` attribute is just used to enable \ - niche optimizations in the standard library"), - rustc_attr!(rustc_simd_monomorphize_lane_limit, "the `#[rustc_simd_monomorphize_lane_limit]` attribute is just used by std::simd \ - for better error messages"), - rustc_attr!(rustc_nonnull_optimization_guaranteed, + rustc_attr!( + rustc_layout_scalar_valid_range_start, Normal, template!(List: &["value"]), ErrorFollowing, + EncodeCrossCrate::Yes, + "the `#[rustc_layout_scalar_valid_range_start]` attribute is just used to enable \ + niche optimizations in the standard library", + ), + rustc_attr!( + rustc_layout_scalar_valid_range_end, Normal, template!(List: &["value"]), ErrorFollowing, + EncodeCrossCrate::Yes, + "the `#[rustc_layout_scalar_valid_range_end]` attribute is just used to enable \ + niche optimizations in the standard library", + ), + rustc_attr!( + rustc_simd_monomorphize_lane_limit, Normal, template!(NameValueStr: "N"), ErrorFollowing, + EncodeCrossCrate::Yes, + "the `#[rustc_simd_monomorphize_lane_limit]` attribute is just used by std::simd \ + for better error messages", + ), + rustc_attr!( + rustc_nonnull_optimization_guaranteed, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::Yes, "the `#[rustc_nonnull_optimization_guaranteed]` attribute is just used to document \ guaranteed niche optimizations in the standard library", "the compiler does not even check whether the type indeed is being non-null-optimized; \ - it is your responsibility to ensure that the attribute is only used on types that are optimized"), + it is your responsibility to ensure that the attribute is only used on types that are optimized", + ), // ========================================================================== // Internal attributes, Misc: // ========================================================================== gated!( - lang,lang_items, - "lang items are subject to change" - ), - rustc_attr!(rustc_as_ptr, "`#[rustc_as_ptr]` is used to mark functions returning pointers to their inner allocations"), - rustc_attr!(rustc_should_not_be_called_on_const_items, "`#[rustc_should_not_be_called_on_const_items]` is used to mark methods that don't make sense to be called on interior mutable consts"), - rustc_attr!(rustc_pass_by_value, "`#[rustc_pass_by_value]` is used to mark types that must be passed by value instead of reference"), - rustc_attr!(rustc_never_returns_null_ptr, "`#[rustc_never_returns_null_ptr]` is used to mark functions returning non-null pointers"), - rustc_attr!(rustc_no_implicit_autorefs, "`#[rustc_no_implicit_autorefs]` is used to mark functions for which an autoref to the dereference of a raw pointer should not be used as an argument"), - rustc_attr!(rustc_coherence_is_core, "`#![rustc_coherence_is_core]` allows inherent methods on builtin types, only intended to be used in `core`"), - rustc_attr!(rustc_coinductive, "`#[rustc_coinductive]` changes a trait to be coinductive, allowing cycles in the trait solver"), - rustc_attr!(rustc_allow_incoherent_impl, "`#[rustc_allow_incoherent_impl]` has to be added to all impl items of an incoherent inherent impl"), - rustc_attr!(rustc_preserve_ub_checks, "`#![rustc_preserve_ub_checks]` prevents the designated crate from evaluating whether UB checks are enabled when optimizing MIR"), - rustc_attr!(rustc_deny_explicit_impl, + lang, Normal, template!(NameValueStr: "name"), DuplicatesOk, EncodeCrossCrate::No, lang_items, + "lang items are subject to change", + ), + rustc_attr!( + rustc_as_ptr, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::Yes, + "`#[rustc_as_ptr]` is used to mark functions returning pointers to their inner allocations" + ), + rustc_attr!( + rustc_should_not_be_called_on_const_items, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::Yes, + "`#[rustc_should_not_be_called_on_const_items]` is used to mark methods that don't make sense to be called on interior mutable consts" + ), + rustc_attr!( + rustc_pass_by_value, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::Yes, + "`#[rustc_pass_by_value]` is used to mark types that must be passed by value instead of reference" + ), + rustc_attr!( + rustc_never_returns_null_ptr, Normal, template!(Word), ErrorFollowing, + EncodeCrossCrate::Yes, + "`#[rustc_never_returns_null_ptr]` is used to mark functions returning non-null pointers" + ), + rustc_attr!( + rustc_no_implicit_autorefs, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, + "`#[rustc_no_implicit_autorefs]` is used to mark functions for which an autoref to the dereference of a raw pointer should not be used as an argument" + ), + rustc_attr!( + rustc_coherence_is_core, AttributeType::CrateLevel, template!(Word), ErrorFollowing, EncodeCrossCrate::No, + "`#![rustc_coherence_is_core]` allows inherent methods on builtin types, only intended to be used in `core`" + ), + rustc_attr!( + rustc_coinductive, AttributeType::Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, + "`#[rustc_coinductive]` changes a trait to be coinductive, allowing cycles in the trait solver" + ), + rustc_attr!( + rustc_allow_incoherent_impl, AttributeType::Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::No, + "`#[rustc_allow_incoherent_impl]` has to be added to all impl items of an incoherent inherent impl" + ), + rustc_attr!( + rustc_preserve_ub_checks, AttributeType::CrateLevel, template!(Word), ErrorFollowing, EncodeCrossCrate::No, + "`#![rustc_preserve_ub_checks]` prevents the designated crate from evaluating whether UB checks are enabled when optimizing MIR", + ), + rustc_attr!( + rustc_deny_explicit_impl, + AttributeType::Normal, + template!(Word), + ErrorFollowing, + EncodeCrossCrate::No, "`#[rustc_deny_explicit_impl]` enforces that a trait can have no user-provided impls" ), - rustc_attr!(rustc_dyn_incompatible_trait, + rustc_attr!( + rustc_dyn_incompatible_trait, + AttributeType::Normal, + template!(Word), + ErrorFollowing, + EncodeCrossCrate::No, "`#[rustc_dyn_incompatible_trait]` marks a trait as dyn-incompatible, \ even if it otherwise satisfies the requirements to be dyn-compatible." ), - rustc_attr!(rustc_has_incoherent_inherent_impls, "`#[rustc_has_incoherent_inherent_impls]` allows the addition of incoherent inherent impls for \ + rustc_attr!( + rustc_has_incoherent_inherent_impls, AttributeType::Normal, template!(Word), + ErrorFollowing, EncodeCrossCrate::Yes, + "`#[rustc_has_incoherent_inherent_impls]` allows the addition of incoherent inherent impls for \ the given type by annotating all impl items with `#[rustc_allow_incoherent_impl]`" ), - rustc_attr!(rustc_non_const_trait_method, "`#[rustc_non_const_trait_method]` should only used by the standard library to mark trait methods \ + rustc_attr!( + rustc_non_const_trait_method, AttributeType::Normal, template!(Word), + ErrorFollowing, EncodeCrossCrate::No, + "`#[rustc_non_const_trait_method]` should only used by the standard library to mark trait methods \ as non-const to allow large traits an easier transition to const" ), BuiltinAttribute { name: sym::rustc_diagnostic_item, + // FIXME: This can be `true` once we always use `tcx.is_diagnostic_item`. + encode_cross_crate: EncodeCrossCrate::Yes, + type_: Normal, safety: AttributeSafety::Normal, + template: template!(NameValueStr: "name"), + duplicates: ErrorFollowing, gate: Gated { feature: sym::rustc_attrs, message: "use of an internal attribute", @@ -717,87 +1342,236 @@ pub static BUILTIN_ATTRIBUTES: &[BuiltinAttribute] = &[ }, gated!( // Used in resolve: - prelude_import, "`#[prelude_import]` is for use by rustc only", + prelude_import, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::No, "`#[prelude_import]` is for use by rustc only", ), gated!( - rustc_paren_sugar,unboxed_closures, "unboxed_closures are still evolving", + rustc_paren_sugar, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, + unboxed_closures, "unboxed_closures are still evolving", ), - rustc_attr!(rustc_inherit_overflow_checks,"the `#[rustc_inherit_overflow_checks]` attribute is just used to control \ + rustc_attr!( + rustc_inherit_overflow_checks, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, + "the `#[rustc_inherit_overflow_checks]` attribute is just used to control \ overflow checking behavior of several functions in the standard library that are inlined \ - across crates" + across crates", ), - rustc_attr!(rustc_reservation_impl,"the `#[rustc_reservation_impl]` attribute is internally used \ + rustc_attr!( + rustc_reservation_impl, Normal, + template!(NameValueStr: "reservation message"), ErrorFollowing, EncodeCrossCrate::Yes, + "the `#[rustc_reservation_impl]` attribute is internally used \ for reserving `impl From for T` as part of the effort to stabilize `!`" ), - rustc_attr!(rustc_test_marker, "the `#[rustc_test_marker]` attribute is used internally to track tests"), - rustc_attr!(rustc_unsafe_specialization_marker, + rustc_attr!( + rustc_test_marker, Normal, template!(NameValueStr: "name"), WarnFollowing, + EncodeCrossCrate::No, "the `#[rustc_test_marker]` attribute is used internally to track tests", + ), + rustc_attr!( + rustc_unsafe_specialization_marker, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No, "the `#[rustc_unsafe_specialization_marker]` attribute is used to check specializations" ), - rustc_attr!(rustc_specialization_trait, + rustc_attr!( + rustc_specialization_trait, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No, "the `#[rustc_specialization_trait]` attribute is used to check specializations" ), - rustc_attr!(rustc_main,"the `#[rustc_main]` attribute is used internally to specify test entry point function"), - rustc_attr!(rustc_skip_during_method_dispatch, "the `#[rustc_skip_during_method_dispatch]` attribute is used to exclude a trait \ + rustc_attr!( + rustc_main, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::No, + "the `#[rustc_main]` attribute is used internally to specify test entry point function", + ), + rustc_attr!( + rustc_skip_during_method_dispatch, Normal, template!(List: &["array, boxed_slice"]), ErrorFollowing, + EncodeCrossCrate::No, + "the `#[rustc_skip_during_method_dispatch]` attribute is used to exclude a trait \ from method dispatch when the receiver is of the following type, for compatibility in \ editions < 2021 (array) or editions < 2024 (boxed_slice)" ), - rustc_attr!(rustc_must_implement_one_of,"the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \ + rustc_attr!( + rustc_must_implement_one_of, Normal, template!(List: &["function1, function2, ..."]), + ErrorFollowing, EncodeCrossCrate::No, + "the `#[rustc_must_implement_one_of]` attribute is used to change minimal complete \ definition of a trait. Its syntax and semantics are highly experimental and will be \ subject to change before stabilization", ), - rustc_attr!(rustc_doc_primitive,"the `#[rustc_doc_primitive]` attribute is used by the standard library \ + rustc_attr!( + rustc_doc_primitive, Normal, template!(NameValueStr: "primitive name"), ErrorFollowing, + EncodeCrossCrate::Yes, "the `#[rustc_doc_primitive]` attribute is used by the standard library \ to provide a way to generate documentation for primitive types", ), gated!( - rustc_intrinsic,intrinsics, - "the `#[rustc_intrinsic]` attribute is used to declare intrinsics as function items"), - rustc_attr!(rustc_no_mir_inline,"`#[rustc_no_mir_inline]` prevents the MIR inliner from inlining a function while not affecting codegen" + rustc_intrinsic, Normal, template!(Word), ErrorFollowing, EncodeCrossCrate::Yes, intrinsics, + "the `#[rustc_intrinsic]` attribute is used to declare intrinsics as function items", + ), + rustc_attr!( + rustc_no_mir_inline, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes, + "`#[rustc_no_mir_inline]` prevents the MIR inliner from inlining a function while not affecting codegen" + ), + rustc_attr!( + rustc_force_inline, Normal, template!(Word, NameValueStr: "reason"), WarnFollowing, EncodeCrossCrate::Yes, + "`#[rustc_force_inline]` forces a free function to be inlined" + ), + rustc_attr!( + rustc_scalable_vector, Normal, template!(List: &["count"]), WarnFollowing, EncodeCrossCrate::Yes, + "`#[rustc_scalable_vector]` defines a scalable vector type" ), - rustc_attr!(rustc_force_inline,"`#[rustc_force_inline]` forces a free function to be inlined"), - rustc_attr!(rustc_scalable_vector,"`#[rustc_scalable_vector]` defines a scalable vector type"), // ========================================================================== // Internal attributes, Testing: // ========================================================================== - rustc_attr!(TEST, rustc_effective_visibility), - rustc_attr!(TEST, rustc_dump_inferred_outlives), - rustc_attr!(TEST, rustc_capture_analysis), - rustc_attr!(TEST, rustc_insignificant_dtor), - rustc_attr!(TEST, rustc_no_implicit_bounds), - rustc_attr!(TEST, rustc_strict_coherence), - rustc_attr!(TEST, rustc_dump_variances), - rustc_attr!(TEST, rustc_dump_variances_of_opaques), - rustc_attr!(TEST, rustc_hidden_type_of_opaques), - rustc_attr!(TEST, rustc_layout), - rustc_attr!(TEST, rustc_abi), - rustc_attr!(TEST, rustc_regions), - rustc_attr!(TEST, rustc_delayed_bug_from_inside_query), - rustc_attr!(TEST, rustc_dump_user_args), - rustc_attr!(TEST, rustc_evaluate_where_clauses), - rustc_attr!(TEST, rustc_if_this_changed), - rustc_attr!(TEST, rustc_then_this_would_need), - rustc_attr!(TEST, rustc_clean), - rustc_attr!(TEST, rustc_partition_reused), - rustc_attr!(TEST, rustc_partition_codegened), - rustc_attr!(TEST, rustc_expected_cgu_reuse), - rustc_attr!(TEST, rustc_symbol_name), - rustc_attr!(TEST, rustc_def_path), - rustc_attr!(TEST, rustc_mir), - gated!(custom_mir,"the `#[custom_mir]` attribute is just used for the Rust test suite"), - rustc_attr!(TEST, rustc_dump_item_bounds), - rustc_attr!(TEST, rustc_dump_predicates), - rustc_attr!(TEST, rustc_dump_def_parents), - rustc_attr!(TEST, rustc_dump_object_lifetime_defaults), - rustc_attr!(TEST, rustc_dump_vtable), - rustc_attr!(TEST, rustc_dummy), - rustc_attr!(TEST, pattern_complexity_limit), + rustc_attr!(TEST, rustc_effective_visibility, Normal, template!(Word), WarnFollowing, EncodeCrossCrate::Yes), + rustc_attr!( + TEST, rustc_dump_inferred_outlives, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_capture_analysis, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_insignificant_dtor, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::Yes + ), + rustc_attr!( + TEST, rustc_no_implicit_bounds, CrateLevel, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_strict_coherence, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::Yes + ), + rustc_attr!( + TEST, rustc_dump_variances, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_dump_variances_of_opaques, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_hidden_type_of_opaques, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_layout, Normal, template!(List: &["field1, field2, ..."]), + WarnFollowing, EncodeCrossCrate::Yes + ), + rustc_attr!( + TEST, rustc_abi, Normal, template!(List: &["field1, field2, ..."]), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_regions, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_delayed_bug_from_inside_query, Normal, + template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_dump_user_args, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_evaluate_where_clauses, Normal, template!(Word), WarnFollowing, + EncodeCrossCrate::Yes + ), + rustc_attr!( + TEST, rustc_if_this_changed, Normal, template!(Word, List: &["DepNode"]), DuplicatesOk, + EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_then_this_would_need, Normal, template!(List: &["DepNode"]), DuplicatesOk, + EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_clean, Normal, + template!(List: &[r#"cfg = "...", /*opt*/ label = "...", /*opt*/ except = "...""#]), + DuplicatesOk, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_partition_reused, Normal, + template!(List: &[r#"cfg = "...", module = "...""#]), DuplicatesOk, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_partition_codegened, Normal, + template!(List: &[r#"cfg = "...", module = "...""#]), DuplicatesOk, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_expected_cgu_reuse, Normal, + template!(List: &[r#"cfg = "...", module = "...", kind = "...""#]), DuplicatesOk, + EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_symbol_name, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_def_path, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_mir, Normal, template!(List: &["arg1, arg2, ..."]), + DuplicatesOk, EncodeCrossCrate::Yes + ), + gated!( + custom_mir, Normal, template!(List: &[r#"dialect = "...", phase = "...""#]), + ErrorFollowing, EncodeCrossCrate::No, + "the `#[custom_mir]` attribute is just used for the Rust test suite", + ), + rustc_attr!( + TEST, rustc_dump_item_bounds, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_dump_predicates, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_dump_def_parents, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_dump_object_lifetime_defaults, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_dump_vtable, Normal, template!(Word), + WarnFollowing, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, rustc_dummy, Normal, template!(Word /* doesn't matter*/), + DuplicatesOk, EncodeCrossCrate::No + ), + rustc_attr!( + TEST, pattern_complexity_limit, CrateLevel, template!(NameValueStr: "N"), + ErrorFollowing, EncodeCrossCrate::No, + ), ]; pub fn is_builtin_attr_name(name: Symbol) -> bool { BUILTIN_ATTRIBUTE_MAP.get(&name).is_some() } +/// Whether this builtin attribute is encoded cross crate. +/// This means it can be used cross crate. +pub fn encode_cross_crate(name: Symbol) -> bool { + if let Some(attr) = BUILTIN_ATTRIBUTE_MAP.get(&name) { + attr.encode_cross_crate == EncodeCrossCrate::Yes + } else { + true + } +} + +pub fn is_valid_for_get_attr(name: Symbol) -> bool { + BUILTIN_ATTRIBUTE_MAP.get(&name).is_some_and(|attr| match attr.duplicates { + WarnFollowing | ErrorFollowing | ErrorPreceding | FutureWarnFollowing + | FutureWarnPreceding => true, + DuplicatesOk | WarnFollowingWordOnly => false, + }) +} + pub static BUILTIN_ATTRIBUTE_MAP: LazyLock> = LazyLock::new(|| { let mut map = FxHashMap::default(); diff --git a/compiler/rustc_feature/src/lib.rs b/compiler/rustc_feature/src/lib.rs index 40a637bfa0b8f..9d046bdef1cf3 100644 --- a/compiler/rustc_feature/src/lib.rs +++ b/compiler/rustc_feature/src/lib.rs @@ -129,9 +129,10 @@ pub fn find_feature_issue(feature: Symbol, issue: GateIssue) -> Option) -> bool && p.encode_cross_crate() == EncodeCrossCrate::No { // Attributes not marked encode-cross-crate don't need to be encoded for downstream crates. + } else if let Some(name) = attr.name() + && !rustc_feature::encode_cross_crate(name) + { + // Attributes not marked encode-cross-crate don't need to be encoded for downstream crates. } else if let hir::Attribute::Parsed(AttributeKind::DocComment { .. }) = attr { // We keep all doc comments reachable to rustdoc because they might be "imported" into // downstream crates if they use `#[doc(inline)]` to copy an item's documentation into diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 36a474688f07a..dbf7d643a42ce 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -1725,8 +1725,13 @@ impl<'tcx> TyCtxt<'tcx> { #[deprecated = "Though there are valid usecases for this method, especially when your attribute is not a parsed attribute, usually you want to call rustc_hir::find_attr! instead."] pub fn get_attr(self, did: impl Into, attr: Symbol) -> Option<&'tcx hir::Attribute> { - #[allow(deprecated)] - self.get_attrs(did, attr).next() + if cfg!(debug_assertions) && !rustc_feature::is_valid_for_get_attr(attr) { + let did: DefId = did.into(); + bug!("get_attr: unexpected called with DefId `{:?}`, attr `{:?}`", did, attr); + } else { + #[allow(deprecated)] + self.get_attrs(did, attr).next() + } } /// Determines whether an item is annotated with an attribute. diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 3e3810b7f1257..12b583d8fee15 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -6,15 +6,17 @@ //! item. use std::cell::Cell; +use std::collections::hash_map::Entry; use std::slice; use rustc_abi::ExternAbi; use rustc_ast::ast; use rustc_attr_parsing::{AttributeParser, Late}; +use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::thin_vec::ThinVec; use rustc_data_structures::unord::UnordMap; use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, msg}; -use rustc_feature::BUILTIN_ATTRIBUTE_MAP; +use rustc_feature::{AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute}; use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::{ AttributeKind, CrateType, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, @@ -135,6 +137,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { target: Target, item: Option>, ) { + let mut seen = FxHashMap::default(); let attrs = self.tcx.hir_attrs(hir_id); for attr in attrs { match attr { @@ -401,6 +404,64 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } + if hir_id != CRATE_HIR_ID { + match attr { + Attribute::Parsed(_) => { /* Already validated. */ } + Attribute::Unparsed(attr) => { + // FIXME(jdonszelmann): remove once all crate-level attrs are parsed and caught by + // the above + if let Some(BuiltinAttribute { type_: AttributeType::CrateLevel, .. }) = + attr.path + .segments + .first() + .and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name)) + { + match attr.style { + ast::AttrStyle::Outer => { + let attr_span = attr.span; + let bang_position = self + .tcx + .sess + .source_map() + .span_until_char(attr_span, '[') + .shrink_to_hi(); + + self.tcx.emit_node_span_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::OuterCrateLevelAttr { + suggestion: errors::OuterCrateLevelAttrSuggestion { + bang_position, + }, + }, + ) + } + ast::AttrStyle::Inner => self.tcx.emit_node_span_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr.span, + errors::InnerCrateLevelAttr, + ), + } + } + } + } + } + + if let Attribute::Unparsed(unparsed_attr) = attr + && let Some(BuiltinAttribute { duplicates, .. }) = + attr.name().and_then(|name| BUILTIN_ATTRIBUTE_MAP.get(&name)) + { + check_duplicates( + self.tcx, + unparsed_attr.span, + attr, + hir_id, + *duplicates, + &mut seen, + ); + } self.check_unused_attribute(hir_id, attr) } @@ -1933,6 +1994,67 @@ pub(crate) fn provide(providers: &mut Providers) { *providers = Providers { check_mod_attrs, ..*providers }; } +// FIXME(jdonszelmann): remove, check during parsing +fn check_duplicates( + tcx: TyCtxt<'_>, + attr_span: Span, + attr: &Attribute, + hir_id: HirId, + duplicates: AttributeDuplicates, + seen: &mut FxHashMap, +) { + use AttributeDuplicates::*; + if matches!(duplicates, WarnFollowingWordOnly) && !attr.is_word() { + return; + } + let attr_name = attr.name().unwrap(); + match duplicates { + DuplicatesOk => {} + WarnFollowing | FutureWarnFollowing | WarnFollowingWordOnly | FutureWarnPreceding => { + match seen.entry(attr_name) { + Entry::Occupied(mut entry) => { + let (this, other) = if matches!(duplicates, FutureWarnPreceding) { + let to_remove = entry.insert(attr_span); + (to_remove, attr_span) + } else { + (attr_span, *entry.get()) + }; + tcx.emit_node_span_lint( + UNUSED_ATTRIBUTES, + hir_id, + this, + errors::UnusedDuplicate { + this, + other, + warning: matches!( + duplicates, + FutureWarnFollowing | FutureWarnPreceding + ), + }, + ); + } + Entry::Vacant(entry) => { + entry.insert(attr_span); + } + } + } + ErrorFollowing | ErrorPreceding => match seen.entry(attr_name) { + Entry::Occupied(mut entry) => { + let (this, other) = if matches!(duplicates, ErrorPreceding) { + let to_remove = entry.insert(attr_span); + (to_remove, attr_span) + } else { + (attr_span, *entry.get()) + }; + tcx.dcx().emit_err(errors::UnusedMultiple { this, other, name: attr_name }); + } + Entry::Vacant(entry) => { + entry.insert(attr_span); + } + }, + } +} + fn doc_fake_variadic_is_allowed_self_ty(self_ty: &hir::Ty<'_>) -> bool { matches!(&self_ty.kind, hir::TyKind::Tup([_])) || if let hir::TyKind::FnPtr(fn_ptr_ty) = &self_ty.kind { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 46b96ff1da353..628d0b0c961a1 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -326,6 +326,30 @@ pub(crate) struct InvalidMayDangle { pub attr_span: Span, } +#[derive(Diagnostic)] +#[diag("unused attribute")] +pub(crate) struct UnusedDuplicate { + #[suggestion("remove this attribute", code = "", applicability = "machine-applicable")] + pub this: Span, + #[note("attribute also specified here")] + pub other: Span, + #[warning( + "this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!" + )] + pub warning: bool, +} + +#[derive(Diagnostic)] +#[diag("multiple `{$name}` attributes")] +pub(crate) struct UnusedMultiple { + #[primary_span] + #[suggestion("remove this attribute", code = "", applicability = "machine-applicable")] + pub this: Span, + #[note("attribute also specified here")] + pub other: Span, + pub name: Symbol, +} + #[derive(Diagnostic)] #[diag("this `#[deprecated]` annotation has no effect")] pub(crate) struct DeprecatedAnnotationHasNoEffect { From 65745a1b95ece068a127b2cd009d2c0bd4a9f65d Mon Sep 17 00:00:00 2001 From: Jonathan Brouwer Date: Thu, 9 Apr 2026 18:27:19 +0200 Subject: [PATCH 2/5] Revert #152369 because of multiple regressions The regressions are documented in the PR comments. This reverts commit 2972b5e, reversing changes made to f908263. --- Cargo.lock | 1 + compiler/rustc_ast_lowering/src/lib.rs | 2 +- .../src/attributes/codegen_attrs.rs | 7 +- .../rustc_attr_parsing/src/attributes/lint.rs | 371 --------------- .../rustc_attr_parsing/src/attributes/mod.rs | 1 - compiler/rustc_attr_parsing/src/context.rs | 17 +- compiler/rustc_attr_parsing/src/interface.rs | 76 +-- .../src/session_diagnostics.rs | 21 - .../src/error_codes/E0452.md | 3 +- compiler/rustc_expand/src/base.rs | 3 +- compiler/rustc_expand/src/expand.rs | 2 +- .../rustc_hir/src/attrs/data_structures.rs | 146 +----- .../rustc_hir/src/attrs/encode_cross_crate.rs | 1 - .../rustc_hir/src/attrs/pretty_printing.rs | 6 +- compiler/rustc_hir/src/hir.rs | 14 - compiler/rustc_hir_typeck/src/expr.rs | 36 +- compiler/rustc_interface/src/passes.rs | 3 +- compiler/rustc_lint/src/builtin.rs | 3 +- compiler/rustc_lint/src/context.rs | 71 ++- compiler/rustc_lint/src/early.rs | 214 +++------ compiler/rustc_lint/src/early/diagnostics.rs | 22 - compiler/rustc_lint/src/errors.rs | 30 ++ compiler/rustc_lint/src/expect.rs | 32 +- compiler/rustc_lint/src/levels.rs | 441 ++++++++++++------ compiler/rustc_lint/src/lib.rs | 6 +- compiler/rustc_lint/src/lints.rs | 32 +- compiler/rustc_lint_defs/Cargo.toml | 1 + compiler/rustc_lint_defs/src/lib.rs | 124 ++--- compiler/rustc_mir_build/src/builder/scope.rs | 11 +- compiler/rustc_passes/src/check_attr.rs | 164 ++++--- compiler/rustc_passes/src/errors.rs | 2 + compiler/rustc_resolve/src/def_collector.rs | 2 +- compiler/rustc_session/src/session.rs | 11 +- .../rustc_span/src/caching_source_map_view.rs | 2 +- .../clippy/clippy_lints/src/attrs/mod.rs | 2 +- .../src/attrs/unnecessary_clippy_cfg.rs | 7 +- .../src/attrs/useless_attribute.rs | 2 +- .../clippy/clippy_lints/src/attrs/utils.rs | 6 +- .../clippy/clippy_lints/src/collapsible_if.rs | 38 +- .../src/returns/needless_return.rs | 30 +- .../tests/ui/expect_tool_lint_rfc_2383.rs | 2 + .../tests/ui/expect_tool_lint_rfc_2383.stderr | 18 +- .../tests/ui/unknown_clippy_lints.stderr | 30 +- tests/pretty/delegation-inherit-attributes.pp | 11 +- tests/pretty/delegation-inline-attribute.pp | 11 +- tests/pretty/hir-delegation.pp | 11 +- tests/pretty/hir-lifetimes.pp | 14 +- tests/pretty/pin-ergonomics-hir.pp | 11 +- .../lints/renamed-lint-still-applies-2.rs | 12 - .../lints/renamed-lint-still-applies-2.stderr | 32 -- .../lints/renamed-lint-still-applies.rs | 5 +- .../lints/renamed-lint-still-applies.stderr | 36 +- ...-highlight-span-extra-arguments-147070.svg | 2 +- tests/ui/attributes/malformed-attrs.stderr | 170 ++++--- .../unsafe/proc-unsafe-attributes.rs | 9 +- .../unsafe/proc-unsafe-attributes.stderr | 62 +-- ...deduplicate-diagnostics.deduplicate.stderr | 29 +- .../deduplicate-diagnostics.duplicate.stderr | 35 +- .../deduplicate-diagnostics.rs | 4 +- tests/ui/error-codes/E0452.rs | 8 + tests/ui/error-codes/E0452.stderr | 49 ++ ...issue-43106-gating-of-builtin-attrs.stderr | 206 ++++---- ...between-expected-trait-and-found-trait.svg | 2 +- tests/ui/lint/empty-lint-attributes.stderr | 8 +- tests/ui/lint/inert-attr-macro.rs | 6 +- tests/ui/lint/inert-attr-macro.stderr | 9 +- tests/ui/lint/issue-97094.stderr | 36 +- tests/ui/lint/keyword-idents/multi-file.rs | 19 +- .../ui/lint/keyword-idents/multi-file.stderr | 54 --- tests/ui/lint/lint-malformed.rs | 8 +- tests/ui/lint/lint-malformed.stderr | 63 ++- tests/ui/lint/reasons-erroneous.rs | 34 +- tests/ui/lint/reasons-erroneous.stderr | 141 +----- tests/ui/lint/register-tool-lint.rs | 2 + tests/ui/lint/register-tool-lint.stderr | 11 +- .../ui/lint/renamed-lints-still-apply.stderr | 16 +- .../expect_lint_from_macro.rs | 4 +- .../expect_lint_from_macro.stderr | 9 +- .../expect_tool_lint_rfc_2383.rs | 2 + .../expect_tool_lint_rfc_2383.stderr | 10 +- .../expect_unfulfilled_expectation.rs | 7 + .../expect_unfulfilled_expectation.stderr | 23 +- .../lint-attribute-only-with-reason.stderr | 10 +- .../multiple_expect_attrs.rs | 1 + .../multiple_expect_attrs.stderr | 2 +- .../semicolon-in-expressions-from-macros.rs | 3 +- ...emicolon-in-expressions-from-macros.stderr | 9 +- tests/ui/lint/unused/empty-attributes.stderr | 48 +- tests/ui/parser/issues/issue-104620.rs | 4 +- tests/ui/proc-macro/cfg-eval.stderr | 2 +- tests/ui/tool-attributes/tool_lints.rs | 2 + tests/ui/tool-attributes/tool_lints.stderr | 20 +- .../tool-attributes/unknown-lint-tool-name.rs | 10 +- .../unknown-lint-tool-name.stderr | 40 +- tests/ui/unpretty/exhaustive.hir.stdout | 56 +-- ...ct-exprs-tuple-call-pretty-printing.stdout | 11 +- tests/ui/unpretty/unpretty-expr-fn-arg.stdout | 9 +- .../ui/where-clauses/unsupported_attribute.rs | 4 +- .../unsupported_attribute.stderr | 8 +- 99 files changed, 1455 insertions(+), 1978 deletions(-) delete mode 100644 compiler/rustc_attr_parsing/src/attributes/lint.rs delete mode 100644 tests/rustdoc-ui/lints/renamed-lint-still-applies-2.rs delete mode 100644 tests/rustdoc-ui/lints/renamed-lint-still-applies-2.stderr create mode 100644 tests/ui/error-codes/E0452.rs create mode 100644 tests/ui/error-codes/E0452.stderr delete mode 100644 tests/ui/lint/keyword-idents/multi-file.stderr diff --git a/Cargo.lock b/Cargo.lock index 686b98cf61281..d9f3e90d652e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4224,6 +4224,7 @@ dependencies = [ name = "rustc_lint_defs" version = "0.0.0" dependencies = [ + "rustc_ast", "rustc_data_structures", "rustc_error_messages", "rustc_hir_id", diff --git a/compiler/rustc_ast_lowering/src/lib.rs b/compiler/rustc_ast_lowering/src/lib.rs index 0561490344d21..6d9fe9870c42e 100644 --- a/compiler/rustc_ast_lowering/src/lib.rs +++ b/compiler/rustc_ast_lowering/src/lib.rs @@ -154,7 +154,7 @@ struct LoweringContext<'a, 'hir, R> { impl<'a, 'hir, R: ResolverAstLoweringExt<'hir>> LoweringContext<'a, 'hir, R> { fn new(tcx: TyCtxt<'hir>, resolver: &'a mut R) -> Self { - let registered_tools = tcx.registered_tools(()); + let registered_tools = tcx.registered_tools(()).iter().map(|x| x.name).collect(); Self { tcx, resolver, diff --git a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs index 93664aff49157..ff94cf50adf67 100644 --- a/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/codegen_attrs.rs @@ -295,15 +295,10 @@ impl AttributeParser for NakedParser { let span = self.span?; - let Some(tools) = cx.tools else { - unreachable!("tools required while parsing attributes"); - }; - - let tools = tools.iter().map(|tool| tool.name).collect::>(); // only if we found a naked attribute do we do the somewhat expensive check 'outer: for other_attr in cx.all_attrs { for allowed_attr in ALLOW_LIST { - if other_attr.segments().next().is_some_and(|i| tools.contains(&i.name)) { + if other_attr.segments().next().is_some_and(|i| cx.tools.contains(&i.name)) { // effectively skips the error message being emitted below // if it's a tool attribute continue 'outer; diff --git a/compiler/rustc_attr_parsing/src/attributes/lint.rs b/compiler/rustc_attr_parsing/src/attributes/lint.rs deleted file mode 100644 index f9c8ff6e68749..0000000000000 --- a/compiler/rustc_attr_parsing/src/attributes/lint.rs +++ /dev/null @@ -1,371 +0,0 @@ -use rustc_ast::LitKind; -use rustc_hir::HashIgnoredAttrId; -use rustc_hir::attrs::{LintAttribute, LintAttributeKind, LintInstance}; -use rustc_hir::lints::AttributeLintKind; -use rustc_hir::target::GenericParamKind; -use rustc_session::DynLintStore; -use rustc_session::lint::builtin::{RENAMED_AND_REMOVED_LINTS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES}; -use rustc_session::lint::{CheckLintNameResult, LintId}; - -use super::prelude::*; -use crate::attributes::AcceptFn; -use crate::session_diagnostics::UnknownToolInScopedLint; - -pub(crate) trait Lint { - const KIND: LintAttributeKind; - const ATTR_SYMBOL: Symbol = Self::KIND.symbol(); -} - -pub(crate) struct Allow; - -impl Lint for Allow { - const KIND: LintAttributeKind = LintAttributeKind::Allow; -} -pub(crate) struct Deny; - -impl Lint for Deny { - const KIND: LintAttributeKind = LintAttributeKind::Deny; -} -pub(crate) struct Expect; - -impl Lint for Expect { - const KIND: LintAttributeKind = LintAttributeKind::Expect; -} -pub(crate) struct Forbid; - -impl Lint for Forbid { - const KIND: LintAttributeKind = LintAttributeKind::Forbid; -} -pub(crate) struct Warn; - -impl Lint for Warn { - const KIND: LintAttributeKind = LintAttributeKind::Warn; -} - -#[derive(Default)] -pub(crate) struct LintParser { - lint_attrs: ThinVec, -} - -trait Mapping { - const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn); -} -impl Mapping for T { - const MAPPING: (&'static [Symbol], AttributeTemplate, AcceptFn) = ( - &[T::ATTR_SYMBOL], - template!( - List: &["lint1", "lint1, lint2, ...", r#"lint1, lint2, lint3, reason = "...""#], - "https://doc.rust-lang.org/reference/attributes/diagnostics.html#lint-check-attributes" - ), - |this, cx, args| { - if let Some(lint_attr) = validate_lint_attr::(cx, args) { - this.lint_attrs.push(lint_attr); - } - }, - ); -} - -impl AttributeParser for LintParser { - const ATTRIBUTES: AcceptMapping = - &[Allow::MAPPING, Deny::MAPPING, Expect::MAPPING, Forbid::MAPPING, Warn::MAPPING]; - - const ALLOWED_TARGETS: AllowedTargets = { - use super::prelude::{Allow, Warn}; - AllowedTargets::AllowList(&[ - Allow(Target::ExternCrate), - Allow(Target::Use), - Allow(Target::Static), - Allow(Target::Const), - Allow(Target::Fn), - Allow(Target::Closure), - Allow(Target::Mod), - Allow(Target::ForeignMod), - Allow(Target::GlobalAsm), - Allow(Target::TyAlias), - Allow(Target::Enum), - Allow(Target::Variant), - Allow(Target::Struct), - Allow(Target::Field), - Allow(Target::Union), - Allow(Target::Trait), - Allow(Target::TraitAlias), - Allow(Target::Impl { of_trait: false }), - Allow(Target::Impl { of_trait: true }), - Allow(Target::Expression), - Allow(Target::Statement), - Allow(Target::Arm), - Allow(Target::AssocConst), - Allow(Target::Method(MethodKind::Inherent)), - Allow(Target::Method(MethodKind::Trait { body: false })), - Allow(Target::Method(MethodKind::Trait { body: true })), - Allow(Target::Method(MethodKind::TraitImpl)), - Allow(Target::AssocTy), - Allow(Target::ForeignFn), - Allow(Target::ForeignStatic), - Allow(Target::ForeignTy), - Allow(Target::MacroDef), - Allow(Target::Param), - Allow(Target::PatField), - Allow(Target::ExprField), - Allow(Target::Crate), - Allow(Target::Delegation { mac: false }), - Allow(Target::Delegation { mac: true }), - Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: false }), - Allow(Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: false }), - Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: false }), - Allow(Target::GenericParam { kind: GenericParamKind::Type, has_default: true }), - Allow(Target::GenericParam { kind: GenericParamKind::Lifetime, has_default: true }), - Allow(Target::GenericParam { kind: GenericParamKind::Const, has_default: true }), - Warn(Target::MacroCall), - ]) - }; - - fn finalize(mut self, _cx: &FinalizeContext<'_, '_, S>) -> Option { - if !self.lint_attrs.is_empty() { - // Sort to ensure correct order operations later - self.lint_attrs.sort_by(|a, b| a.attr_span.cmp(&b.attr_span)); - Some(AttributeKind::LintAttributes(self.lint_attrs)) - } else { - None - } - } -} - -#[inline(always)] -fn validate_lint_attr( - cx: &mut AcceptContext<'_, '_, S>, - args: &ArgParser, -) -> Option { - let Some(lint_store) = cx.sess.lint_store.as_ref().map(|store| store.to_owned()) else { - unreachable!("lint_store required while parsing attributes"); - }; - let lint_store = lint_store.as_ref(); - let Some(list) = args.list() else { - let span = cx.inner_span; - cx.adcx().expected_list(span, args); - return None; - }; - let mut list = list.mixed().peekable(); - - let mut skip_unused_check = false; - let mut errored = false; - let mut reason = None; - let mut lint_instances = ThinVec::new(); - let mut lint_index = 0; - let targeting_crate = matches!(cx.target, Target::Crate); - while let Some(item) = list.next() { - let Some(meta_item) = item.meta_item() else { - cx.adcx().expected_identifier(item.span()); - errored = true; - continue; - }; - - match meta_item.args() { - ArgParser::NameValue(nv_parser) if meta_item.path().word_is(sym::reason) => { - //FIXME replace this with duplicate check? - if list.peek().is_some() { - cx.adcx().expected_nv_as_last_argument(meta_item.span(), sym::reason); - errored = true; - continue; - } - - let val_lit = nv_parser.value_as_lit(); - let LitKind::Str(reason_sym, _) = val_lit.kind else { - cx.adcx().expected_string_literal(nv_parser.value_span, Some(val_lit)); - errored = true; - continue; - }; - reason = Some(reason_sym); - } - ArgParser::NameValue(_) => { - cx.adcx().expected_specific_argument(meta_item.span(), &[sym::reason]); - errored = true; - } - ArgParser::List(list) => { - cx.adcx().expected_no_args(list.span); - errored = true; - } - ArgParser::NoArgs => { - skip_unused_check = true; - let mut segments = meta_item.path().segments(); - - let Some(tool_or_name) = segments.next() else { - unreachable!("first segment should always exist"); - }; - - let rest = segments.collect::>(); - let (tool_name, tool_span, name): (Option, Option, _) = - if rest.is_empty() { - let name = tool_or_name.name; - (None, None, name.to_string()) - } else { - let tool = tool_or_name; - let name = rest - .into_iter() - .map(|ident| ident.to_string()) - .collect::>() - .join("::"); - (Some(tool.name), Some(tool.span), name) - }; - - let meta_item_span = meta_item.span(); - let original_name = Symbol::intern(&name); - let mut full_name = tool_name - .map(|tool| Symbol::intern(&format!("{tool}::{}", original_name))) - .unwrap_or(original_name); - - if let Some(ids) = check_lint( - cx, - lint_store, - original_name, - &mut full_name, - tool_name, - tool_span, - meta_item_span, - ) { - if !targeting_crate && ids.iter().any(|lint_id| lint_id.lint.crate_level_only) { - cx.emit_lint( - UNUSED_ATTRIBUTES, - AttributeLintKind::IgnoredUnlessCrateSpecified { - level: T::ATTR_SYMBOL, - name: original_name, - }, - meta_item_span, - ); - } - lint_instances.extend(ids.into_iter().map(|id| { - LintInstance::new(full_name, id.to_string(), meta_item_span, lint_index) - })); - } - lint_index += 1; - } - } - } - if !skip_unused_check && !errored && lint_instances.is_empty() { - let span = cx.attr_span; - cx.adcx().warn_empty_attribute(span); - } - - (!errored).then_some(LintAttribute { - reason, - lint_instances, - attr_span: cx.attr_span, - attr_style: cx.attr_style, - attr_id: HashIgnoredAttrId { attr_id: cx.attr_id }, - kind: T::KIND, - }) -} - -fn check_lint<'a, S: Stage>( - cx: &mut AcceptContext<'_, '_, S>, - lint_store: &'a dyn DynLintStore, - original_name: Symbol, - full_name: &mut Symbol, - tool_name: Option, - tool_span: Option, - span: Span, -) -> Option<&'a [LintId]> { - let Some(tools) = cx.tools else { - unreachable!("tools required while parsing attributes"); - }; - if tools.is_empty() { - unreachable!("tools should never be empty") - } - - match lint_store.check_lint_name(original_name.as_str(), tool_name, tools) { - CheckLintNameResult::Ok(ids) => Some(ids), - CheckLintNameResult::Tool(ids, new_lint_name) => { - let _name = match new_lint_name { - None => original_name, - Some(new_lint_name) => { - let new_lint_name = Symbol::intern(&new_lint_name); - cx.emit_lint( - RENAMED_AND_REMOVED_LINTS, - AttributeLintKind::DeprecatedLintName { - name: *full_name, - suggestion: span, - replace: new_lint_name, - }, - span, - ); - new_lint_name - } - }; - Some(ids) - } - - CheckLintNameResult::MissingTool => { - // If `MissingTool` is returned, then either the lint does not - // exist in the tool or the code was not compiled with the tool and - // therefore the lint was never added to the `LintStore`. To detect - // this is the responsibility of the lint tool. - None - } - - CheckLintNameResult::NoTool => { - cx.emit_err(UnknownToolInScopedLint { - span: tool_span, - tool_name: tool_name.unwrap(), - full_lint_name: *full_name, - is_nightly_build: cx.sess.is_nightly_build(), - }); - None - } - - CheckLintNameResult::Renamed(replace) => { - cx.emit_lint( - RENAMED_AND_REMOVED_LINTS, - AttributeLintKind::RenamedLint { name: *full_name, replace, suggestion: span }, - span, - ); - - // Since it was renamed, and we have emitted the warning - // we replace the "full_name", to ensure we don't get notes with: - // `#[allow(NEW_NAME)]` implied by `#[allow(OLD_NAME)]` - // Other lints still have access to the original name as the user wrote it, - // through `original_name` - *full_name = replace; - - // If this lint was renamed, apply the new lint instead of ignoring the - // attribute. Ignore any errors or warnings that happen because the new - // name is inaccurate. - // NOTE: `new_name` already includes the tool name, so we don't - // have to add it again. - match lint_store.check_lint_name(replace.as_str(), None, tools) { - CheckLintNameResult::Ok(ids) => Some(ids), - _ => panic!("renamed lint does not exist: {replace}"), - } - } - - CheckLintNameResult::RenamedToolLint(new_name) => { - cx.emit_lint( - RENAMED_AND_REMOVED_LINTS, - AttributeLintKind::RenamedLint { - name: *full_name, - replace: new_name, - suggestion: span, - }, - span, - ); - None - } - - CheckLintNameResult::Removed(reason) => { - cx.emit_lint( - RENAMED_AND_REMOVED_LINTS, - AttributeLintKind::RemovedLint { name: *full_name, reason }, - span, - ); - None - } - - CheckLintNameResult::NoLint(suggestion) => { - cx.emit_lint( - UNKNOWN_LINTS, - AttributeLintKind::UnknownLint { name: *full_name, suggestion, span }, - span, - ); - None - } - } -} diff --git a/compiler/rustc_attr_parsing/src/attributes/mod.rs b/compiler/rustc_attr_parsing/src/attributes/mod.rs index 66f452040954b..d7f64ff2319a9 100644 --- a/compiler/rustc_attr_parsing/src/attributes/mod.rs +++ b/compiler/rustc_attr_parsing/src/attributes/mod.rs @@ -46,7 +46,6 @@ pub(crate) mod dummy; pub(crate) mod inline; pub(crate) mod instruction_set; pub(crate) mod link_attrs; -pub(crate) mod lint; pub(crate) mod lint_helpers; pub(crate) mod loop_match; pub(crate) mod macro_attrs; diff --git a/compiler/rustc_attr_parsing/src/context.rs b/compiler/rustc_attr_parsing/src/context.rs index 0be1b9b45b220..6ab3f98e2015c 100644 --- a/compiler/rustc_attr_parsing/src/context.rs +++ b/compiler/rustc_attr_parsing/src/context.rs @@ -15,7 +15,7 @@ use rustc_hir::{AttrPath, HirId}; use rustc_parse::parser::Recovery; use rustc_session::Session; use rustc_session::lint::{Lint, LintId}; -use rustc_span::{AttrId, ErrorGuaranteed, Span, Symbol}; +use rustc_span::{ErrorGuaranteed, Span, Symbol}; use crate::AttributeParser; // Glob imports to avoid big, bitrotty import lists @@ -37,7 +37,6 @@ use crate::attributes::dummy::*; use crate::attributes::inline::*; use crate::attributes::instruction_set::*; use crate::attributes::link_attrs::*; -use crate::attributes::lint::*; use crate::attributes::lint_helpers::*; use crate::attributes::loop_match::*; use crate::attributes::macro_attrs::*; @@ -150,7 +149,6 @@ attribute_parsers!( ConfusablesParser, ConstStabilityParser, DocParser, - LintParser, MacroUseParser, NakedParser, OnConstParser, @@ -447,8 +445,6 @@ pub struct AcceptContext<'f, 'sess, S: Stage> { /// The name of the attribute we're currently accepting. pub(crate) attr_path: AttrPath, - - pub(crate) attr_id: AttrId, } impl<'f, 'sess: 'f, S: Stage> SharedContext<'f, 'sess, S> { @@ -808,17 +804,6 @@ where ) } - pub(crate) fn expected_nv_as_last_argument( - &mut self, - span: Span, - name_value_key: Symbol, - ) -> ErrorGuaranteed { - self.emit_parse_error( - span, - AttributeParseErrorReason::ExpectedNameValueAsLastArgument { span, name_value_key }, - ) - } - pub(crate) fn warn_empty_attribute(&mut self, span: Span) { let attr_path = self.attr_path.clone().to_string(); let valid_without_list = self.template.word; diff --git a/compiler/rustc_attr_parsing/src/interface.rs b/compiler/rustc_attr_parsing/src/interface.rs index 902f2419c46d5..f66d6dd3f4c9c 100644 --- a/compiler/rustc_attr_parsing/src/interface.rs +++ b/compiler/rustc_attr_parsing/src/interface.rs @@ -3,7 +3,6 @@ use std::convert::identity; use rustc_ast as ast; use rustc_ast::token::DocFragmentKind; use rustc_ast::{AttrItemKind, AttrStyle, NodeId, Safety}; -use rustc_data_structures::fx::FxIndexSet; use rustc_errors::DiagCtxtHandle; use rustc_feature::{AttributeTemplate, Features}; use rustc_hir::attrs::AttributeKind; @@ -11,7 +10,7 @@ use rustc_hir::lints::AttributeLintKind; use rustc_hir::{AttrArgs, AttrItem, AttrPath, Attribute, HashIgnoredAttrId, Target}; use rustc_session::Session; use rustc_session::lint::LintId; -use rustc_span::{DUMMY_SP, Ident, Span, Symbol, sym}; +use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use crate::context::{AcceptContext, FinalizeContext, FinalizeFn, SharedContext, Stage}; use crate::early_parsed::{EARLY_PARSED_ATTRIBUTES, EarlyParsedState}; @@ -22,7 +21,7 @@ use crate::{Early, Late, OmitDoc, ShouldEmit}; /// Context created once, for example as part of the ast lowering /// context, through which all attributes can be lowered. pub struct AttributeParser<'sess, S: Stage = Late> { - pub(crate) tools: Option<&'sess FxIndexSet>, + pub(crate) tools: Vec, pub(crate) features: Option<&'sess Features>, pub(crate) sess: &'sess Session, pub(crate) stage: S, @@ -48,8 +47,6 @@ impl<'sess> AttributeParser<'sess, Early> { /// No diagnostics will be emitted when parsing limited. Lints are not emitted at all, while /// errors will be emitted as a delayed bugs. in other words, we *expect* attributes parsed /// with `parse_limited` to be reparsed later during ast lowering where we *do* emit the errors - /// - /// Due to this function not taking in RegisteredTools (`FxIndexSet`), *do not* use this for parsing any lint attributes pub fn parse_limited( sess: &'sess Session, attrs: &[ast::Attribute], @@ -72,8 +69,6 @@ impl<'sess> AttributeParser<'sess, Early> { /// This does the same as `parse_limited`, except it has a `should_emit` parameter which allows it to emit errors. /// Usually you want `parse_limited`, which emits no errors. - /// - /// Due to this function not taking in RegisteredTools (`FxIndexSet`), *do not* use this for parsing any lint attributes pub fn parse_limited_should_emit( sess: &'sess Session, attrs: &[ast::Attribute], @@ -93,7 +88,6 @@ impl<'sess> AttributeParser<'sess, Early> { target_node_id, features, should_emit, - None, ); assert!(parsed.len() <= 1); parsed.pop() @@ -106,18 +100,18 @@ impl<'sess> AttributeParser<'sess, Early> { /// `rustc_ast_lowering`. Some attributes require access to features to parse, which would /// crash if you tried to do so through [`parse_limited_all`](Self::parse_limited_all). /// Therefore, if `parse_only` is None, then features *must* be provided. - pub fn parse_limited_all<'a>( + pub fn parse_limited_all( sess: &'sess Session, - attrs: impl IntoIterator, + attrs: &[ast::Attribute], parse_only: Option, target: Target, target_span: Span, target_node_id: NodeId, features: Option<&'sess Features>, emit_errors: ShouldEmit, - tools: Option<&'sess FxIndexSet>, ) -> Vec { - let mut p = Self { features, tools, parse_only, sess, stage: Early { emit_errors } }; + let mut p = + Self { features, tools: Vec::new(), parse_only, sess, stage: Early { emit_errors } }; p.parse_attribute_list( attrs, target_span, @@ -128,32 +122,6 @@ impl<'sess> AttributeParser<'sess, Early> { ) } - /// This method provides the same functionality as [`parse_limited_all`](Self::parse_limited_all) except filtered, - /// making sure that only allow-listed symbols are parsed - pub fn parse_limited_all_filtered<'a>( - sess: &'sess Session, - attrs: impl IntoIterator, - filter: &[Symbol], - target: Target, - target_span: Span, - target_node_id: NodeId, - features: Option<&'sess Features>, - emit_errors: ShouldEmit, - tools: &'sess FxIndexSet, - ) -> Vec { - Self::parse_limited_all( - sess, - attrs.into_iter().filter(|attr| attr.has_any_name(filter)), - None, - target, - target_span, - target_node_id, - features, - emit_errors, - Some(tools), - ) - } - /// This method parses a single attribute, using `parse_fn`. /// This is useful if you already know what exact attribute this is, and want to parse it. pub fn parse_single( @@ -220,15 +188,19 @@ impl<'sess> AttributeParser<'sess, Early> { parse_fn: fn(cx: &mut AcceptContext<'_, '_, Early>, item: &I) -> T, template: &AttributeTemplate, ) -> T { - let mut parser = - Self { features, tools: None, parse_only: None, sess, stage: Early { emit_errors } }; + let mut parser = Self { + features, + tools: Vec::new(), + parse_only: None, + sess, + stage: Early { emit_errors }, + }; let mut emit_lint = |lint_id: LintId, span: Span, kind: AttributeLintKind| { sess.psess.buffer_lint(lint_id.lint, span, target_node_id, kind) }; if let Some(safety) = attr_safety { parser.check_attribute_safety(&attr_path, inner_span, safety, &mut emit_lint) } - let attr_id = sess.psess.attr_id_generator.mk_attr_id(); let mut cx: AcceptContext<'_, 'sess, Early> = AcceptContext { shared: SharedContext { cx: &mut parser, @@ -242,7 +214,6 @@ impl<'sess> AttributeParser<'sess, Early> { parsed_description, template, attr_path, - attr_id, }; parse_fn(&mut cx, args) } @@ -252,10 +223,10 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { pub fn new( sess: &'sess Session, features: &'sess Features, - tools: &'sess FxIndexSet, + tools: Vec, stage: S, ) -> Self { - Self { features: Some(features), tools: Some(tools), parse_only: None, sess, stage } + Self { features: Some(features), tools, parse_only: None, sess, stage } } pub(crate) fn sess(&self) -> &'sess Session { @@ -278,9 +249,9 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { /// /// `target_span` is the span of the thing this list of attributes is applied to, /// and when `omit_doc` is set, doc attributes are filtered out. - pub fn parse_attribute_list<'a>( + pub fn parse_attribute_list( &mut self, - attrs: impl IntoIterator, + attrs: &[ast::Attribute], target_span: Span, target: Target, omit_doc: OmitDoc, @@ -296,9 +267,9 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { let mut attr_paths: Vec> = Vec::new(); let mut early_parsed_state = EarlyParsedState::default(); - let mut finalizers: Vec<&FinalizeFn> = Vec::new(); + let mut finalizers: Vec<&FinalizeFn> = Vec::with_capacity(attrs.len()); - for attr in attrs.into_iter() { + for attr in attrs { // If we're only looking for a single attribute, skip all the ones we don't care about. if let Some(expected) = self.parse_only { if !attr.has_name(expected) { @@ -408,7 +379,6 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { parsed_description: ParsedDescription::Attribute, template: &accept.template, attr_path: attr_path.clone(), - attr_id: attr.id, }; (accept.accept_fn)(&mut cx, &args); @@ -435,9 +405,11 @@ impl<'sess, S: Stage> AttributeParser<'sess, S> { let attr = Attribute::Unparsed(Box::new(attr)); - if self - .tools - .is_some_and(|tools| tools.iter().any(|tool| tool.name == parts[0])) + if self.tools.contains(&parts[0]) + // FIXME: this can be removed once #152369 has been merged. + // https://github.com/rust-lang/rust/pull/152369 + || [sym::allow, sym::deny, sym::expect, sym::forbid, sym::warn] + .contains(&parts[0]) { attributes.push(attr); } else { diff --git a/compiler/rustc_attr_parsing/src/session_diagnostics.rs b/compiler/rustc_attr_parsing/src/session_diagnostics.rs index 5b198813dc432..1b3e5af5af0a9 100644 --- a/compiler/rustc_attr_parsing/src/session_diagnostics.rs +++ b/compiler/rustc_attr_parsing/src/session_diagnostics.rs @@ -575,10 +575,6 @@ pub(crate) enum AttributeParseErrorReason<'a> { list: bool, }, ExpectedIdentifier, - ExpectedNameValueAsLastArgument { - span: Span, - name_value_key: Symbol, - }, } /// A description of a thing that can be parsed using an attribute parser. @@ -839,12 +835,6 @@ impl<'a, G: EmissionGuarantee> Diagnostic<'a, G> for AttributeParseError<'_> { AttributeParseErrorReason::ExpectedIdentifier => { diag.span_label(self.span, "expected a valid identifier here"); } - AttributeParseErrorReason::ExpectedNameValueAsLastArgument { span, name_value_key } => { - diag.span_label( - *span, - format!("expected {name_value_key} = \"...\" to be the last argument"), - ); - } } if let Some(link) = self.template.docs { @@ -1138,14 +1128,3 @@ pub(crate) struct UnstableAttrForAlreadyStableFeature { #[label("the stability attribute annotates this item")] pub item_span: Span, } - -#[derive(Diagnostic)] -#[diag("unknown tool name `{$tool_name}` found in scoped lint: `{$full_lint_name}`", code = E0710)] -pub(crate) struct UnknownToolInScopedLint { - #[primary_span] - pub span: Option, - pub tool_name: Symbol, - pub full_lint_name: Symbol, - #[help("add `#![register_tool({$tool_name})]` to the crate root")] - pub is_nightly_build: bool, -} diff --git a/compiler/rustc_error_codes/src/error_codes/E0452.md b/compiler/rustc_error_codes/src/error_codes/E0452.md index a2471ec78eed5..429813a7cdd4e 100644 --- a/compiler/rustc_error_codes/src/error_codes/E0452.md +++ b/compiler/rustc_error_codes/src/error_codes/E0452.md @@ -1,9 +1,8 @@ -#### Note: this error code is no longer emitted by the compiler An invalid lint attribute has been given. Erroneous code example: -```compile_fail +```compile_fail,E0452 #![allow(foo = "")] // error: malformed lint attribute ``` diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index 4f60ba2e9725c..7fd891395fa02 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -9,7 +9,7 @@ use std::sync::Arc; use rustc_ast::attr::MarkedAttrs; use rustc_ast::tokenstream::TokenStream; use rustc_ast::visit::{AssocCtxt, Visitor}; -use rustc_ast::{self as ast, AttrVec, HasAttrs, Item, NodeId, PatKind, Safety}; +use rustc_ast::{self as ast, AttrVec, Attribute, HasAttrs, Item, NodeId, PatKind, Safety}; use rustc_data_structures::fx::{FxHashMap, FxIndexMap}; use rustc_data_structures::sync; use rustc_errors::{BufferedEarlyLint, DiagCtxtHandle, ErrorGuaranteed, PResult}; @@ -1188,6 +1188,7 @@ pub trait LintStoreExpand { features: &Features, registered_tools: &RegisteredTools, node_id: NodeId, + attrs: &[Attribute], items: &[Box], name: Symbol, ); diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index 91d6611d719cb..d607a995e73b1 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1403,6 +1403,7 @@ impl InvocationCollectorNode for Box { ecx.ecfg.features, ecx.resolver.registered_tools(), ecx.current_expansion.lint_node_id, + &attrs, &items, ident.name, ); @@ -2257,7 +2258,6 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { self.cx.current_expansion.lint_node_id, Some(self.cx.ecfg.features), ShouldEmit::ErrorsAndLints { recovery: Recovery::Allowed }, - Some(self.cx.resolver.registered_tools()), ); let current_span = if let Some(sp) = span { sp.to(attr.span) } else { attr.span }; diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index f18d5a1f190a2..a18ddff947099 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -15,16 +15,14 @@ use rustc_hir::LangItem; use rustc_macros::{Decodable, Encodable, HashStable_Generic, PrintAttribute}; use rustc_span::def_id::DefId; use rustc_span::hygiene::Transparency; -use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol, sym}; +use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; pub use rustc_target::spec::SanitizerSet; use thin_vec::ThinVec; use crate::attrs::diagnostic::*; use crate::attrs::pretty_printing::PrintAttribute; use crate::limit::Limit; -use crate::{ - DefaultBodyStability, HashIgnoredAttrId, PartialConstStability, RustcVersion, Stability, -}; +use crate::{DefaultBodyStability, PartialConstStability, RustcVersion, Stability}; #[derive(Copy, Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] pub enum EiiImplResolution { @@ -896,143 +894,6 @@ impl fmt::Display for AutoDiffItem { } } -#[derive(Clone, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute)] -pub struct LintAttribute { - /// See RFC #2383 - pub reason: Option, - pub kind: LintAttributeKind, - pub attr_style: AttrStyle, - pub attr_span: Span, - /// Needed by `LintExpectationId` to track fulfilled expectations - pub attr_id: HashIgnoredAttrId, - pub lint_instances: ThinVec, -} - -#[derive(Debug, Clone, Encodable, Decodable, HashStable_Generic)] -pub struct LintInstance { - /// The span of the `MetaItem` that produced this `LintInstance` - span: Span, - /// The fully resolved name of the lint - /// for renamed lints, this gets updated to match the new name - lint_name: Symbol, - /// The raw identifier for resolving this lint - /// if this is none, lint_name never diffed from the original - /// name after parsing, original_name.unwrap_or(self.lint_name) - original_name: Option, - /// Index of this lint, used to keep track of lint groups - lint_index: usize, - kind: LintAttrTool, -} - -impl fmt::Display for LintInstance { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.full_lint().fmt(f) - } -} - -impl LintInstance { - pub fn new( - original_name: Symbol, - long_lint_name: String, - span: Span, - lint_index: usize, - ) -> Self { - let original_name = (original_name.as_str() != long_lint_name).then_some(original_name); - let mut tool_name = None; - - let lint_name = match long_lint_name.split_once("::") { - Some((new_tool_name, lint_name)) => { - tool_name = Some(Symbol::intern(new_tool_name)); - Symbol::intern(lint_name) - } - None => Symbol::intern(&long_lint_name), - }; - let kind = match tool_name { - Some(tool_name) => { - let full_lint = Symbol::intern(&format!("{tool_name}::{lint_name}",)); - LintAttrTool::Present { tool_name, full_lint } - } - None => LintAttrTool::NoTool, - }; - - Self { original_name, span, lint_index, lint_name, kind } - } - - pub fn full_lint(&self) -> Symbol { - match self.kind { - LintAttrTool::Present { full_lint, .. } => full_lint, - LintAttrTool::NoTool => self.lint_name, - } - } - - pub fn span(&self) -> Span { - self.span - } - - pub fn lint_index(&self) -> usize { - self.lint_index - } - - pub fn lint_name(&self) -> Symbol { - self.lint_name - } - - pub fn original_name_without_tool(&self) -> Symbol { - let full_original_lint_name = self.original_lint_name(); - match self.kind { - LintAttrTool::Present { tool_name, .. } => Symbol::intern( - full_original_lint_name - .as_str() - .trim_start_matches(tool_name.as_str()) - .trim_start_matches("::"), - ), - LintAttrTool::NoTool => full_original_lint_name, - } - } - - pub fn tool_name(&self) -> Option { - if let LintAttrTool::Present { tool_name, .. } = self.kind { Some(tool_name) } else { None } - } - - pub fn tool_is_named(&self, other: Symbol) -> bool { - self.tool_name().is_some_and(|tool_name| tool_name == other) - } - - pub fn original_lint_name(&self) -> Symbol { - match self.original_name { - Some(name) => name, - None => self.full_lint(), - } - } -} - -#[derive(Debug, Clone, PrintAttribute, Encodable, Decodable, HashStable_Generic)] -enum LintAttrTool { - Present { tool_name: Symbol, full_lint: Symbol }, - NoTool, -} - -#[derive(Clone, Copy, Debug, HashStable_Generic, Encodable, Decodable, PrintAttribute, PartialEq)] -pub enum LintAttributeKind { - Allow, - Deny, - Expect, - Forbid, - Warn, -} - -impl LintAttributeKind { - pub const fn symbol(&self) -> Symbol { - match self { - Self::Allow => sym::allow, - Self::Deny => sym::deny, - Self::Expect => sym::expect, - Self::Forbid => sym::forbid, - Self::Warn => sym::warn, - } - } -} - /// Represents parsed *built-in* inert attributes. /// /// ## Overview @@ -1236,9 +1097,6 @@ pub enum AttributeKind { /// Represents `#[linkage]`. Linkage(Linkage, Span), - /// Represents `#[allow]`, `#[expect]`, `#[warn]`, `#[deny]`, `#[forbid]` - LintAttributes(ThinVec), - /// Represents `#[loop_match]`. LoopMatch(Span), diff --git a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs index 6612ebd6135b8..c19fc6976c6e6 100644 --- a/compiler/rustc_hir/src/attrs/encode_cross_crate.rs +++ b/compiler/rustc_hir/src/attrs/encode_cross_crate.rs @@ -56,7 +56,6 @@ impl AttributeKind { LinkOrdinal { .. } => No, LinkSection { .. } => Yes, // Needed for rustdoc Linkage(..) => No, - LintAttributes { .. } => No, LoopMatch(..) => No, MacroEscape(..) => No, MacroExport { .. } => Yes, diff --git a/compiler/rustc_hir/src/attrs/pretty_printing.rs b/compiler/rustc_hir/src/attrs/pretty_printing.rs index 811b250b6fcd6..9d14f9de3078d 100644 --- a/compiler/rustc_hir/src/attrs/pretty_printing.rs +++ b/compiler/rustc_hir/src/attrs/pretty_printing.rs @@ -17,8 +17,6 @@ use rustc_span::{ErrorGuaranteed, Ident, Span, Symbol}; use rustc_target::spec::SanitizerSet; use thin_vec::ThinVec; -use crate::HashIgnoredAttrId; -use crate::attrs::LintInstance; use crate::limit::Limit; /// This trait is used to print attributes in `rustc_hir_pretty`. @@ -193,8 +191,8 @@ macro_rules! print_tup { } print_tup!(A B C D E F G H); -print_skip!(Span, (), ErrorGuaranteed, AttrId, HashIgnoredAttrId); -print_disp!(u8, u16, u32, u128, usize, bool, NonZero, Limit, LintInstance); +print_skip!(Span, (), ErrorGuaranteed, AttrId); +print_disp!(u8, u16, u32, u128, usize, bool, NonZero, Limit); print_debug!( Symbol, Ident, diff --git a/compiler/rustc_hir/src/hir.rs b/compiler/rustc_hir/src/hir.rs index 71ef6c9a9c03c..57cf42cc54794 100644 --- a/compiler/rustc_hir/src/hir.rs +++ b/compiler/rustc_hir/src/hir.rs @@ -1304,19 +1304,6 @@ impl Attribute { Attribute::Unparsed(_) => false, } } - - pub fn has_span_without_desugaring_kind(&self) -> bool { - let span = match self { - Attribute::Unparsed(attr) => attr.span, - Attribute::Parsed(AttributeKind::Deprecated { span, .. }) => *span, - Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs)) => { - return sub_attrs.iter().any(|attr| attr.attr_span.desugaring_kind().is_none()); - } - Attribute::Parsed(attr) => panic!("can't get span of parsed attr: {:?}", attr), - }; - - span.desugaring_kind().is_none() - } } impl AttributeExt for Attribute { @@ -1391,7 +1378,6 @@ impl AttributeExt for Attribute { Attribute::Parsed(AttributeKind::DocComment { span, .. }) => *span, Attribute::Parsed(AttributeKind::Deprecated { span, .. }) => *span, Attribute::Parsed(AttributeKind::CfgTrace(cfgs)) => cfgs[0].1, - Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs)) => sub_attrs[0].attr_span, a => panic!("can't get the span of an arbitrary parsed attribute: {a:?}"), } } diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index b288720db9cc7..55e6d233f4755 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -16,10 +16,11 @@ use rustc_errors::{ Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, listify, pluralize, struct_span_code_err, }; +use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::lang_items::LangItem; -use rustc_hir::{self as hir, Attribute, ExprKind, HirId, QPath, find_attr, is_range_literal}; +use rustc_hir::{ExprKind, HirId, QPath, find_attr, is_range_literal}; use rustc_hir_analysis::NoVariantNamed; use rustc_hir_analysis::errors::NoFieldOnType; use rustc_hir_analysis::hir_ty_lowering::HirTyLowerer as _; @@ -55,21 +56,26 @@ use crate::{ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { pub(crate) fn precedence(&self, expr: &hir::Expr<'_>) -> ExprPrecedence { - // For the purpose of rendering suggestions, disregard attributes - // that originate from desugaring of any kind. For example, `x?` - // desugars to `#[allow(unreachable_code)] match ...`. Failing to - // ignore the prefix attribute in the desugaring would cause this - // suggestion: - // - // let y: u32 = x?.try_into().unwrap(); - // ++++++++++++++++++++ - // - // to be rendered as: - // - // let y: u32 = (x?).try_into().unwrap(); - // + +++++++++++++++++++++ let has_attr = |id: HirId| -> bool { - self.tcx.hir_attrs(id).iter().any(Attribute::has_span_without_desugaring_kind) + for attr in self.tcx.hir_attrs(id) { + // For the purpose of rendering suggestions, disregard attributes + // that originate from desugaring of any kind. For example, `x?` + // desugars to `#[allow(unreachable_code)] match ...`. Failing to + // ignore the prefix attribute in the desugaring would cause this + // suggestion: + // + // let y: u32 = x?.try_into().unwrap(); + // ++++++++++++++++++++ + // + // to be rendered as: + // + // let y: u32 = (x?).try_into().unwrap(); + // + +++++++++++++++++++++ + if attr.span().desugaring_kind().is_none() { + return true; + } + } + false }; // Special case: range expressions are desugared to struct literals in HIR, diff --git a/compiler/rustc_interface/src/passes.rs b/compiler/rustc_interface/src/passes.rs index 43efce545fc28..b51131f4712e6 100644 --- a/compiler/rustc_interface/src/passes.rs +++ b/compiler/rustc_interface/src/passes.rs @@ -119,10 +119,11 @@ impl LintStoreExpand for LintStoreExpandImpl<'_> { features: &Features, registered_tools: &RegisteredTools, node_id: ast::NodeId, + attrs: &[ast::Attribute], items: &[Box], name: Symbol, ) { - pre_expansion_lint(sess, features, self.0, registered_tools, (node_id, items), name); + pre_expansion_lint(sess, features, self.0, registered_tools, (node_id, attrs, items), name); } } diff --git a/compiler/rustc_lint/src/builtin.rs b/compiler/rustc_lint/src/builtin.rs index b49a6e261d73d..af590d98c301c 100644 --- a/compiler/rustc_lint/src/builtin.rs +++ b/compiler/rustc_lint/src/builtin.rs @@ -1901,10 +1901,9 @@ impl KeywordIdents { return; } - cx.sess().psess.buffer_lint( + cx.emit_span_lint( lint, ident.span, - CRATE_NODE_ID, BuiltinKeywordIdents { kw: ident, next: edition, suggestion: ident.span, prefix }, ); } diff --git a/compiler/rustc_lint/src/context.rs b/compiler/rustc_lint/src/context.rs index ae90c74c7e06b..752c2220d4147 100644 --- a/compiler/rustc_lint/src/context.rs +++ b/compiler/rustc_lint/src/context.rs @@ -25,9 +25,7 @@ use rustc_middle::middle::privacy::EffectiveVisibilities; use rustc_middle::ty::layout::{LayoutError, LayoutOfHelpers, TyAndLayout}; use rustc_middle::ty::print::{PrintError, PrintTraitRefExt as _, Printer, with_no_trimmed_paths}; use rustc_middle::ty::{self, GenericArg, RegisteredTools, Ty, TyCtxt, TypingEnv, TypingMode}; -use rustc_session::lint::{ - CheckLintNameResult, FutureIncompatibleInfo, Lint, LintExpectationId, LintId, TargetLint, -}; +use rustc_session::lint::{FutureIncompatibleInfo, Lint, LintExpectationId, LintId}; use rustc_session::{DynLintStore, Session}; use rustc_span::edit_distance::find_best_match_for_names; use rustc_span::{Ident, Span, Symbol, sym}; @@ -71,19 +69,26 @@ impl DynLintStore for LintStore { rustc_session::LintGroup { name, lints, is_externally_loaded } })) } +} - fn check_lint_name( - &self, - lint_name: &str, - tool_name: Option, - registered_tools: &RegisteredTools, - ) -> CheckLintNameResult<'_> { - self.check_lint_name(lint_name, tool_name, registered_tools) - } +/// The target of the `by_name` map, which accounts for renaming/deprecation. +#[derive(Debug)] +enum TargetLint { + /// A direct lint target + Id(LintId), - fn find_lints(&self, lint_name: &str) -> Option<&[LintId]> { - self.find_lints(lint_name) - } + /// Temporary renaming, used for easing migration pain; see #16545 + Renamed(String, LintId), + + /// Lint with this name existed previously, but has been removed/deprecated. + /// The string argument is the reason for removal. + Removed(String), + + /// A lint name that should give no warnings and have no effect. + /// + /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers + /// them as tool lints. + Ignored, } struct LintAlias { @@ -98,6 +103,29 @@ struct LintGroup { depr: Option, } +#[derive(Debug)] +pub enum CheckLintNameResult<'a> { + Ok(&'a [LintId]), + /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name. + NoLint(Option<(Symbol, bool)>), + /// The lint refers to a tool that has not been registered. + NoTool, + /// The lint has been renamed to a new name. + Renamed(String), + /// The lint has been removed due to the given reason. + Removed(String), + + /// The lint is from a tool. The `LintId` will be returned as if it were a + /// rustc lint. The `Option` indicates if the lint has been + /// renamed. + Tool(&'a [LintId], Option), + + /// The lint is from a tool. Either the lint does not exist in the tool or + /// the code was not compiled with the tool and therefore the lint was + /// never added to the `LintStore`. + MissingTool, +} + impl LintStore { pub fn new() -> LintStore { LintStore { @@ -276,10 +304,6 @@ impl LintStore { self.by_name.insert(name.into(), Removed(reason.into())); } - pub fn get_lint_by_name(&self, lint_name: &str) -> Option<&TargetLint> { - self.by_name.get(lint_name) - } - pub fn find_lints(&self, lint_name: &str) -> Option<&[LintId]> { match self.by_name.get(lint_name) { Some(Id(lint_id)) => Some(slice::from_ref(lint_id)), @@ -369,7 +393,7 @@ impl LintStore { } } match self.by_name.get(&complete_name) { - Some(Renamed(new_name, _)) => CheckLintNameResult::Renamed(Symbol::intern(new_name)), + Some(Renamed(new_name, _)) => CheckLintNameResult::Renamed(new_name.to_string()), Some(Removed(reason)) => CheckLintNameResult::Removed(reason.to_string()), None => match self.lint_groups.get(&*complete_name) { // If neither the lint, nor the lint group exists check if there is a `clippy::` @@ -578,8 +602,6 @@ impl<'tcx> LintContext for LateContext<'tcx> { } } - /// Only appropriate for use inside of the compiler - /// since the compiler doesn't track levels of tool lints fn get_lint_level(&self, lint: &'static Lint) -> LevelAndSource { self.tcx.lint_level_at_node(lint, self.last_node_with_lint_attrs) } @@ -820,7 +842,12 @@ impl<'tcx> LateContext<'tcx> { /// be used for pretty-printing HIR by rustc_hir_pretty. pub fn precedence(&self, expr: &hir::Expr<'_>) -> ExprPrecedence { let has_attr = |id: hir::HirId| -> bool { - self.tcx.hir_attrs(id).iter().any(hir::Attribute::has_span_without_desugaring_kind) + for attr in self.tcx.hir_attrs(id) { + if attr.span().desugaring_kind().is_none() { + return true; + } + } + false }; expr.precedence(&has_attr) } diff --git a/compiler/rustc_lint/src/early.rs b/compiler/rustc_lint/src/early.rs index e55cfc052ac49..df5adf694d3aa 100644 --- a/compiler/rustc_lint/src/early.rs +++ b/compiler/rustc_lint/src/early.rs @@ -12,7 +12,7 @@ use rustc_feature::Features; use rustc_middle::ty::{RegisteredTools, TyCtxt}; use rustc_session::Session; use rustc_session::lint::LintPass; -use rustc_span::{DUMMY_SP, Ident, Span}; +use rustc_span::{Ident, Span}; use tracing::debug; use crate::context::{EarlyContext, LintContext, LintStore}; @@ -63,17 +63,13 @@ impl<'ecx, 'tcx, T: EarlyLintPass> EarlyContextAndPass<'ecx, 'tcx, T> { /// Merge the lints specified by any lint attributes into the /// current lint context, call the provided function, then reset the /// lints in effect to their previous state. - fn with_lint_attrs( - &mut self, - id: ast::NodeId, - attrs: &'_ [ast::Attribute], - f: F, - target_span: Span, - ) where + fn with_lint_attrs(&mut self, id: ast::NodeId, attrs: &'_ [ast::Attribute], f: F) + where F: FnOnce(&mut Self), { + let is_crate_node = id == ast::CRATE_NODE_ID; debug!(?id); - let push = self.context.builder.push(attrs, id, target_span); + let push = self.context.builder.push(attrs, is_crate_node, None); debug!("early context: enter_attrs({:?})", attrs); lint_callback!(self, check_attributes, attrs); @@ -92,39 +88,24 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } fn visit_param(&mut self, param: &'ast ast::Param) { - self.with_lint_attrs( - param.id, - ¶m.attrs, - |cx| { - lint_callback!(cx, check_param, param); - ast_visit::walk_param(cx, param); - }, - param.span, - ); + self.with_lint_attrs(param.id, ¶m.attrs, |cx| { + lint_callback!(cx, check_param, param); + ast_visit::walk_param(cx, param); + }); } fn visit_item(&mut self, it: &'ast ast::Item) { - self.with_lint_attrs( - it.id, - &it.attrs, - |cx| { - lint_callback!(cx, check_item, it); - ast_visit::walk_item(cx, it); - lint_callback!(cx, check_item_post, it); - }, - it.span, - ) + self.with_lint_attrs(it.id, &it.attrs, |cx| { + lint_callback!(cx, check_item, it); + ast_visit::walk_item(cx, it); + lint_callback!(cx, check_item_post, it); + }) } fn visit_foreign_item(&mut self, it: &'ast ast::ForeignItem) { - self.with_lint_attrs( - it.id, - &it.attrs, - |cx| { - ast_visit::walk_item(cx, it); - }, - it.span, - ) + self.with_lint_attrs(it.id, &it.attrs, |cx| { + ast_visit::walk_item(cx, it); + }) } fn visit_pat(&mut self, p: &'ast ast::Pat) { @@ -134,38 +115,23 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } fn visit_pat_field(&mut self, field: &'ast ast::PatField) { - self.with_lint_attrs( - field.id, - &field.attrs, - |cx| { - ast_visit::walk_pat_field(cx, field); - }, - field.span, - ); + self.with_lint_attrs(field.id, &field.attrs, |cx| { + ast_visit::walk_pat_field(cx, field); + }); } fn visit_expr(&mut self, e: &'ast ast::Expr) { - self.with_lint_attrs( - e.id, - &e.attrs, - |cx| { - lint_callback!(cx, check_expr, e); - ast_visit::walk_expr(cx, e); - lint_callback!(cx, check_expr_post, e); - }, - e.span, - ) + self.with_lint_attrs(e.id, &e.attrs, |cx| { + lint_callback!(cx, check_expr, e); + ast_visit::walk_expr(cx, e); + lint_callback!(cx, check_expr_post, e); + }) } fn visit_expr_field(&mut self, f: &'ast ast::ExprField) { - self.with_lint_attrs( - f.id, - &f.attrs, - |cx| { - ast_visit::walk_expr_field(cx, f); - }, - f.span, - ) + self.with_lint_attrs(f.id, &f.attrs, |cx| { + ast_visit::walk_expr_field(cx, f); + }) } fn visit_stmt(&mut self, s: &'ast ast::Stmt) { @@ -177,15 +143,10 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> // // Note that statements get their attributes from // the AST struct that they wrap (e.g. an item) - self.with_lint_attrs( - s.id, - s.attrs(), - |cx| { - lint_callback!(cx, check_stmt, s); - ast_visit::walk_stmt(cx, s); - }, - s.span, - ); + self.with_lint_attrs(s.id, s.attrs(), |cx| { + lint_callback!(cx, check_stmt, s); + ast_visit::walk_stmt(cx, s); + }); } fn visit_fn(&mut self, fk: ast_visit::FnKind<'ast>, _: &AttrVec, span: Span, id: ast::NodeId) { @@ -194,26 +155,16 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } fn visit_field_def(&mut self, s: &'ast ast::FieldDef) { - self.with_lint_attrs( - s.id, - &s.attrs, - |cx| { - ast_visit::walk_field_def(cx, s); - }, - s.span, - ) + self.with_lint_attrs(s.id, &s.attrs, |cx| { + ast_visit::walk_field_def(cx, s); + }) } fn visit_variant(&mut self, v: &'ast ast::Variant) { - self.with_lint_attrs( - v.id, - &v.attrs, - |cx| { - lint_callback!(cx, check_variant, v); - ast_visit::walk_variant(cx, v); - }, - v.span, - ) + self.with_lint_attrs(v.id, &v.attrs, |cx| { + lint_callback!(cx, check_variant, v); + ast_visit::walk_variant(cx, v); + }) } fn visit_ty(&mut self, t: &'ast ast::Ty) { @@ -226,15 +177,10 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } fn visit_local(&mut self, l: &'ast ast::Local) { - self.with_lint_attrs( - l.id, - &l.attrs, - |cx| { - lint_callback!(cx, check_local, l); - ast_visit::walk_local(cx, l); - }, - l.span, - ) + self.with_lint_attrs(l.id, &l.attrs, |cx| { + lint_callback!(cx, check_local, l); + ast_visit::walk_local(cx, l); + }) } fn visit_block(&mut self, b: &'ast ast::Block) { @@ -243,15 +189,10 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } fn visit_arm(&mut self, a: &'ast ast::Arm) { - self.with_lint_attrs( - a.id, - &a.attrs, - |cx| { - lint_callback!(cx, check_arm, a); - ast_visit::walk_arm(cx, a); - }, - a.span, - ) + self.with_lint_attrs(a.id, &a.attrs, |cx| { + lint_callback!(cx, check_arm, a); + ast_visit::walk_arm(cx, a); + }) } fn visit_generic_arg(&mut self, arg: &'ast ast::GenericArg) { @@ -260,15 +201,10 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } fn visit_generic_param(&mut self, param: &'ast ast::GenericParam) { - self.with_lint_attrs( - param.id, - ¶m.attrs, - |cx| { - lint_callback!(cx, check_generic_param, param); - ast_visit::walk_generic_param(cx, param); - }, - param.span(), - ); + self.with_lint_attrs(param.id, ¶m.attrs, |cx| { + lint_callback!(cx, check_generic_param, param); + ast_visit::walk_generic_param(cx, param); + }); } fn visit_generics(&mut self, g: &'ast ast::Generics) { @@ -288,30 +224,25 @@ impl<'ast, 'ecx, 'tcx, T: EarlyLintPass> ast_visit::Visitor<'ast> } fn visit_assoc_item(&mut self, item: &'ast ast::AssocItem, ctxt: ast_visit::AssocCtxt) { - self.with_lint_attrs( - item.id, - &item.attrs, - |cx| { - match ctxt { - ast_visit::AssocCtxt::Trait => { - lint_callback!(cx, check_trait_item, item); - } - ast_visit::AssocCtxt::Impl { .. } => { - lint_callback!(cx, check_impl_item, item); - } + self.with_lint_attrs(item.id, &item.attrs, |cx| { + match ctxt { + ast_visit::AssocCtxt::Trait => { + lint_callback!(cx, check_trait_item, item); } - ast_visit::walk_assoc_item(cx, item, ctxt); - match ctxt { - ast_visit::AssocCtxt::Trait => { - lint_callback!(cx, check_trait_item_post, item); - } - ast_visit::AssocCtxt::Impl { .. } => { - lint_callback!(cx, check_impl_item_post, item); - } + ast_visit::AssocCtxt::Impl { .. } => { + lint_callback!(cx, check_impl_item, item); } - }, - item.span, - ); + } + ast_visit::walk_assoc_item(cx, item, ctxt); + match ctxt { + ast_visit::AssocCtxt::Trait => { + lint_callback!(cx, check_trait_item_post, item); + } + ast_visit::AssocCtxt::Impl { .. } => { + lint_callback!(cx, check_impl_item_post, item); + } + } + }); } fn visit_attribute(&mut self, attr: &'ast ast::Attribute) { @@ -383,15 +314,16 @@ impl<'a> EarlyCheckNode<'a> for (&'a ast::Crate, &'a [ast::Attribute]) { } } -impl<'a> EarlyCheckNode<'a> for (ast::NodeId, &'a [Box]) { +impl<'a> EarlyCheckNode<'a> for (ast::NodeId, &'a [ast::Attribute], &'a [Box]) { fn id(self) -> ast::NodeId { self.0 } fn attrs(self) -> &'a [ast::Attribute] { - &[] + self.1 } fn check<'ecx, 'tcx, T: EarlyLintPass>(self, cx: &mut EarlyContextAndPass<'ecx, 'tcx, T>) { - walk_list!(cx, visit_item, self.1); + walk_list!(cx, visit_attribute, self.1); + walk_list!(cx, visit_item, self.2); } } @@ -439,7 +371,7 @@ fn check_ast_node_inner<'a, T: EarlyLintPass>( ) { let mut cx = EarlyContextAndPass { context, tcx, pass }; - cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx), DUMMY_SP); + cx.with_lint_attrs(check_node.id(), check_node.attrs(), |cx| check_node.check(cx)); // All of the buffered lints should have been emitted at this point. // If not, that means that we somehow buffered a lint for a node id diff --git a/compiler/rustc_lint/src/early/diagnostics.rs b/compiler/rustc_lint/src/early/diagnostics.rs index 0f27e04babd46..776313a7e8040 100644 --- a/compiler/rustc_lint/src/early/diagnostics.rs +++ b/compiler/rustc_lint/src/early/diagnostics.rs @@ -215,28 +215,6 @@ impl<'a> Diagnostic<'a, ()> for DecorateAttrLint<'_, '_, '_> { &AttributeLintKind::MissingOptionsForOnMove => { lints::MissingOptionsForOnMoveAttr.into_diag(dcx, level) } - &AttributeLintKind::RenamedLint { name, replace, suggestion } => lints::RenamedLint { - name, - replace, - suggestion: lints::RenamedLintSuggestion::WithSpan { suggestion, replace }, - } - .into_diag(dcx, level), - &AttributeLintKind::DeprecatedLintName { name, suggestion, replace } => { - lints::DeprecatedLintName { name, suggestion, replace }.into_diag(dcx, level) - } - &AttributeLintKind::RemovedLint { name, ref reason } => { - lints::RemovedLint { name, reason }.into_diag(dcx, level) - } - &AttributeLintKind::UnknownLint { name, span, suggestion } => lints::UnknownLint { - name, - suggestion: suggestion.map(|(replace, from_rustc)| { - lints::UnknownLintSuggestion::WithSpan { suggestion: span, replace, from_rustc } - }), - } - .into_diag(dcx, level), - &AttributeLintKind::IgnoredUnlessCrateSpecified { level: attr_level, name } => { - lints::IgnoredUnlessCrateSpecified { level: attr_level, name }.into_diag(dcx, level) - } } } } diff --git a/compiler/rustc_lint/src/errors.rs b/compiler/rustc_lint/src/errors.rs index 33ba7f6edbda6..8fec30816bd13 100644 --- a/compiler/rustc_lint/src/errors.rs +++ b/compiler/rustc_lint/src/errors.rs @@ -44,6 +44,36 @@ impl Subdiagnostic for OverruledAttributeSub { } } +#[derive(Diagnostic)] +#[diag("malformed lint attribute input", code = E0452)] +pub(crate) struct MalformedAttribute { + #[primary_span] + pub span: Span, + #[subdiagnostic] + pub sub: MalformedAttributeSub, +} + +#[derive(Subdiagnostic)] +pub(crate) enum MalformedAttributeSub { + #[label("bad attribute argument")] + BadAttributeArgument(#[primary_span] Span), + #[label("reason must be a string literal")] + ReasonMustBeStringLiteral(#[primary_span] Span), + #[label("reason in lint attribute must come last")] + ReasonMustComeLast(#[primary_span] Span), +} + +#[derive(Diagnostic)] +#[diag("unknown tool name `{$tool_name}` found in scoped lint: `{$tool_name}::{$lint_name}`", code = E0710)] +pub(crate) struct UnknownToolInScopedLint { + #[primary_span] + pub span: Option, + pub tool_name: Symbol, + pub lint_name: String, + #[help("add `#![register_tool({$tool_name})]` to the crate root")] + pub is_nightly_build: bool, +} + #[derive(Diagnostic)] #[diag("`...` range patterns are deprecated", code = E0783)] pub(crate) struct BuiltinEllipsisInclusiveRangePatterns { diff --git a/compiler/rustc_lint/src/expect.rs b/compiler/rustc_lint/src/expect.rs index 12e23eea720fe..481e116d06e01 100644 --- a/compiler/rustc_lint/src/expect.rs +++ b/compiler/rustc_lint/src/expect.rs @@ -18,33 +18,33 @@ fn lint_expectations(tcx: TyCtxt<'_>, (): ()) -> Vec<(LintExpectationId, LintExp let mut expectations = Vec::new(); for owner in krate.owners() { - // Deduplicate expectations - let mut inner_expectations = Vec::new(); let lints = tcx.shallow_lint_levels_on(owner); - for expectation in &lints.expectations { - let canonicalized = canonicalize_id(&expectation.0); - if !inner_expectations.iter().any(|(id, _)| canonicalize_id(id) == canonicalized) { - inner_expectations.push(expectation.clone()); - } - } - expectations.extend(inner_expectations); + expectations.extend_from_slice(&lints.expectations); } expectations } -fn canonicalize_id(expect_id: &LintExpectationId) -> (rustc_span::AttrId, u16) { - match *expect_id { - LintExpectationId::Unstable { attr_id, lint_index, .. } => (attr_id, lint_index), - LintExpectationId::Stable { attr_id, lint_index, .. } => (attr_id, lint_index), - } -} - fn check_expectations(tcx: TyCtxt<'_>, tool_filter: Option) { let lint_expectations = tcx.lint_expectations(()); let fulfilled_expectations = tcx.dcx().steal_fulfilled_expectation_ids(); // Turn a `LintExpectationId` into a `(AttrId, lint_index)` pair. + let canonicalize_id = |expect_id: &LintExpectationId| { + match *expect_id { + LintExpectationId::Unstable { attr_id, lint_index: Some(lint_index) } => { + (attr_id, lint_index) + } + LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => { + // We are an `eval_always` query, so looking at the attribute's `AttrId` is ok. + let attr_id = tcx.hir_attrs(hir_id)[attr_index as usize].id(); + + (attr_id, lint_index) + } + _ => panic!("fulfilled expectations must have a lint index"), + } + }; + let fulfilled_expectations: FxHashSet<_> = fulfilled_expectations.iter().map(canonicalize_id).collect(); diff --git a/compiler/rustc_lint/src/levels.rs b/compiler/rustc_lint/src/levels.rs index 12c5748d5ecdf..2b859b65c9f8f 100644 --- a/compiler/rustc_lint/src/levels.rs +++ b/compiler/rustc_lint/src/levels.rs @@ -1,13 +1,13 @@ use rustc_ast as ast; -use rustc_ast::{DUMMY_NODE_ID, NodeId}; -use rustc_attr_parsing::AttributeParser; +use rustc_ast::attr::AttributeExt; +use rustc_ast_pretty::pprust; use rustc_data_structures::fx::{FxHashSet, FxIndexMap}; use rustc_data_structures::unord::UnordSet; use rustc_errors::{Diag, DiagCtxtHandle, Diagnostic, MultiSpan, msg}; use rustc_feature::{Features, GateIssue}; -use rustc_hir::attrs::{LintAttribute, LintAttributeKind, LintInstance}; +use rustc_hir as hir; +use rustc_hir::HirId; use rustc_hir::intravisit::{self, Visitor}; -use rustc_hir::{self as hir, HirId, Target, find_attr}; use rustc_index::IndexVec; use rustc_middle::bug; use rustc_middle::hir::nested_filter; @@ -20,37 +20,26 @@ use rustc_middle::ty::{RegisteredTools, TyCtxt}; use rustc_session::Session; use rustc_session::lint::builtin::{ self, FORBIDDEN_LINT_GROUPS, RENAMED_AND_REMOVED_LINTS, SINGLE_USE_LIFETIMES, - UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, -}; -use rustc_session::lint::{ - CheckLintNameResult, Level, Lint, LintExpectationId, LintId, TargetLint, + UNFULFILLED_LINT_EXPECTATIONS, UNKNOWN_LINTS, UNUSED_ATTRIBUTES, }; +use rustc_session::lint::{Level, Lint, LintExpectationId, LintId}; use rustc_span::{DUMMY_SP, Span, Symbol, sym}; use tracing::{debug, instrument}; use crate::builtin::MISSING_DOCS; -use crate::context::LintStore; +use crate::context::{CheckLintNameResult, LintStore}; use crate::errors::{ - CheckNameUnknownTool, OverruledAttribute, OverruledAttributeSub, RequestedLevel, - UnsupportedGroup, + CheckNameUnknownTool, MalformedAttribute, MalformedAttributeSub, OverruledAttribute, + OverruledAttributeSub, RequestedLevel, UnknownToolInScopedLint, UnsupportedGroup, }; use crate::late::unerased_lint_store; use crate::lints::{ - DeprecatedLintNameFromCommandLine, OverruledAttributeLint, RemovedLintFromCommandLine, - RenamedLintFromCommandLine, RenamedLintSuggestion, UnknownLintFromCommandLine, + DeprecatedLintName, DeprecatedLintNameFromCommandLine, IgnoredUnlessCrateSpecified, + OverruledAttributeLint, RemovedLint, RemovedLintFromCommandLine, RenamedLint, + RenamedLintFromCommandLine, RenamedLintSuggestion, UnknownLint, UnknownLintFromCommandLine, UnknownLintSuggestion, }; -const ALLOW_LISTED_ATTRS: &[Symbol] = &[ - sym::allow, - sym::deny, - sym::expect, - sym::forbid, - sym::warn, - sym::automatically_derived, - sym::doc, -]; - /// Collection of lint levels for the whole crate. /// This is used by AST-based lints, which do not /// wait until we have built HIR to be emitted. @@ -277,7 +266,11 @@ impl LintLevelsProvider for LintLevelQueryMap<'_> { impl<'tcx> LintLevelsBuilder<'_, LintLevelQueryMap<'tcx>> { fn add_id(&mut self, hir_id: HirId) { self.provider.cur = hir_id; - self.add(self.provider.attrs.get(hir_id.local_id), Some(hir_id)); + self.add( + self.provider.attrs.get(hir_id.local_id), + hir_id == hir::CRATE_HIR_ID, + Some(hir_id), + ); } } @@ -397,19 +390,7 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { crate_attrs: &[ast::Attribute], ) -> Self { let mut builder = Self::new(sess, features, lint_added_lints, store, registered_tools); - let parsed_crate_attrs = AttributeParser::parse_limited_all_filtered( - sess, - crate_attrs, - ALLOW_LISTED_ATTRS, - Target::Crate, - DUMMY_SP, - DUMMY_NODE_ID, - Some(features), - rustc_attr_parsing::ShouldEmit::Nothing, - registered_tools, - ); - - builder.add(&parsed_crate_attrs, None); + builder.add(crate_attrs, true, None); builder } @@ -439,31 +420,18 @@ impl<'s> LintLevelsBuilder<'s, TopDown> { pub(crate) fn push( &mut self, attrs: &[ast::Attribute], - node_id: NodeId, - target_span: Span, + is_crate_node: bool, + source_hir_id: Option, ) -> BuilderPush { let prev = self.provider.cur; self.provider.cur = self.provider.sets.list.push(LintSet { specs: FxIndexMap::default(), parent: prev }); - if !attrs.is_empty() { - let attrs = AttributeParser::parse_limited_all_filtered( - self.sess, - attrs, - ALLOW_LISTED_ATTRS, - Target::Fn, - target_span, - node_id, - Some(self.features), - rustc_attr_parsing::ShouldEmit::Nothing, - self.registered_tools, - ); - self.add(&attrs, None); + self.add(attrs, is_crate_node, source_hir_id); - if self.provider.current_specs().is_empty() { - self.provider.sets.list.pop(); - self.provider.cur = prev; - } + if self.provider.current_specs().is_empty() { + self.provider.sets.list.pop(); + self.provider.cur = prev; } BuilderPush { prev } @@ -510,7 +478,7 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { .emit_err(UnsupportedGroup { lint_group: crate::WARNINGS.name_lower() }); } match self.store.check_lint_name(lint_name_only, tool_name, self.registered_tools) { - CheckLintNameResult::Renamed(replace) => { + CheckLintNameResult::Renamed(ref replace) => { let name = lint_name.as_str(); let suggestion = RenamedLintSuggestion::WithoutSpan { replace }; let requested_level = RequestedLevel { level, lint_name }; @@ -670,102 +638,297 @@ impl<'s, P: LintLevelsProvider> LintLevelsBuilder<'s, P> { }; } - fn simple_add( + fn add( &mut self, - level: Level, - lint: &LintInstance, - reason: Option, - expect_lint_id: Option, + attrs: &[impl AttributeExt], + is_crate_node: bool, + source_hir_id: Option, ) { - // If this function returns none, it means the attribute parser has already emitted appropriate errors + let sess = self.sess; + for (attr_index, attr) in attrs.iter().enumerate() { + if attr.is_automatically_derived_attr() { + self.insert( + LintId::of(SINGLE_USE_LIFETIMES), + LevelAndSource { + level: Level::Allow, + lint_id: None, + src: LintLevelSource::Default, + }, + ); + continue; + } - let src = - LintLevelSource::Node { name: lint.original_lint_name(), span: lint.span(), reason }; + // `#[doc(hidden)]` disables missing_docs check. + if attr.is_doc_hidden() { + self.insert( + LintId::of(MISSING_DOCS), + LevelAndSource { + level: Level::Allow, + lint_id: None, + src: LintLevelSource::Default, + }, + ); + continue; + } - let id = match self.store.get_lint_by_name(lint.full_lint().as_str()) { - Some(TargetLint::Id(id)) => id, - None | Some(_) => bug!( - "guaranteed to find id due to previous parsing, happened while parsing {:?}", - lint, - ), - }; + let (level, lint_id) = match Level::from_attr(attr) { + None => continue, + // This is the only lint level with a `LintExpectationId` that can be created from + // an attribute. + Some((Level::Expect, Some(unstable_id))) if let Some(hir_id) = source_hir_id => { + let LintExpectationId::Unstable { lint_index: None, attr_id: _ } = unstable_id + else { + bug!("stable id Level::from_attr") + }; + + let stable_id = LintExpectationId::Stable { + hir_id, + attr_index: attr_index.try_into().unwrap(), + lint_index: None, + }; + + (Level::Expect, Some(stable_id)) + } + Some((lvl, id)) => (lvl, id), + }; - if self.check_gated_lint(*id, lint.span(), false) { - self.insert_spec(*id, LevelAndSource { level, lint_id: expect_lint_id, src }); - } - } + let Some(mut metas) = attr.meta_item_list() else { continue }; - fn add(&mut self, attrs: &[hir::Attribute], source_hir_id: Option) { - if find_attr!(attrs, AutomaticallyDerived(..)) { - self.insert( - LintId::of(SINGLE_USE_LIFETIMES), - LevelAndSource { - level: Level::Allow, - lint_id: None, - src: LintLevelSource::Default, - }, - ); - } - // `#[doc(hidden)]` disables missing_docs check. - if find_attr!(attrs, Doc(d) if d.hidden.is_some()) { - self.insert( - LintId::of(MISSING_DOCS), - LevelAndSource { - level: Level::Allow, - lint_id: None, - src: LintLevelSource::Default, - }, - ); - } + // Check whether `metas` is empty, and get its last element. + let Some(tail_li) = metas.last() else { + // This emits the unused_attributes lint for `#[level()]` + continue; + }; - let Some(attrs) = find_attr!(attrs, LintAttributes(sub_attrs) => sub_attrs.into_iter()) - else { - return; - }; + // Before processing the lint names, look for a reason (RFC 2383) + // at the end. + let mut reason = None; + if let Some(item) = tail_li.meta_item() { + match item.kind { + ast::MetaItemKind::Word => {} // actual lint names handled later + ast::MetaItemKind::NameValue(ref name_value) => { + if item.path == sym::reason { + if let ast::LitKind::Str(rationale, _) = name_value.kind { + reason = Some(rationale); + } else { + sess.dcx().emit_err(MalformedAttribute { + span: name_value.span, + sub: MalformedAttributeSub::ReasonMustBeStringLiteral( + name_value.span, + ), + }); + } + // found reason, reslice meta list to exclude it + metas.pop().unwrap(); + } else { + sess.dcx().emit_err(MalformedAttribute { + span: item.span, + sub: MalformedAttributeSub::BadAttributeArgument(item.span), + }); + } + } + ast::MetaItemKind::List(_) => { + sess.dcx().emit_err(MalformedAttribute { + span: item.span, + sub: MalformedAttributeSub::BadAttributeArgument(item.span), + }); + } + } + } - for (attr_index, LintAttribute { reason, lint_instances, attr_id, kind, .. }) in - attrs.enumerate() - { - let attr_id = attr_id.attr_id; - let level = match kind { - LintAttributeKind::Allow => Level::Allow, - LintAttributeKind::Deny => Level::Deny, - LintAttributeKind::Forbid => Level::Forbid, - LintAttributeKind::Warn => Level::Warn, - LintAttributeKind::Expect => { - for lint in lint_instances { - let lint_index = lint.lint_index().try_into().unwrap(); - let attr_index = attr_index.try_into().unwrap(); - let expectation_id = match source_hir_id { - None => LintExpectationId::Unstable { attr_id, lint_index }, - Some(hir_id) => LintExpectationId::Stable { - hir_id, - attr_id, - lint_index, - attr_index, - }, + for (lint_index, li) in metas.iter_mut().enumerate() { + let mut lint_id = lint_id; + if let Some(id) = &mut lint_id { + id.set_lint_index(Some(lint_index as u16)); + } + + let sp = li.span(); + let meta_item = match li { + ast::MetaItemInner::MetaItem(meta_item) if meta_item.is_word() => meta_item, + _ => { + let sub = if let Some(item) = li.meta_item() + && let ast::MetaItemKind::NameValue(_) = item.kind + && item.path == sym::reason + { + MalformedAttributeSub::ReasonMustComeLast(sp) + } else { + MalformedAttributeSub::BadAttributeArgument(sp) + }; + + sess.dcx().emit_err(MalformedAttribute { span: sp, sub }); + continue; + } + }; + let tool_ident = if meta_item.path.segments.len() > 1 { + Some(meta_item.path.segments.remove(0).ident) + } else { + None + }; + let tool_name = tool_ident.map(|ident| ident.name); + let name = pprust::path_to_string(&meta_item.path); + let lint_result = + self.store.check_lint_name(&name, tool_name, self.registered_tools); + + let (ids, name) = match lint_result { + CheckLintNameResult::Ok(ids) => { + let name = + meta_item.path.segments.last().expect("empty lint name").ident.name; + (ids, name) + } + + CheckLintNameResult::Tool(ids, new_lint_name) => { + let name = match new_lint_name { + None => { + let complete_name = + &format!("{}::{}", tool_ident.unwrap().name, name); + Symbol::intern(complete_name) + } + Some(new_lint_name) => { + self.emit_span_lint( + builtin::RENAMED_AND_REMOVED_LINTS, + sp.into(), + DeprecatedLintName { + name, + suggestion: sp, + replace: &new_lint_name, + }, + ); + Symbol::intern(&new_lint_name) + } + }; + (ids, name) + } + + CheckLintNameResult::MissingTool => { + // If `MissingTool` is returned, then either the lint does not + // exist in the tool or the code was not compiled with the tool and + // therefore the lint was never added to the `LintStore`. To detect + // this is the responsibility of the lint tool. + continue; + } + + CheckLintNameResult::NoTool => { + sess.dcx().emit_err(UnknownToolInScopedLint { + span: tool_ident.map(|ident| ident.span), + tool_name: tool_name.unwrap(), + lint_name: pprust::path_to_string(&meta_item.path), + is_nightly_build: sess.is_nightly_build(), + }); + continue; + } + + CheckLintNameResult::Renamed(ref replace) => { + if self.lint_added_lints { + let suggestion = + RenamedLintSuggestion::WithSpan { suggestion: sp, replace }; + let name = + tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); + self.emit_span_lint( + RENAMED_AND_REMOVED_LINTS, + sp.into(), + RenamedLint { name: name.as_str(), replace, suggestion }, + ); + } + + // If this lint was renamed, apply the new lint instead of ignoring the + // attribute. Ignore any errors or warnings that happen because the new + // name is inaccurate. + // NOTE: `new_name` already includes the tool name, so we don't + // have to add it again. + let CheckLintNameResult::Ok(ids) = + self.store.check_lint_name(replace, None, self.registered_tools) + else { + panic!("renamed lint does not exist: {replace}"); }; - self.simple_add(Level::Expect, lint, *reason, Some(expectation_id)); - - let is_unfulfilled_lint_expectations = - lint.lint_name().as_str() == UNFULFILLED_LINT_EXPECTATIONS.name_lower(); - self.provider.push_expectation( - expectation_id, - LintExpectation::new( - *reason, - lint.span(), - is_unfulfilled_lint_expectations, - lint.tool_name(), - ), - ); + (ids, Symbol::intern(&replace)) } + CheckLintNameResult::Removed(ref reason) => { + if self.lint_added_lints { + let name = + tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); + self.emit_span_lint( + RENAMED_AND_REMOVED_LINTS, + sp.into(), + RemovedLint { name: name.as_str(), reason }, + ); + } + continue; + } + + CheckLintNameResult::NoLint(suggestion) => { + if self.lint_added_lints { + let name = + tool_ident.map(|tool| format!("{tool}::{name}")).unwrap_or(name); + let suggestion = suggestion.map(|(replace, from_rustc)| { + UnknownLintSuggestion::WithSpan { + suggestion: sp, + replace, + from_rustc, + } + }); + self.emit_span_lint( + UNKNOWN_LINTS, + sp.into(), + UnknownLint { name, suggestion }, + ); + } + continue; + } + }; + + let src = LintLevelSource::Node { name, span: sp, reason }; + for &id in ids { + if self.check_gated_lint(id, sp, false) { + self.insert_spec(id, LevelAndSource { level, lint_id, src }); + } + } + + // This checks for instances where the user writes + // `#[expect(unfulfilled_lint_expectations)]` in that case we want to avoid + // overriding the lint level but instead add an expectation that can't be + // fulfilled. The lint message will include an explanation, that the + // `unfulfilled_lint_expectations` lint can't be expected. + if let (Level::Expect, Some(expect_id)) = (level, lint_id) { + // The `unfulfilled_lint_expectations` lint is not part of any lint + // groups. Therefore. we only need to check the slice if it contains a + // single lint. + let is_unfulfilled_lint_expectations = match ids { + [lint] => *lint == LintId::of(UNFULFILLED_LINT_EXPECTATIONS), + _ => false, + }; + self.provider.push_expectation( + expect_id, + LintExpectation::new( + reason, + sp, + is_unfulfilled_lint_expectations, + tool_name, + ), + ); + } + } + } + + if self.lint_added_lints && !is_crate_node { + for (id, &LevelAndSource { level, ref src, .. }) in self.current_specs().iter() { + if !id.lint.crate_level_only { continue; } - }; - for lint in lint_instances { - self.simple_add(level, lint, *reason, None); + + let LintLevelSource::Node { name: lint_attr_name, span: lint_attr_span, .. } = *src + else { + continue; + }; + + self.emit_span_lint( + UNUSED_ATTRIBUTES, + lint_attr_span.into(), + IgnoredUnlessCrateSpecified { level: level.as_str(), name: lint_attr_name }, + ); + // don't set a separate error for every lint in the group + break; } } } diff --git a/compiler/rustc_lint/src/lib.rs b/compiler/rustc_lint/src/lib.rs index 6f6aa0f96e75e..30b1e736ef3b1 100644 --- a/compiler/rustc_lint/src/lib.rs +++ b/compiler/rustc_lint/src/lib.rs @@ -128,7 +128,7 @@ use unused::*; #[rustfmt::skip] pub use builtin::{MissingDoc, SoftLints}; -pub use context::{EarlyContext, LateContext, LintContext, LintStore}; +pub use context::{CheckLintNameResult, EarlyContext, LateContext, LintContext, LintStore}; pub use early::diagnostics::{DecorateAttrLint, DiagAndSess}; pub use early::{EarlyCheckNode, check_ast_node}; pub use late::{check_crate, late_lint_mod, unerased_lint_store}; @@ -136,9 +136,7 @@ pub use levels::LintLevelsBuilder; pub use passes::{EarlyLintPass, LateLintPass}; pub use rustc_errors::BufferedEarlyLint; pub use rustc_session::lint::Level::{self, *}; -pub use rustc_session::lint::{ - CheckLintNameResult, FutureIncompatibleInfo, Lint, LintId, LintPass, LintVec, -}; +pub use rustc_session::lint::{FutureIncompatibleInfo, Lint, LintId, LintPass, LintVec}; pub fn provide(providers: &mut Providers) { levels::provide(providers); diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index f52b90382d956..4279ab230df55 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1238,11 +1238,11 @@ pub(crate) struct OverruledAttributeLint<'a> { #[derive(Diagnostic)] #[diag("lint name `{$name}` is deprecated and may not have an effect in the future")] -pub(crate) struct DeprecatedLintName { - pub name: Symbol, +pub(crate) struct DeprecatedLintName<'a> { + pub name: String, #[suggestion("change it to", code = "{replace}", applicability = "machine-applicable")] pub suggestion: Span, - pub replace: Symbol, + pub replace: &'a str, } #[derive(Diagnostic)] @@ -1257,32 +1257,32 @@ pub(crate) struct DeprecatedLintNameFromCommandLine<'a> { #[derive(Diagnostic)] #[diag("lint `{$name}` has been renamed to `{$replace}`")] -pub(crate) struct RenamedLint { - pub name: Symbol, - pub replace: Symbol, +pub(crate) struct RenamedLint<'a> { + pub name: &'a str, + pub replace: &'a str, #[subdiagnostic] - pub suggestion: RenamedLintSuggestion, + pub suggestion: RenamedLintSuggestion<'a>, } #[derive(Subdiagnostic)] -pub(crate) enum RenamedLintSuggestion { +pub(crate) enum RenamedLintSuggestion<'a> { #[suggestion("use the new name", code = "{replace}", applicability = "machine-applicable")] WithSpan { #[primary_span] suggestion: Span, - replace: Symbol, + replace: &'a str, }, #[help("use the new name `{$replace}`")] - WithoutSpan { replace: Symbol }, + WithoutSpan { replace: &'a str }, } #[derive(Diagnostic)] #[diag("lint `{$name}` has been renamed to `{$replace}`")] pub(crate) struct RenamedLintFromCommandLine<'a> { pub name: &'a str, - pub replace: Symbol, + pub replace: &'a str, #[subdiagnostic] - pub suggestion: RenamedLintSuggestion, + pub suggestion: RenamedLintSuggestion<'a>, #[subdiagnostic] pub requested_level: RequestedLevel<'a>, } @@ -1290,7 +1290,7 @@ pub(crate) struct RenamedLintFromCommandLine<'a> { #[derive(Diagnostic)] #[diag("lint `{$name}` has been removed: {$reason}")] pub(crate) struct RemovedLint<'a> { - pub name: Symbol, + pub name: &'a str, pub reason: &'a str, } @@ -1306,7 +1306,7 @@ pub(crate) struct RemovedLintFromCommandLine<'a> { #[derive(Diagnostic)] #[diag("unknown lint: `{$name}`")] pub(crate) struct UnknownLint { - pub name: Symbol, + pub name: String, #[subdiagnostic] pub suggestion: Option, } @@ -1348,8 +1348,8 @@ pub(crate) struct UnknownLintFromCommandLine<'a> { #[derive(Diagnostic)] #[diag("{$level}({$name}) is ignored unless specified at crate level")] -pub(crate) struct IgnoredUnlessCrateSpecified { - pub level: Symbol, +pub(crate) struct IgnoredUnlessCrateSpecified<'a> { + pub level: &'a str, pub name: Symbol, } diff --git a/compiler/rustc_lint_defs/Cargo.toml b/compiler/rustc_lint_defs/Cargo.toml index 2ca62f7fa8cdc..c8201d5ea8ccc 100644 --- a/compiler/rustc_lint_defs/Cargo.toml +++ b/compiler/rustc_lint_defs/Cargo.toml @@ -5,6 +5,7 @@ edition = "2024" [dependencies] # tidy-alphabetical-start +rustc_ast = { path = "../rustc_ast" } rustc_data_structures = { path = "../rustc_data_structures" } rustc_error_messages = { path = "../rustc_error_messages" } rustc_hir_id = { path = "../rustc_hir_id" } diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index 36070dac276fb..2cec2ed06a502 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -1,6 +1,8 @@ use std::borrow::Cow; use std::fmt::Display; +use rustc_ast::AttrId; +use rustc_ast::attr::AttributeExt; use rustc_data_structures::fx::FxIndexSet; use rustc_data_structures::stable_hasher::{ HashStable, StableCompare, StableHasher, ToStableHashKey, @@ -10,7 +12,7 @@ use rustc_hir_id::{HirId, ItemLocalId}; use rustc_macros::{Decodable, Encodable, HashStable_Generic}; use rustc_span::def_id::DefPathHash; pub use rustc_span::edition::Edition; -use rustc_span::{AttrId, HashStableContext, Ident, Span, Symbol, sym}; +use rustc_span::{HashStableContext, Ident, Span, Symbol, sym}; use serde::{Deserialize, Serialize}; pub use self::Level::*; @@ -105,12 +107,12 @@ pub enum Applicability { pub enum LintExpectationId { /// Used for lints emitted during the `EarlyLintPass`. This id is not /// hash stable and should not be cached. - Unstable { attr_id: AttrId, lint_index: u16 }, + Unstable { attr_id: AttrId, lint_index: Option }, /// The [`HirId`] that the lint expectation is attached to. This id is /// stable and can be cached. The additional index ensures that nodes with /// several expectations can correctly match diagnostics to the individual /// expectation. - Stable { hir_id: HirId, attr_id: AttrId, attr_index: u16, lint_index: u16 }, + Stable { hir_id: HirId, attr_index: u16, lint_index: Option }, } impl LintExpectationId { @@ -121,14 +123,14 @@ impl LintExpectationId { } } - pub fn get_lint_index(&self) -> u16 { + pub fn get_lint_index(&self) -> Option { let (LintExpectationId::Unstable { lint_index, .. } | LintExpectationId::Stable { lint_index, .. }) = self; *lint_index } - pub fn set_lint_index(&mut self, new_lint_index: u16) { + pub fn set_lint_index(&mut self, new_lint_index: Option) { let (LintExpectationId::Unstable { lint_index, .. } | LintExpectationId::Stable { lint_index, .. }) = self; @@ -140,7 +142,7 @@ impl HashStable for LintExpectationId { #[inline] fn hash_stable(&self, hcx: &mut Hcx, hasher: &mut StableHasher) { match self { - LintExpectationId::Stable { hir_id, attr_index, lint_index, .. } => { + LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => { hir_id.hash_stable(hcx, hasher); attr_index.hash_stable(hcx, hasher); lint_index.hash_stable(hcx, hasher); @@ -160,7 +162,7 @@ impl ToStableHashKey for LintExpectationId { #[inline] fn to_stable_hash_key(&self, hcx: &mut Hcx) -> Self::KeyType { match self { - LintExpectationId::Stable { hir_id, attr_index, lint_index, .. } => { + LintExpectationId::Stable { hir_id, attr_index, lint_index: Some(lint_index) } => { let (def_path_hash, lint_idx) = hir_id.to_stable_hash_key(hcx); (def_path_hash, lint_idx, *attr_index, *lint_index) } @@ -233,17 +235,6 @@ impl Level { } } - pub fn from_symbol(x: Symbol) -> Option { - match x { - sym::allow => Some(Level::Allow), - sym::deny => Some(Level::Deny), - sym::expect => Some(Level::Expect), - sym::forbid => Some(Level::Forbid), - sym::warn => Some(Level::Warn), - _ => None, - } - } - /// Converts a lower-case string to a level. This will never construct the expect /// level as that would require a [`LintExpectationId`]. pub fn from_str(x: &str) -> Option { @@ -256,6 +247,35 @@ impl Level { } } + /// Converts an `Attribute` to a level. + pub fn from_attr(attr: &impl AttributeExt) -> Option<(Self, Option)> { + attr.name().and_then(|name| Self::from_symbol(name, || Some(attr.id()))) + } + + /// Converts a `Symbol` to a level. + pub fn from_symbol( + s: Symbol, + id: impl FnOnce() -> Option, + ) -> Option<(Self, Option)> { + match s { + sym::allow => Some((Level::Allow, None)), + sym::expect => { + if let Some(attr_id) = id() { + Some(( + Level::Expect, + Some(LintExpectationId::Unstable { attr_id, lint_index: None }), + )) + } else { + None + } + } + sym::warn => Some((Level::Warn, None)), + sym::deny => Some((Level::Deny, None)), + sym::forbid => Some((Level::Forbid, None)), + _ => None, + } + } + pub fn to_cmd_flag(self) -> &'static str { match self { Level::Warn => "-W", @@ -563,26 +583,6 @@ impl Lint { } } -/// The target of the `by_name` map, which accounts for renaming/deprecation. -#[derive(Debug)] -pub enum TargetLint { - /// A direct lint target - Id(LintId), - - /// Temporary renaming, used for easing migration pain; see #16545 - Renamed(String, LintId), - - /// Lint with this name existed previously, but has been removed/deprecated. - /// The string argument is the reason for removal. - Removed(String), - - /// A lint name that should give no warnings and have no effect. - /// - /// This is used by rustc to avoid warning about old rustdoc lints before rustdoc registers - /// them as tool lints. - Ignored, -} - /// Identifies a lint known to the compiler. #[derive(Clone, Copy, Debug)] pub struct LintId { @@ -762,29 +762,6 @@ pub enum AttributeLintKind { name: Symbol, }, OnMoveMalformedAttrExpectedLiteralOrDelimiter, - RenamedLint { - name: Symbol, - replace: Symbol, - suggestion: Span, - }, - DeprecatedLintName { - name: Symbol, - suggestion: Span, - replace: Symbol, - }, - RemovedLint { - name: Symbol, - reason: String, - }, - UnknownLint { - name: Symbol, - span: Span, - suggestion: Option<(Symbol, bool)>, - }, - IgnoredUnlessCrateSpecified { - level: Symbol, - name: Symbol, - }, } #[derive(Debug, Clone, HashStable_Generic)] @@ -793,31 +770,6 @@ pub enum FormatWarning { InvalidSpecifier { name: String, span: Span }, } -#[derive(Debug)] -pub enum CheckLintNameResult<'a> { - Ok(&'a [LintId]), - /// Lint doesn't exist. Potentially contains a suggestion for a correct lint name. - NoLint(Option<(Symbol, bool)>), - /// The lint refers to a tool that has not been registered. - NoTool, - /// The lint has been renamed to a new name. - Renamed(Symbol), - /// Lint that previously was part of rustc, but now is part of external lint tool - RenamedToolLint(Symbol), - /// The lint has been removed due to the given reason. - Removed(String), - - /// The lint is from a tool. The `LintId` will be returned as if it were a - /// rustc lint. The `Option` indicates if the lint has been - /// renamed. - Tool(&'a [LintId], Option), - - /// The lint is from a tool. Either the lint does not exist in the tool or - /// the code was not compiled with the tool and therefore the lint was - /// never added to the `LintStore`. - MissingTool, -} - pub type RegisteredTools = FxIndexSet; /// Declares a static item of type `&'static Lint`. diff --git a/compiler/rustc_mir_build/src/builder/scope.rs b/compiler/rustc_mir_build/src/builder/scope.rs index ab63685705143..91610e768d012 100644 --- a/compiler/rustc_mir_build/src/builder/scope.rs +++ b/compiler/rustc_mir_build/src/builder/scope.rs @@ -85,8 +85,7 @@ use std::mem; use interpret::ErrorHandled; use rustc_data_structures::fx::FxHashMap; -use rustc_hir::attrs::AttributeKind; -use rustc_hir::{Attribute, HirId}; +use rustc_hir::HirId; use rustc_index::{IndexSlice, IndexVec}; use rustc_middle::middle::region; use rustc_middle::mir::{self, *}; @@ -94,6 +93,7 @@ use rustc_middle::thir::{AdtExpr, AdtExprBase, ArmId, ExprId, ExprKind}; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt, ValTree}; use rustc_middle::{bug, span_bug}; use rustc_pattern_analysis::rustc::RustcPatCtxt; +use rustc_session::lint::Level; use rustc_span::{DUMMY_SP, Span, Spanned}; use tracing::{debug, instrument}; @@ -1298,12 +1298,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { break; } - if self - .tcx - .hir_attrs(id) - .iter() - .any(|attr| matches!(attr, Attribute::Parsed(AttributeKind::LintAttributes { .. }))) - { + if self.tcx.hir_attrs(id).iter().any(|attr| Level::from_attr(attr).is_some()) { // This is a rare case. It's for a node path that doesn't reach the root due to an // intervening lint level attribute. This result doesn't get cached. return id; diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index 12b583d8fee15..6aeb0ae57e752 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -10,7 +10,7 @@ use std::collections::hash_map::Entry; use std::slice; use rustc_abi::ExternAbi; -use rustc_ast::ast; +use rustc_ast::{AttrStyle, MetaItemKind, ast}; use rustc_attr_parsing::{AttributeParser, Late}; use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::thin_vec::ThinVec; @@ -19,8 +19,8 @@ use rustc_errors::{DiagCtxtHandle, IntoDiagArg, MultiSpan, msg}; use rustc_feature::{AttributeDuplicates, AttributeType, BUILTIN_ATTRIBUTE_MAP, BuiltinAttribute}; use rustc_hir::attrs::diagnostic::Directive; use rustc_hir::attrs::{ - AttributeKind, CrateType, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, - InlineAttr, LintAttribute, ReprAttr, SanitizerSet, + AttributeKind, DocAttribute, DocInline, EiiDecl, EiiImpl, EiiImplResolution, InlineAttr, + ReprAttr, SanitizerSet, }; use rustc_hir::def::DefKind; use rustc_hir::def_id::LocalModDefId; @@ -37,6 +37,7 @@ use rustc_middle::traits::ObligationCause; use rustc_middle::ty::error::{ExpectedFound, TypeError}; use rustc_middle::ty::{self, TyCtxt, TypingMode}; use rustc_middle::{bug, span_bug}; +use rustc_session::config::CrateType; use rustc_session::lint; use rustc_session::lint::builtin::{ CONFLICTING_REPR_HINTS, INVALID_DOC_ATTRIBUTES, MALFORMED_DIAGNOSTIC_FORMAT_LITERALS, @@ -140,6 +141,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { let mut seen = FxHashMap::default(); let attrs = self.tcx.hir_attrs(hir_id); for attr in attrs { + let mut style = None; match attr { Attribute::Parsed(AttributeKind::ProcMacro(_)) => { self.check_proc_macro(hir_id, target, ProcMacroKind::FunctionLike) @@ -221,7 +223,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { Attribute::Parsed(AttributeKind::OnMove { span, directive }) => { self.check_diagnostic_on_move(*span, hir_id, target, directive.as_deref()) }, - Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs)) => self.check_lint_attr(hir_id, sub_attrs), Attribute::Parsed( // tidy-alphabetical-start AttributeKind::RustcAllowIncoherentImpl(..) @@ -379,8 +380,18 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | AttributeKind::WindowsSubsystem(..) // tidy-alphabetical-end ) => { /* do nothing */ } - Attribute::Unparsed(_) => { + Attribute::Unparsed(attr_item) => { + style = Some(attr_item.style); match attr.path().as_slice() { + [ + // ok + sym::allow + | sym::expect + | sym::warn + | sym::deny + | sym::forbid, + .. + ] => {} [name, rest@..] => { match BUILTIN_ATTRIBUTE_MAP.get(name) { Some(_) => { @@ -462,7 +473,8 @@ impl<'tcx> CheckAttrVisitor<'tcx> { &mut seen, ); } - self.check_unused_attribute(hir_id, attr) + + self.check_unused_attribute(hir_id, attr, style) } self.check_repr(attrs, span, target, item, hir_id); @@ -1571,76 +1583,88 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } - fn check_lint_attr(&self, hir_id: HirId, sub_attrs: &[LintAttribute]) { - for LintAttribute { attr_span, lint_instances, attr_style, .. } in sub_attrs { - if !lint_instances.iter().any(|id| { - id.lint_name() == sym::linker_messages || id.lint_name() == sym::linker_info - }) { - continue; - }; - let note = if hir_id != CRATE_HIR_ID { - match attr_style { - ast::AttrStyle::Outer => { - let attr_span = attr_span; - let bang_position = self - .tcx - .sess - .source_map() - .span_until_char(*attr_span, '[') - .shrink_to_hi(); - - self.tcx.emit_node_span_lint( + fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute, style: Option) { + // Warn on useless empty attributes. + // FIXME(jdonszelmann): this lint should be moved to attribute parsing, see `AcceptContext::warn_empty_attribute` + let note = + if attr.has_any_name(&[sym::allow, sym::expect, sym::warn, sym::deny, sym::forbid]) + && attr.meta_item_list().is_some_and(|list| list.is_empty()) + { + errors::UnusedNote::EmptyList { name: attr.name().unwrap() } + } else if attr.has_any_name(&[ + sym::allow, + sym::warn, + sym::deny, + sym::forbid, + sym::expect, + ]) && let Some(meta) = attr.meta_item_list() + && let [meta] = meta.as_slice() + && let Some(item) = meta.meta_item() + && let MetaItemKind::NameValue(_) = &item.kind + && item.path == sym::reason + { + errors::UnusedNote::NoLints { name: attr.name().unwrap() } + } else if attr.has_any_name(&[ + sym::allow, + sym::warn, + sym::deny, + sym::forbid, + sym::expect, + ]) && let Some(meta) = attr.meta_item_list() + && meta.iter().any(|meta| { + meta.meta_item().map_or(false, |item| { + item.path == sym::linker_messages || item.path == sym::linker_info + }) + }) + { + if hir_id != CRATE_HIR_ID { + match style { + Some(ast::AttrStyle::Outer) => { + let attr_span = attr.span(); + let bang_position = self + .tcx + .sess + .source_map() + .span_until_char(attr_span, '[') + .shrink_to_hi(); + + self.tcx.emit_node_span_lint( + UNUSED_ATTRIBUTES, + hir_id, + attr_span, + errors::OuterCrateLevelAttr { + suggestion: errors::OuterCrateLevelAttrSuggestion { + bang_position, + }, + }, + ) + } + Some(ast::AttrStyle::Inner) | None => self.tcx.emit_node_span_lint( UNUSED_ATTRIBUTES, hir_id, - *attr_span, - errors::OuterCrateLevelAttr { - suggestion: errors::OuterCrateLevelAttrSuggestion { bang_position }, - }, - ) - } - ast::AttrStyle::Inner => self.tcx.emit_node_span_lint( - UNUSED_ATTRIBUTES, - hir_id, - *attr_span, - errors::InnerCrateLevelAttr, - ), - }; - continue; - } else { - let never_needs_link = self - .tcx - .crate_types() - .iter() - .all(|kind| matches!(kind, CrateType::Rlib | CrateType::StaticLib)); - if never_needs_link { - errors::UnusedNote::LinkerMessagesBinaryCrateOnly + attr.span(), + errors::InnerCrateLevelAttr, + ), + }; + return; } else { - continue; + let never_needs_link = self + .tcx + .crate_types() + .iter() + .all(|kind| matches!(kind, CrateType::Rlib | CrateType::StaticLib)); + if never_needs_link { + errors::UnusedNote::LinkerMessagesBinaryCrateOnly + } else { + return; + } } + } else if attr.has_name(sym::default_method_body_is_const) { + errors::UnusedNote::DefaultMethodBodyConst + } else { + return; }; - self.tcx.emit_node_span_lint( - UNUSED_ATTRIBUTES, - hir_id, - *attr_span, - errors::Unused { attr_span: *attr_span, note }, - ); - } - } - - fn check_unused_attribute(&self, hir_id: HirId, attr: &Attribute) { - // Warn on useless empty attributes. - // FIXME(jdonszelmann): this lint should be moved to attribute parsing, see `AcceptContext::warn_empty_attribute` - let note = if attr.has_any_name(&[sym::feature]) - && attr.meta_item_list().is_some_and(|list| list.is_empty()) - { - errors::UnusedNote::EmptyList { name: attr.name().unwrap() } - } else if attr.has_name(sym::default_method_body_is_const) { - errors::UnusedNote::DefaultMethodBodyConst - } else { - return; - }; - self.tcx.emit_node_span_lint( UNUSED_ATTRIBUTES, hir_id, diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 628d0b0c961a1..f9dc696f320e3 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -294,6 +294,8 @@ pub(crate) enum MacroExport { pub(crate) enum UnusedNote { #[note("attribute `{$name}` with an empty list has no effect")] EmptyList { name: Symbol }, + #[note("attribute `{$name}` without any lints has no effect")] + NoLints { name: Symbol }, #[note("`default_method_body_is_const` has been replaced with `const` on traits")] DefaultMethodBodyConst, #[note( diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index 0cc633e713b3d..427a75c6bff4d 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -146,7 +146,7 @@ impl<'a, 'ra, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'ra, 'tcx> { let mut parser = AttributeParser::<'_, Early>::new( &self.resolver.tcx.sess, self.resolver.tcx.features(), - self.resolver.tcx().registered_tools(()), + Vec::new(), Early { emit_errors: ShouldEmit::Nothing }, ); let attrs = parser.parse_attribute_list( diff --git a/compiler/rustc_session/src/session.rs b/compiler/rustc_session/src/session.rs index 786cf0a373d8c..a9e7f1503b9ca 100644 --- a/compiler/rustc_session/src/session.rs +++ b/compiler/rustc_session/src/session.rs @@ -40,7 +40,7 @@ use crate::config::{ Input, InstrumentCoverage, OptLevel, OutFileName, OutputType, SwitchWithOptPath, }; use crate::filesearch::FileSearch; -use crate::lint::{CheckLintNameResult, LintId, RegisteredTools}; +use crate::lint::LintId; use crate::parse::{ParseSess, add_feature_diagnostics}; use crate::search_paths::SearchPath; use crate::{errors, filesearch, lint}; @@ -81,15 +81,6 @@ pub struct CompilerIO { pub trait DynLintStore: Any + DynSync + DynSend { /// Provides a way to access lint groups without depending on `rustc_lint` fn lint_groups_iter(&self) -> Box + '_>; - - fn check_lint_name( - &self, - lint_name: &str, - tool_name: Option, - registered_tools: &RegisteredTools, - ) -> CheckLintNameResult<'_>; - - fn find_lints(&self, lint_name: &str) -> Option<&[LintId]>; } /// Represents the data associated with a compilation diff --git a/compiler/rustc_span/src/caching_source_map_view.rs b/compiler/rustc_span/src/caching_source_map_view.rs index 5c8b1400f36bc..3a3f238a7e55b 100644 --- a/compiler/rustc_span/src/caching_source_map_view.rs +++ b/compiler/rustc_span/src/caching_source_map_view.rs @@ -141,7 +141,7 @@ impl<'sm> CachingSourceMapView<'sm> { lo_line_number, span_data.lo - lo_line_bounds.start, hi_line_number, - span_data.hi, + span_data.hi - hi_line_bounds.start, )) } diff --git a/src/tools/clippy/clippy_lints/src/attrs/mod.rs b/src/tools/clippy/clippy_lints/src/attrs/mod.rs index 372defbb4d7e2..c15a378053e39 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/mod.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/mod.rs @@ -583,7 +583,7 @@ impl EarlyLintPass for PostExpansionEarlyAttributes { if matches!(name, sym::allow | sym::expect) && self.msrv.meets(msrvs::LINT_REASONS_STABILIZATION) { allow_attributes_without_reason::check(cx, name, items, attr); } - if is_lint_level(name) { + if is_lint_level(name, attr.id) { blanket_clippy_restriction_lints::check(cx, name, items); } if items.is_empty() || !attr.has_name(sym::deprecated) { diff --git a/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs b/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs index 5d095c9b27ade..6ee3290fa761d 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/unnecessary_clippy_cfg.rs @@ -1,12 +1,10 @@ -use crate::attrs::is_lint_level; - use super::{Attribute, UNNECESSARY_CLIPPY_CFG}; use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_sugg}; use clippy_utils::source::SpanRangeExt; use itertools::Itertools; use rustc_ast::AttrStyle; use rustc_errors::Applicability; -use rustc_lint::{EarlyContext}; +use rustc_lint::{EarlyContext, Level}; use rustc_span::sym; pub(super) fn check( @@ -15,10 +13,9 @@ pub(super) fn check( behind_cfg_attr: &rustc_ast::MetaItem, attr: &Attribute, ) { - // FIXME use proper attr parsing here if cfg_attr.has_name(sym::clippy) && let Some(ident) = behind_cfg_attr.ident() - && is_lint_level(ident.name) + && Level::from_symbol(ident.name, || Some(attr.id)).is_some() && let Some(items) = behind_cfg_attr.meta_item_list() { let nb_items = items.len(); diff --git a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs index 2d56086a96024..9a1e315ae5306 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/useless_attribute.rs @@ -15,7 +15,7 @@ pub(super) fn check(cx: &EarlyContext<'_>, item: &Item, attrs: &[Attribute]) { return; } if let Some(lint_list) = &attr.meta_item_list() - && attr.name().is_some_and(is_lint_level) + && attr.name().is_some_and(|name| is_lint_level(name, attr.id)) { for lint in lint_list { match item.kind { diff --git a/src/tools/clippy/clippy_lints/src/attrs/utils.rs b/src/tools/clippy/clippy_lints/src/attrs/utils.rs index 512f961228b15..7b66f91f6c073 100644 --- a/src/tools/clippy/clippy_lints/src/attrs/utils.rs +++ b/src/tools/clippy/clippy_lints/src/attrs/utils.rs @@ -1,5 +1,5 @@ use clippy_utils::macros::{is_panic, macro_backtrace}; -use rustc_ast::{MetaItemInner}; +use rustc_ast::{AttrId, MetaItemInner}; use rustc_hir::{ Block, Expr, ExprKind, ImplItem, ImplItemKind, Item, ItemKind, StmtKind, TraitFn, TraitItem, TraitItemKind, }; @@ -16,8 +16,8 @@ pub(super) fn is_word(nmi: &MetaItemInner, expected: Symbol) -> bool { } } -pub(super) fn is_lint_level(symbol: Symbol) -> bool { - Level::from_symbol(symbol).is_some() +pub(super) fn is_lint_level(symbol: Symbol, attr_id: AttrId) -> bool { + Level::from_symbol(symbol, || Some(attr_id)).is_some() } pub(super) fn is_relevant_item(cx: &LateContext<'_>, item: &Item<'_>) -> bool { diff --git a/src/tools/clippy/clippy_lints/src/collapsible_if.rs b/src/tools/clippy/clippy_lints/src/collapsible_if.rs index ca04ce1764ff4..52e602bbac577 100644 --- a/src/tools/clippy/clippy_lints/src/collapsible_if.rs +++ b/src/tools/clippy/clippy_lints/src/collapsible_if.rs @@ -3,12 +3,11 @@ use clippy_utils::diagnostics::span_lint_hir_and_then; use clippy_utils::msrvs::Msrv; use clippy_utils::source::{HasSession, IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability}; use clippy_utils::{can_use_if_let_chains, span_contains_cfg, span_contains_non_whitespace, sym, tokenize_with_text}; -use rustc_ast::BinOpKind; +use rustc_ast::{BinOpKind, MetaItemInner}; use rustc_errors::Applicability; -use rustc_hir::attrs::{AttributeKind, LintAttributeKind}; -use rustc_hir::{Attribute, Block, Expr, ExprKind, StmtKind}; +use rustc_hir::{Block, Expr, ExprKind, StmtKind}; use rustc_lexer::TokenKind; -use rustc_lint::{LateContext, LateLintPass}; +use rustc_lint::{LateContext, LateLintPass, Level}; use rustc_session::impl_lint_pass; use rustc_span::{BytePos, Span, Symbol}; @@ -238,24 +237,19 @@ impl CollapsibleIf { !span_contains_non_whitespace(cx, span, self.lint_commented_code) }, - [Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs))] => { - sub_attrs - .into_iter() - .filter(|attr| attr.kind == LintAttributeKind::Expect) - .flat_map(|attr| attr.lint_instances.iter().map(|group| (attr.attr_span, group))) - .filter(|(_, lint_id)| { - lint_id.tool_is_named(sym::clippy) - && (expected_lint_name == lint_id.lint_name() - || [expected_lint_name, sym::style, sym::all] - .contains(&lint_id.original_name_without_tool())) - }) - .any(|(attr_span, _)| { - // There is an `expect` attribute -- check that there is no _other_ significant text - let span_before_attr = inner_if.span.split_at(1).1.until(attr_span); - let span_after_attr = attr_span.between(inner_if_expr.span); - !span_contains_non_whitespace(cx, span_before_attr, self.lint_commented_code) - && !span_contains_non_whitespace(cx, span_after_attr, self.lint_commented_code) - }) + [attr] + if matches!(Level::from_attr(attr), Some((Level::Expect, _))) + && let Some(metas) = attr.meta_item_list() + && let Some(MetaItemInner::MetaItem(meta_item)) = metas.first() + && let [tool, lint_name] = meta_item.path.segments.as_slice() + && tool.ident.name == sym::clippy + && [expected_lint_name, sym::style, sym::all].contains(&lint_name.ident.name) => + { + // There is an `expect` attribute -- check that there is no _other_ significant text + let span_before_attr = inner_if.span.split_at(1).1.until(attr.span()); + let span_after_attr = attr.span().between(inner_if_expr.span); + !span_contains_non_whitespace(cx, span_before_attr, self.lint_commented_code) + && !span_contains_non_whitespace(cx, span_after_attr, self.lint_commented_code) }, // There are other attributes, which are significant tokens -- check failed diff --git a/src/tools/clippy/clippy_lints/src/returns/needless_return.rs b/src/tools/clippy/clippy_lints/src/returns/needless_return.rs index aab6adf5d19a4..04e4f379e37c1 100644 --- a/src/tools/clippy/clippy_lints/src/returns/needless_return.rs +++ b/src/tools/clippy/clippy_lints/src/returns/needless_return.rs @@ -4,11 +4,11 @@ use clippy_utils::{ binary_expr_needs_parentheses, is_from_proc_macro, leaks_droppable_temporary_with_limited_lifetime, span_contains_cfg, span_find_starting_semi, sym, }; +use rustc_ast::MetaItemInner; use rustc_errors::Applicability; -use rustc_hir::attrs::{AttributeKind, LintAttributeKind}; use rustc_hir::intravisit::FnKind; -use rustc_hir::{Attribute, Body, Expr, ExprKind, HirId, LangItem, MatchSource, StmtKind}; -use rustc_lint::{LateContext, LintContext}; +use rustc_hir::{Body, Expr, ExprKind, HirId, LangItem, MatchSource, StmtKind}; +use rustc_lint::{LateContext, Level, LintContext}; use rustc_middle::ty::{self, Ty}; use rustc_span::{BytePos, Pos, Span}; use std::borrow::Cow; @@ -180,18 +180,20 @@ fn check_final_expr<'tcx>( // actually fulfill the expectation (clippy::#12998) match cx.tcx.hir_attrs(expr.hir_id) { [] => {}, - [Attribute::Parsed(AttributeKind::LintAttributes(sub_attrs))] => { - if !sub_attrs - .into_iter() - .filter(|attr| attr.kind == LintAttributeKind::Expect) - .flat_map(|attr| &attr.lint_instances) - .any(|lint| { - matches!( - lint.original_name_without_tool(), - sym::needless_return | sym::style | sym::all | sym::warnings - ) - }) + [attr] => { + if matches!(Level::from_attr(attr), Some((Level::Expect, _))) + && let metas = attr.meta_item_list() + && let Some(lst) = metas + && let [MetaItemInner::MetaItem(meta_item), ..] = lst.as_slice() + && let [tool, lint_name] = meta_item.path.segments.as_slice() + && tool.ident.name == sym::clippy + && matches!( + lint_name.ident.name, + sym::needless_return | sym::style | sym::all | sym::warnings + ) { + // This is an expectation of the `needless_return` lint + } else { return; } }, diff --git a/src/tools/clippy/tests/ui/expect_tool_lint_rfc_2383.rs b/src/tools/clippy/tests/ui/expect_tool_lint_rfc_2383.rs index 68271048fcb93..82ac4db172d88 100644 --- a/src/tools/clippy/tests/ui/expect_tool_lint_rfc_2383.rs +++ b/src/tools/clippy/tests/ui/expect_tool_lint_rfc_2383.rs @@ -36,6 +36,8 @@ mod rustc_warn { #[expect(invalid_nan_comparisons)] //~^ ERROR: this lint expectation is unfulfilled + //~| NOTE: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + //~| ERROR: this lint expectation is unfulfilled let _b = x == 5; } } diff --git a/src/tools/clippy/tests/ui/expect_tool_lint_rfc_2383.stderr b/src/tools/clippy/tests/ui/expect_tool_lint_rfc_2383.stderr index aa187f351f458..b274d5c236933 100644 --- a/src/tools/clippy/tests/ui/expect_tool_lint_rfc_2383.stderr +++ b/src/tools/clippy/tests/ui/expect_tool_lint_rfc_2383.stderr @@ -14,28 +14,36 @@ LL | #[expect(invalid_nan_comparisons)] | ^^^^^^^^^^^^^^^^^^^^^^^ error: this lint expectation is unfulfilled - --> tests/ui/expect_tool_lint_rfc_2383.rs:108:14 + --> tests/ui/expect_tool_lint_rfc_2383.rs:37:18 + | +LL | #[expect(invalid_nan_comparisons)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: this lint expectation is unfulfilled + --> tests/ui/expect_tool_lint_rfc_2383.rs:110:14 | LL | #[expect(clippy::almost_swapped)] | ^^^^^^^^^^^^^^^^^^^^^^ error: this lint expectation is unfulfilled - --> tests/ui/expect_tool_lint_rfc_2383.rs:116:14 + --> tests/ui/expect_tool_lint_rfc_2383.rs:118:14 | LL | #[expect(clippy::bytes_nth)] | ^^^^^^^^^^^^^^^^^ error: this lint expectation is unfulfilled - --> tests/ui/expect_tool_lint_rfc_2383.rs:122:14 + --> tests/ui/expect_tool_lint_rfc_2383.rs:124:14 | LL | #[expect(clippy::if_same_then_else)] | ^^^^^^^^^^^^^^^^^^^^^^^^^ error: this lint expectation is unfulfilled - --> tests/ui/expect_tool_lint_rfc_2383.rs:128:14 + --> tests/ui/expect_tool_lint_rfc_2383.rs:130:14 | LL | #[expect(clippy::overly_complex_bool_expr)] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -error: aborting due to 6 previous errors +error: aborting due to 7 previous errors diff --git a/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr index 974c24bdc3bf3..592fdfbebd43a 100644 --- a/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr +++ b/src/tools/clippy/tests/ui/unknown_clippy_lints.stderr @@ -1,11 +1,23 @@ +error: unknown lint: `clippy::All` + --> tests/ui/unknown_clippy_lints.rs:3:10 + | +LL | #![allow(clippy::All)] + | ^^^^^^^^^^^ help: did you mean: `clippy::all` + | + = note: `-D unknown-lints` implied by `-D warnings` + = help: to override `-D warnings` add `#[allow(unknown_lints)]` + +error: unknown lint: `clippy::CMP_OWNED` + --> tests/ui/unknown_clippy_lints.rs:5:9 + | +LL | #![warn(clippy::CMP_OWNED)] + | ^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::cmp_owned` + error: unknown lint: `clippy::if_not_els` --> tests/ui/unknown_clippy_lints.rs:9:8 | LL | #[warn(clippy::if_not_els)] | ^^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::if_not_else` - | - = note: `-D unknown-lints` implied by `-D warnings` - = help: to override `-D warnings` add `#[allow(unknown_lints)]` error: unknown lint: `clippy::UNNecsaRy_cAst` --> tests/ui/unknown_clippy_lints.rs:11:8 @@ -55,17 +67,5 @@ LL - #[warn(clippy::missing_docs)] LL + #[warn(missing_docs)] | -error: unknown lint: `clippy::All` - --> tests/ui/unknown_clippy_lints.rs:3:10 - | -LL | #![allow(clippy::All)] - | ^^^^^^^^^^^ help: did you mean: `clippy::all` - -error: unknown lint: `clippy::CMP_OWNED` - --> tests/ui/unknown_clippy_lints.rs:5:9 - | -LL | #![warn(clippy::CMP_OWNED)] - | ^^^^^^^^^^^^^^^^^ help: did you mean: `clippy::cmp_owned` - error: aborting due to 9 previous errors diff --git a/tests/pretty/delegation-inherit-attributes.pp b/tests/pretty/delegation-inherit-attributes.pp index 2db27f3598cdb..242e7161aa84c 100644 --- a/tests/pretty/delegation-inherit-attributes.pp +++ b/tests/pretty/delegation-inherit-attributes.pp @@ -1,15 +1,14 @@ -#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, -lint_instances: [incomplete_features]}])] -#![attr = Feature([fn_delegation#0])] -extern crate std; -#[attr = PreludeImport] -use std::prelude::rust_2021::*; //@ edition:2021 //@ aux-crate:to_reuse_functions=to-reuse-functions.rs //@ pretty-mode:hir //@ pretty-compare-only //@ pp-exact:delegation-inherit-attributes.pp +#![allow(incomplete_features)] +#![attr = Feature([fn_delegation#0])] +extern crate std; +#[attr = PreludeImport] +use std::prelude::rust_2021::*; extern crate to_reuse_functions; diff --git a/tests/pretty/delegation-inline-attribute.pp b/tests/pretty/delegation-inline-attribute.pp index 4828f2e6c80f6..125ed1c298262 100644 --- a/tests/pretty/delegation-inline-attribute.pp +++ b/tests/pretty/delegation-inline-attribute.pp @@ -1,13 +1,12 @@ -#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, -lint_instances: [incomplete_features]}])] -#![attr = Feature([fn_delegation#0])] -extern crate std; -#[attr = PreludeImport] -use ::std::prelude::rust_2015::*; //@ pretty-compare-only //@ pretty-mode:hir //@ pp-exact:delegation-inline-attribute.pp +#![allow(incomplete_features)] +#![attr = Feature([fn_delegation#0])] +extern crate std; +#[attr = PreludeImport] +use ::std::prelude::rust_2015::*; mod to_reuse { fn foo(x: usize) -> usize { x } diff --git a/tests/pretty/hir-delegation.pp b/tests/pretty/hir-delegation.pp index 5337dd2e96dfa..28bb49458ce1d 100644 --- a/tests/pretty/hir-delegation.pp +++ b/tests/pretty/hir-delegation.pp @@ -1,13 +1,12 @@ -#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, -lint_instances: [incomplete_features]}])] -#![attr = Feature([fn_delegation#0])] -extern crate std; -#[attr = PreludeImport] -use ::std::prelude::rust_2015::*; //@ pretty-compare-only //@ pretty-mode:hir //@ pp-exact:hir-delegation.pp +#![allow(incomplete_features)] +#![attr = Feature([fn_delegation#0])] +extern crate std; +#[attr = PreludeImport] +use ::std::prelude::rust_2015::*; fn b(e: C) { } diff --git a/tests/pretty/hir-lifetimes.pp b/tests/pretty/hir-lifetimes.pp index 07ac4ccfdd450..c35a40eed0c50 100644 --- a/tests/pretty/hir-lifetimes.pp +++ b/tests/pretty/hir-lifetimes.pp @@ -1,19 +1,13 @@ -#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, -lint_instances: [unused_imports, unused_variables, unused_visibilities, -unused_assignments, dead_code, unused_mut, unreachable_code, -unreachable_patterns, unused_must_use, unused_unsafe, path_statements, -unused_attributes, unused_macros, unused_macro_rules, unused_allocation, -unused_doc_comments, unused_extern_crates, unused_features, unused_labels, -unused_parens, unused_braces, redundant_semicolons, map_unit_fn]}])] -extern crate std; -#[attr = PreludeImport] -use ::std::prelude::rust_2015::*; //@ pretty-compare-only //@ pretty-mode:hir //@ pp-exact:hir-lifetimes.pp // This tests the pretty-printing of lifetimes in lots of ways. +#![allow(unused)] +extern crate std; +#[attr = PreludeImport] +use ::std::prelude::rust_2015::*; struct Foo<'a> { x: &'a u32, diff --git a/tests/pretty/pin-ergonomics-hir.pp b/tests/pretty/pin-ergonomics-hir.pp index 5b024bfff3b59..6c9dec2bfb1fb 100644 --- a/tests/pretty/pin-ergonomics-hir.pp +++ b/tests/pretty/pin-ergonomics-hir.pp @@ -1,13 +1,12 @@ -#![attr = Feature([pin_ergonomics#0])] -#![attr = LintAttributes([LintAttribute {kind: Allow, attr_style: Inner, -lint_instances: [dead_code, incomplete_features]}])] -extern crate std; -#[attr = PreludeImport] -use ::std::prelude::rust_2015::*; //@ pretty-compare-only //@ pretty-mode:hir //@ pp-exact:pin-ergonomics-hir.pp +#![allow(dead_code, incomplete_features)] +#![attr = Feature([pin_ergonomics#0])] +extern crate std; +#[attr = PreludeImport] +use ::std::prelude::rust_2015::*; use std::pin::Pin; diff --git a/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.rs b/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.rs deleted file mode 100644 index 6fe663518ad68..0000000000000 --- a/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.rs +++ /dev/null @@ -1,12 +0,0 @@ -// compile-args: --crate-type lib - -// This file does not emit the rename warnings -// due to compilation aborting before we emit delayed lints - -#![deny(broken_intra_doc_links)] -//! [x] -//~^ ERROR unresolved link - -#![deny(rustdoc::non_autolinks)] -//! http://example.com -//~^ ERROR not a hyperlink diff --git a/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.stderr b/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.stderr deleted file mode 100644 index 484566587d9e2..0000000000000 --- a/tests/rustdoc-ui/lints/renamed-lint-still-applies-2.stderr +++ /dev/null @@ -1,32 +0,0 @@ -error: unresolved link to `x` - --> $DIR/renamed-lint-still-applies-2.rs:7:6 - | -LL | //! [x] - | ^ no item named `x` in scope - | - = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` -note: the lint level is defined here - --> $DIR/renamed-lint-still-applies-2.rs:6:9 - | -LL | #![deny(broken_intra_doc_links)] - | ^^^^^^^^^^^^^^^^^^^^^^ - -error: this URL is not a hyperlink - --> $DIR/renamed-lint-still-applies-2.rs:11:5 - | -LL | //! http://example.com - | ^^^^^^^^^^^^^^^^^^ - | - = note: bare URLs are not automatically turned into clickable links -note: the lint level is defined here - --> $DIR/renamed-lint-still-applies-2.rs:10:9 - | -LL | #![deny(rustdoc::non_autolinks)] - | ^^^^^^^^^^^^^^^^^^^^^^ -help: use an automatic link instead - | -LL | //! - | + + - -error: aborting due to 2 previous errors - diff --git a/tests/rustdoc-ui/lints/renamed-lint-still-applies.rs b/tests/rustdoc-ui/lints/renamed-lint-still-applies.rs index 8dded5460f124..a4d3a4b497117 100644 --- a/tests/rustdoc-ui/lints/renamed-lint-still-applies.rs +++ b/tests/rustdoc-ui/lints/renamed-lint-still-applies.rs @@ -1,7 +1,10 @@ -//@ check-pass // compile-args: --crate-type lib #![deny(broken_intra_doc_links)] //~^ WARNING renamed to `rustdoc::broken_intra_doc_links` +//! [x] +//~^ ERROR unresolved link #![deny(rustdoc::non_autolinks)] //~^ WARNING renamed to `rustdoc::bare_urls` +//! http://example.com +//~^ ERROR not a hyperlink diff --git a/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr b/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr index b9dde5fbc7fee..88807dfb495d0 100644 --- a/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr +++ b/tests/rustdoc-ui/lints/renamed-lint-still-applies.stderr @@ -1,5 +1,5 @@ warning: lint `broken_intra_doc_links` has been renamed to `rustdoc::broken_intra_doc_links` - --> $DIR/renamed-lint-still-applies.rs:3:9 + --> $DIR/renamed-lint-still-applies.rs:2:9 | LL | #![deny(broken_intra_doc_links)] | ^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `rustdoc::broken_intra_doc_links` @@ -7,10 +7,40 @@ LL | #![deny(broken_intra_doc_links)] = note: `#[warn(renamed_and_removed_lints)]` on by default warning: lint `rustdoc::non_autolinks` has been renamed to `rustdoc::bare_urls` - --> $DIR/renamed-lint-still-applies.rs:6:9 + --> $DIR/renamed-lint-still-applies.rs:7:9 | LL | #![deny(rustdoc::non_autolinks)] | ^^^^^^^^^^^^^^^^^^^^^^ help: use the new name: `rustdoc::bare_urls` -warning: 2 warnings emitted +error: unresolved link to `x` + --> $DIR/renamed-lint-still-applies.rs:4:6 + | +LL | //! [x] + | ^ no item named `x` in scope + | + = help: to escape `[` and `]` characters, add '\' before them like `\[` or `\]` +note: the lint level is defined here + --> $DIR/renamed-lint-still-applies.rs:2:9 + | +LL | #![deny(broken_intra_doc_links)] + | ^^^^^^^^^^^^^^^^^^^^^^ + +error: this URL is not a hyperlink + --> $DIR/renamed-lint-still-applies.rs:9:5 + | +LL | //! http://example.com + | ^^^^^^^^^^^^^^^^^^ + | + = note: bare URLs are not automatically turned into clickable links +note: the lint level is defined here + --> $DIR/renamed-lint-still-applies.rs:7:9 + | +LL | #![deny(rustdoc::non_autolinks)] + | ^^^^^^^^^^^^^^^^^^^^^^ +help: use an automatic link instead + | +LL | //! + | + + + +error: aborting due to 2 previous errors; 2 warnings emitted diff --git a/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.svg b/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.svg index af41631479cf5..549acee7cee54 100644 --- a/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.svg +++ b/tests/ui/argument-suggestions/wrong-highlight-span-extra-arguments-147070.svg @@ -1,4 +1,4 @@ - +