Port #[non_exhaustive] to the new attribute parsing infrastructure

This commit is contained in:
Jonathan Brouwer 2025-07-03 09:10:49 +02:00
parent 0c4fa2690d
commit 027126ce0b
No known key found for this signature in database
GPG key ID: 13619B051B673C52
17 changed files with 88 additions and 49 deletions

View file

@ -284,6 +284,9 @@ pub enum AttributeKind {
/// Represents `#[no_mangle]`
NoMangle(Span),
/// Represents `#[non_exhaustive]`
NonExhaustive(Span),
/// Represents `#[optimize(size|speed)]`
Optimize(OptimizeAttr, Span),

View file

@ -36,6 +36,7 @@ impl AttributeKind {
Naked(..) => No,
NoImplicitPrelude(..) => No,
NoMangle(..) => No,
NonExhaustive(..) => Yes,
Optimize(..) => No,
PassByValue(..) => Yes,
PubTransparent(..) => Yes,

View file

@ -36,6 +36,7 @@ pub(crate) mod lint_helpers;
pub(crate) mod loop_match;
pub(crate) mod must_use;
pub(crate) mod no_implicit_prelude;
pub(crate) mod non_exhaustive;
pub(crate) mod repr;
pub(crate) mod rustc_internal;
pub(crate) mod semantics;

View file

@ -0,0 +1,13 @@
use rustc_attr_data_structures::AttributeKind;
use rustc_span::{Span, Symbol, sym};
use crate::attributes::{NoArgsAttributeParser, OnDuplicate};
use crate::context::Stage;
pub(crate) struct NonExhaustiveParser;
impl<S: Stage> NoArgsAttributeParser<S> for NonExhaustiveParser {
const PATH: &[Symbol] = &[sym::non_exhaustive];
const ON_DUPLICATE: OnDuplicate<S> = OnDuplicate::Warn;
const CREATE: fn(Span) -> AttributeKind = AttributeKind::NonExhaustive;
}

View file

@ -27,6 +27,7 @@ use crate::attributes::lint_helpers::{AsPtrParser, PassByValueParser, PubTranspa
use crate::attributes::loop_match::{ConstContinueParser, LoopMatchParser};
use crate::attributes::must_use::MustUseParser;
use crate::attributes::no_implicit_prelude::NoImplicitPreludeParser;
use crate::attributes::non_exhaustive::NonExhaustiveParser;
use crate::attributes::repr::{AlignParser, ReprParser};
use crate::attributes::rustc_internal::{
RustcLayoutScalarValidRangeEnd, RustcLayoutScalarValidRangeStart,
@ -144,6 +145,7 @@ attribute_parsers!(
Single<WithoutArgs<MayDangleParser>>,
Single<WithoutArgs<NoImplicitPreludeParser>>,
Single<WithoutArgs<NoMangleParser>>,
Single<WithoutArgs<NonExhaustiveParser>>,
Single<WithoutArgs<PassByValueParser>>,
Single<WithoutArgs<PubTransparentParser>>,
Single<WithoutArgs<TrackCallerParser>>,

View file

@ -779,9 +779,11 @@ fn lower_variant<'tcx>(
fields,
parent_did.to_def_id(),
recovered,
adt_kind == AdtKind::Struct && tcx.has_attr(parent_did, sym::non_exhaustive)
|| variant_did
.is_some_and(|variant_did| tcx.has_attr(variant_did, sym::non_exhaustive)),
adt_kind == AdtKind::Struct
&& find_attr!(tcx.get_all_attrs(parent_did), AttributeKind::NonExhaustive(..))
|| variant_did.is_some_and(|variant_did| {
find_attr!(tcx.get_all_attrs(variant_did), AttributeKind::NonExhaustive(..))
}),
)
}

View file

@ -4,6 +4,7 @@ use std::ops::Range;
use std::str;
use rustc_abi::{FIRST_VARIANT, ReprOptions, VariantIdx};
use rustc_attr_data_structures::{AttributeKind, find_attr};
use rustc_data_structures::fingerprint::Fingerprint;
use rustc_data_structures::fx::FxHashMap;
use rustc_data_structures::intern::Interned;
@ -278,7 +279,9 @@ impl AdtDefData {
debug!("AdtDef::new({:?}, {:?}, {:?}, {:?})", did, kind, variants, repr);
let mut flags = AdtFlags::NO_ADT_FLAGS;
if kind == AdtKind::Enum && tcx.has_attr(did, sym::non_exhaustive) {
if kind == AdtKind::Enum
&& find_attr!(tcx.get_all_attrs(did), AttributeKind::NonExhaustive(..))
{
debug!("found non-exhaustive variant list for {:?}", did);
flags = flags | AdtFlags::IS_VARIANT_LIST_NON_EXHAUSTIVE;
}

View file

@ -303,6 +303,7 @@ fn emit_malformed_attribute(
| sym::rustc_allow_const_fn_unstable
| sym::naked
| sym::no_mangle
| sym::non_exhaustive
| sym::must_use
| sym::track_caller
| sym::link_name

View file

@ -194,6 +194,9 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
Attribute::Parsed(AttributeKind::TrackCaller(attr_span)) => {
self.check_track_caller(hir_id, *attr_span, attrs, span, target)
}
Attribute::Parsed(AttributeKind::NonExhaustive(attr_span)) => {
self.check_non_exhaustive(hir_id, *attr_span, span, target, item)
}
Attribute::Parsed(
AttributeKind::RustcLayoutScalarValidRangeStart(_num, attr_span)
| AttributeKind::RustcLayoutScalarValidRangeEnd(_num, attr_span),
@ -237,7 +240,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
[sym::no_sanitize, ..] => {
self.check_no_sanitize(attr, span, target)
}
[sym::non_exhaustive, ..] => self.check_non_exhaustive(hir_id, attr, span, target, item),
[sym::marker, ..] => self.check_marker(hir_id, attr, span, target),
[sym::thread_local, ..] => self.check_thread_local(attr, span, target),
[sym::doc, ..] => self.check_doc_attrs(
@ -779,7 +781,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
fn check_non_exhaustive(
&self,
hir_id: HirId,
attr: &Attribute,
attr_span: Span,
span: Span,
target: Target,
item: Option<ItemLike<'_>>,
@ -794,7 +796,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
&& fields.iter().any(|f| f.default.is_some())
{
self.dcx().emit_err(errors::NonExhaustiveWithDefaultFieldValues {
attr_span: attr.span(),
attr_span,
defn_span: span,
});
}
@ -805,13 +807,11 @@ impl<'tcx> CheckAttrVisitor<'tcx> {
// erroneously allowed it and some crates used it accidentally, to be compatible
// with crates depending on them, we can't throw an error here.
Target::Field | Target::Arm | Target::MacroDef => {
self.inline_attr_str_error_with_macro_def(hir_id, attr.span(), "non_exhaustive");
self.inline_attr_str_error_with_macro_def(hir_id, attr_span, "non_exhaustive");
}
_ => {
self.dcx().emit_err(errors::NonExhaustiveWrongLocation {
attr_span: attr.span(),
defn_span: span,
});
self.dcx()
.emit_err(errors::NonExhaustiveWrongLocation { attr_span, defn_span: span });
}
}
}

View file

@ -5,7 +5,7 @@ use rustc_ast::{
self as ast, CRATE_NODE_ID, Crate, ItemKind, MetaItemInner, MetaItemKind, ModKind, NodeId, Path,
};
use rustc_ast_pretty::pprust;
use rustc_attr_data_structures::{self as attr, Stability};
use rustc_attr_data_structures::{self as attr, AttributeKind, Stability, find_attr};
use rustc_data_structures::fx::{FxHashMap, FxHashSet};
use rustc_data_structures::unord::{UnordMap, UnordSet};
use rustc_errors::codes::*;
@ -1998,9 +1998,9 @@ impl<'ra, 'tcx> Resolver<'ra, 'tcx> {
// Otherwise, point out if the struct has any private fields.
if let Some(def_id) = res.opt_def_id()
&& !def_id.is_local()
&& let Some(attr) = self.tcx.get_attr(def_id, sym::non_exhaustive)
&& let Some(attr_span) = find_attr!(self.tcx.get_all_attrs(def_id), AttributeKind::NonExhaustive(span) => *span)
{
non_exhaustive = Some(attr.span());
non_exhaustive = Some(attr_span);
} else if let Some(span) = ctor_fields_span {
let label = errors::ConstructorPrivateIfAnyFieldPrivate { span };
err.subdiagnostic(label);

View file

@ -621,7 +621,7 @@ impl Item {
}
pub(crate) fn is_non_exhaustive(&self) -> bool {
self.attrs.other_attrs.iter().any(|a| a.has_name(sym::non_exhaustive))
find_attr!(&self.attrs.other_attrs, AttributeKind::NonExhaustive(..))
}
/// Returns a documentation-level item type from the item.
@ -776,6 +776,8 @@ impl Item {
} else if let hir::Attribute::Parsed(AttributeKind::ExportName { name, .. }) = attr
{
Some(format!("#[export_name = \"{name}\"]"))
} else if let hir::Attribute::Parsed(AttributeKind::NonExhaustive(..)) = attr {
Some("#[non_exhaustive]".to_string())
} else if is_json {
match attr {
// rustdoc-json stores this in `Item::deprecation`, so we

View file

@ -4,7 +4,9 @@ use rustc_errors::Applicability;
use rustc_hir::{Item, ItemKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::declare_lint_pass;
use rustc_span::sym;
use rustc_attr_data_structures::AttributeKind;
use rustc_attr_data_structures::find_attr;
declare_clippy_lint! {
/// ### What it does
@ -85,7 +87,7 @@ impl LateLintPass<'_> for ExhaustiveItems {
};
if cx.effective_visibilities.is_exported(item.owner_id.def_id)
&& let attrs = cx.tcx.hir_attrs(item.hir_id())
&& !attrs.iter().any(|a| a.has_name(sym::non_exhaustive))
&& !find_attr!(attrs, AttributeKind::NonExhaustive(..))
&& fields.iter().all(|f| cx.tcx.visibility(f.def_id).is_public())
{
span_lint_and_then(cx, lint, item.span, msg, |diag| {

View file

@ -4,7 +4,6 @@ use clippy_utils::is_doc_hidden;
use clippy_utils::msrvs::{self, Msrv};
use clippy_utils::source::snippet_indent;
use itertools::Itertools;
use rustc_ast::attr;
use rustc_data_structures::fx::FxHashSet;
use rustc_errors::Applicability;
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
@ -12,7 +11,9 @@ use rustc_hir::{Expr, ExprKind, Item, ItemKind, QPath, TyKind, VariantData};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::LocalDefId;
use rustc_span::{Span, sym};
use rustc_span::Span;
use rustc_attr_data_structures::find_attr;
use rustc_attr_data_structures::AttributeKind;
declare_clippy_lint! {
/// ### What it does
@ -93,7 +94,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive {
.then_some((v.def_id, v.span))
});
if let Ok((id, span)) = iter.exactly_one()
&& !attr::contains_name(cx.tcx.hir_attrs(item.hir_id()), sym::non_exhaustive)
&& !find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::NonExhaustive(..))
{
self.potential_enums.push((item.owner_id.def_id, id, item.span, span));
}
@ -113,10 +114,10 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive {
item.span,
"this seems like a manual implementation of the non-exhaustive pattern",
|diag| {
if let Some(non_exhaustive) =
attr::find_by_name(cx.tcx.hir_attrs(item.hir_id()), sym::non_exhaustive)
if let Some(non_exhaustive_span) =
find_attr!(cx.tcx.hir_attrs(item.hir_id()), AttributeKind::NonExhaustive(span) => *span)
{
diag.span_note(non_exhaustive.span(), "the struct is already non-exhaustive");
diag.span_note(non_exhaustive_span, "the struct is already non-exhaustive");
} else {
let indent = snippet_indent(cx, item.span).unwrap_or_default();
diag.span_suggestion_verbose(

View file

@ -7,9 +7,10 @@ use rustc_middle::ty::{AdtDef, TyCtxt};
use rustc_session::Session;
use rustc_span::{Span, Symbol};
use std::str::FromStr;
use rustc_attr_data_structures::find_attr;
use crate::source::SpanRangeExt;
use crate::{sym, tokenize_with_text};
use rustc_attr_data_structures::AttributeKind;
/// Deprecation status of attributes known by Clippy.
pub enum DeprecationStatus {
@ -165,13 +166,13 @@ pub fn is_doc_hidden(attrs: &[impl AttributeExt]) -> bool {
pub fn has_non_exhaustive_attr(tcx: TyCtxt<'_>, adt: AdtDef<'_>) -> bool {
adt.is_variant_list_non_exhaustive()
|| tcx.has_attr(adt.did(), sym::non_exhaustive)
|| find_attr!(tcx.get_all_attrs(adt.did()), AttributeKind::NonExhaustive(..))
|| adt.variants().iter().any(|variant_def| {
variant_def.is_field_list_non_exhaustive() || tcx.has_attr(variant_def.def_id, sym::non_exhaustive)
variant_def.is_field_list_non_exhaustive() || find_attr!(tcx.get_all_attrs(variant_def.def_id), AttributeKind::NonExhaustive(..))
})
|| adt
.all_fields()
.any(|field_def| tcx.has_attr(field_def.did, sym::non_exhaustive))
.any(|field_def| find_attr!(tcx.get_all_attrs(field_def.did), AttributeKind::NonExhaustive(..)))
}
/// Checks if the given span contains a `#[cfg(..)]` attribute

View file

@ -206,12 +206,6 @@ error: malformed `automatically_derived` attribute input
LL | #[automatically_derived = 18]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[automatically_derived]`
error: malformed `non_exhaustive` attribute input
--> $DIR/malformed-attrs.rs:196:1
|
LL | #[non_exhaustive = 1]
| ^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[non_exhaustive]`
error: malformed `thread_local` attribute input
--> $DIR/malformed-attrs.rs:202:1
|
@ -552,6 +546,15 @@ LL | #[rustc_layout_scalar_valid_range_end]
| expected this to be a list
| help: must be of the form: `#[rustc_layout_scalar_valid_range_end(end)]`
error[E0565]: malformed `non_exhaustive` attribute input
--> $DIR/malformed-attrs.rs:196:1
|
LL | #[non_exhaustive = 1]
| ^^^^^^^^^^^^^^^^^---^
| | |
| | didn't expect any arguments here
| help: must be of the form: `#[non_exhaustive]`
error: attribute should be applied to `const fn`
--> $DIR/malformed-attrs.rs:34:1
|

View file

@ -65,18 +65,6 @@ LL | #[should_panic]
| ^^^^^^^^^^^^^^^
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
error: unused attribute
--> $DIR/unused-attr-duplicate.rs:66:1
|
LL | #[non_exhaustive]
| ^^^^^^^^^^^^^^^^^ help: remove this attribute
|
note: attribute also specified here
--> $DIR/unused-attr-duplicate.rs:65:1
|
LL | #[non_exhaustive]
| ^^^^^^^^^^^^^^^^^
error: unused attribute
--> $DIR/unused-attr-duplicate.rs:70:1
|
@ -190,6 +178,18 @@ LL | #[must_use]
| ^^^^^^^^^^^
= warning: this was previously accepted by the compiler but is being phased out; it will become a hard error in a future release!
error: unused attribute
--> $DIR/unused-attr-duplicate.rs:66:1
|
LL | #[non_exhaustive]
| ^^^^^^^^^^^^^^^^^ help: remove this attribute
|
note: attribute also specified here
--> $DIR/unused-attr-duplicate.rs:65:1
|
LL | #[non_exhaustive]
| ^^^^^^^^^^^^^^^^^
error: unused attribute
--> $DIR/unused-attr-duplicate.rs:74:1
|

View file

@ -1,8 +1,11 @@
error: malformed `non_exhaustive` attribute input
error[E0565]: malformed `non_exhaustive` attribute input
--> $DIR/invalid-attribute.rs:1:1
|
LL | #[non_exhaustive(anything)]
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: must be of the form: `#[non_exhaustive]`
| ^^^^^^^^^^^^^^^^----------^
| | |
| | didn't expect any arguments here
| help: must be of the form: `#[non_exhaustive]`
error[E0701]: attribute should be applied to a struct or enum
--> $DIR/invalid-attribute.rs:5:1
@ -27,4 +30,5 @@ LL | | }
error: aborting due to 3 previous errors
For more information about this error, try `rustc --explain E0701`.
Some errors have detailed explanations: E0565, E0701.
For more information about an error, try `rustc --explain E0565`.