Skip to content

Commit c41be9e

Browse files
committed
Auto merge of rust-lang#13345 - Alexendoo:manual-non-exhaustive-visibility, r=y21
Only lint `manual_non_exhaustive` for exported types For types that are not exported the attribute doesn't make a difference, but the manual pattern can still be used to achieve module level non exhaustiveness Fixes rust-lang/rust-clippy#10301 Fixes rust-lang/rust-clippy#12106 changelog: none
2 parents a95afe2 + f7f5505 commit c41be9e

6 files changed

+166
-179
lines changed

clippy_lints/src/lib.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -634,8 +634,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
634634
let format_args = format_args_storage.clone();
635635
store.register_late_pass(move |_| Box::new(methods::Methods::new(conf, format_args.clone())));
636636
store.register_late_pass(move |_| Box::new(matches::Matches::new(conf)));
637-
store.register_early_pass(move || Box::new(manual_non_exhaustive::ManualNonExhaustiveStruct::new(conf)));
638-
store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustiveEnum::new(conf)));
637+
store.register_late_pass(move |_| Box::new(manual_non_exhaustive::ManualNonExhaustive::new(conf)));
639638
store.register_late_pass(move |_| Box::new(manual_strip::ManualStrip::new(conf)));
640639
store.register_early_pass(move || Box::new(redundant_static_lifetimes::RedundantStaticLifetimes::new(conf)));
641640
store.register_early_pass(move || Box::new(redundant_field_names::RedundantFieldNames::new(conf)));

clippy_lints/src/manual_non_exhaustive.rs

+73-109
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,16 @@ use clippy_config::msrvs::{self, Msrv};
22
use clippy_config::Conf;
33
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
44
use clippy_utils::is_doc_hidden;
5-
use clippy_utils::source::SpanRangeExt;
6-
use rustc_ast::ast::{self, VisibilityKind};
5+
use clippy_utils::source::snippet_indent;
6+
use itertools::Itertools;
77
use rustc_ast::attr;
88
use rustc_data_structures::fx::FxHashSet;
99
use rustc_errors::Applicability;
1010
use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
11-
use rustc_hir::{self as hir, Expr, ExprKind, QPath};
12-
use rustc_lint::{EarlyContext, EarlyLintPass, LateContext, LateLintPass, LintContext};
11+
use rustc_hir::{Expr, ExprKind, Item, ItemKind, QPath, TyKind, VariantData};
12+
use rustc_lint::{LateContext, LateLintPass};
1313
use rustc_session::impl_lint_pass;
14-
use rustc_span::def_id::{DefId, LocalDefId};
14+
use rustc_span::def_id::LocalDefId;
1515
use rustc_span::{sym, Span};
1616

1717
declare_clippy_lint! {
@@ -62,29 +62,13 @@ declare_clippy_lint! {
6262
"manual implementations of the non-exhaustive pattern can be simplified using #[non_exhaustive]"
6363
}
6464

65-
#[expect(clippy::module_name_repetitions)]
66-
pub struct ManualNonExhaustiveStruct {
65+
pub struct ManualNonExhaustive {
6766
msrv: Msrv,
68-
}
69-
70-
impl ManualNonExhaustiveStruct {
71-
pub fn new(conf: &'static Conf) -> Self {
72-
Self {
73-
msrv: conf.msrv.clone(),
74-
}
75-
}
76-
}
77-
78-
impl_lint_pass!(ManualNonExhaustiveStruct => [MANUAL_NON_EXHAUSTIVE]);
79-
80-
#[expect(clippy::module_name_repetitions)]
81-
pub struct ManualNonExhaustiveEnum {
82-
msrv: Msrv,
83-
constructed_enum_variants: FxHashSet<(DefId, DefId)>,
67+
constructed_enum_variants: FxHashSet<LocalDefId>,
8468
potential_enums: Vec<(LocalDefId, LocalDefId, Span, Span)>,
8569
}
8670

87-
impl ManualNonExhaustiveEnum {
71+
impl ManualNonExhaustive {
8872
pub fn new(conf: &'static Conf) -> Self {
8973
Self {
9074
msrv: conf.msrv.clone(),
@@ -94,96 +78,78 @@ impl ManualNonExhaustiveEnum {
9478
}
9579
}
9680

97-
impl_lint_pass!(ManualNonExhaustiveEnum => [MANUAL_NON_EXHAUSTIVE]);
98-
99-
impl EarlyLintPass for ManualNonExhaustiveStruct {
100-
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &ast::Item) {
101-
if let ast::ItemKind::Struct(variant_data, _) = &item.kind
102-
&& let (fields, delimiter) = match variant_data {
103-
ast::VariantData::Struct { fields, .. } => (&**fields, '{'),
104-
ast::VariantData::Tuple(fields, _) => (&**fields, '('),
105-
ast::VariantData::Unit(_) => return,
106-
}
107-
&& fields.len() > 1
108-
&& self.msrv.meets(msrvs::NON_EXHAUSTIVE)
109-
{
110-
let mut iter = fields.iter().filter_map(|f| match f.vis.kind {
111-
VisibilityKind::Public => None,
112-
VisibilityKind::Inherited => Some(Ok(f)),
113-
VisibilityKind::Restricted { .. } => Some(Err(())),
114-
});
115-
if let Some(Ok(field)) = iter.next()
116-
&& iter.next().is_none()
117-
&& field.ty.kind.is_unit()
118-
{
119-
span_lint_and_then(
120-
cx,
121-
MANUAL_NON_EXHAUSTIVE,
122-
item.span,
123-
"this seems like a manual implementation of the non-exhaustive pattern",
124-
|diag| {
125-
if !item.attrs.iter().any(|attr| attr.has_name(sym::non_exhaustive))
126-
&& let header_span = cx.sess().source_map().span_until_char(item.span, delimiter)
127-
&& let Some(snippet) = header_span.get_source_text(cx)
128-
{
129-
diag.span_suggestion(
130-
header_span,
131-
"add the attribute",
132-
format!("#[non_exhaustive] {snippet}"),
133-
Applicability::Unspecified,
134-
);
135-
}
136-
diag.span_help(field.span, "remove this field");
137-
},
138-
);
139-
}
140-
}
141-
}
81+
impl_lint_pass!(ManualNonExhaustive => [MANUAL_NON_EXHAUSTIVE]);
14282

143-
extract_msrv_attr!(EarlyContext);
144-
}
145-
146-
impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
147-
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) {
148-
if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) {
83+
impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustive {
84+
fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx Item<'_>) {
85+
if !self.msrv.meets(msrvs::NON_EXHAUSTIVE) || !cx.effective_visibilities.is_exported(item.owner_id.def_id) {
14986
return;
15087
}
15188

152-
if let hir::ItemKind::Enum(def, _) = &item.kind
153-
&& def.variants.len() > 1
154-
{
155-
let mut iter = def.variants.iter().filter_map(|v| {
156-
(matches!(v.data, hir::VariantData::Unit(_, _))
157-
&& is_doc_hidden(cx.tcx.hir().attrs(v.hir_id))
158-
&& !attr::contains_name(cx.tcx.hir().attrs(item.hir_id()), sym::non_exhaustive))
159-
.then_some((v.def_id, v.span))
160-
});
161-
if let Some((id, span)) = iter.next()
162-
&& iter.next().is_none()
163-
{
164-
self.potential_enums.push((item.owner_id.def_id, id, item.span, span));
165-
}
89+
match item.kind {
90+
ItemKind::Enum(def, _) if def.variants.len() > 1 => {
91+
let iter = def.variants.iter().filter_map(|v| {
92+
(matches!(v.data, VariantData::Unit(_, _)) && is_doc_hidden(cx.tcx.hir().attrs(v.hir_id)))
93+
.then_some((v.def_id, v.span))
94+
});
95+
if let Ok((id, span)) = iter.exactly_one()
96+
&& !attr::contains_name(cx.tcx.hir().attrs(item.hir_id()), sym::non_exhaustive)
97+
{
98+
self.potential_enums.push((item.owner_id.def_id, id, item.span, span));
99+
}
100+
},
101+
ItemKind::Struct(variant_data, _) => {
102+
let fields = variant_data.fields();
103+
let private_fields = fields
104+
.iter()
105+
.filter(|field| !cx.effective_visibilities.is_exported(field.def_id));
106+
if fields.len() > 1
107+
&& let Ok(field) = private_fields.exactly_one()
108+
&& let TyKind::Tup([]) = field.ty.kind
109+
{
110+
span_lint_and_then(
111+
cx,
112+
MANUAL_NON_EXHAUSTIVE,
113+
item.span,
114+
"this seems like a manual implementation of the non-exhaustive pattern",
115+
|diag| {
116+
if let Some(non_exhaustive) =
117+
attr::find_by_name(cx.tcx.hir().attrs(item.hir_id()), sym::non_exhaustive)
118+
{
119+
diag.span_note(non_exhaustive.span, "the struct is already non-exhaustive");
120+
} else {
121+
let indent = snippet_indent(cx, item.span).unwrap_or_default();
122+
diag.span_suggestion_verbose(
123+
item.span.shrink_to_lo(),
124+
"use the `#[non_exhaustive]` attribute instead",
125+
format!("#[non_exhaustive]\n{indent}"),
126+
Applicability::MaybeIncorrect,
127+
);
128+
}
129+
diag.span_help(field.span, "remove this field");
130+
},
131+
);
132+
}
133+
},
134+
_ => {},
166135
}
167136
}
168137

169138
fn check_expr(&mut self, cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) {
170139
if let ExprKind::Path(QPath::Resolved(None, p)) = &e.kind
171-
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), id) = p.res
140+
&& let Res::Def(DefKind::Ctor(CtorOf::Variant, CtorKind::Const), ctor_id) = p.res
141+
&& let Some(local_ctor) = ctor_id.as_local()
172142
{
173-
let variant_id = cx.tcx.parent(id);
174-
let enum_id = cx.tcx.parent(variant_id);
175-
176-
self.constructed_enum_variants.insert((enum_id, variant_id));
143+
let variant_id = cx.tcx.local_parent(local_ctor);
144+
self.constructed_enum_variants.insert(variant_id);
177145
}
178146
}
179147

180148
fn check_crate_post(&mut self, cx: &LateContext<'tcx>) {
181-
for &(enum_id, _, enum_span, variant_span) in
182-
self.potential_enums.iter().filter(|&&(enum_id, variant_id, _, _)| {
183-
!self
184-
.constructed_enum_variants
185-
.contains(&(enum_id.to_def_id(), variant_id.to_def_id()))
186-
})
149+
for &(enum_id, _, enum_span, variant_span) in self
150+
.potential_enums
151+
.iter()
152+
.filter(|(_, variant_id, _, _)| !self.constructed_enum_variants.contains(variant_id))
187153
{
188154
let hir_id = cx.tcx.local_def_id_to_hir_id(enum_id);
189155
span_lint_hir_and_then(
@@ -193,15 +159,13 @@ impl<'tcx> LateLintPass<'tcx> for ManualNonExhaustiveEnum {
193159
enum_span,
194160
"this seems like a manual implementation of the non-exhaustive pattern",
195161
|diag| {
196-
let header_span = cx.sess().source_map().span_until_char(enum_span, '{');
197-
if let Some(snippet) = header_span.get_source_text(cx) {
198-
diag.span_suggestion(
199-
header_span,
200-
"add the attribute",
201-
format!("#[non_exhaustive] {snippet}"),
202-
Applicability::Unspecified,
203-
);
204-
}
162+
let indent = snippet_indent(cx, enum_span).unwrap_or_default();
163+
diag.span_suggestion_verbose(
164+
enum_span.shrink_to_lo(),
165+
"use the `#[non_exhaustive]` attribute instead",
166+
format!("#[non_exhaustive]\n{indent}"),
167+
Applicability::MaybeIncorrect,
168+
);
205169
diag.span_help(variant_span, "remove this variant");
206170
},
207171
);
+11-13
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
#![warn(clippy::manual_non_exhaustive)]
22
#![allow(unused)]
33
//@no-rustfix
4-
enum E {
5-
//~^ ERROR: this seems like a manual implementation of the non-exhaustive pattern
4+
pub enum E {
5+
//~^ manual_non_exhaustive
66
A,
77
B,
88
#[doc(hidden)]
@@ -11,44 +11,44 @@ enum E {
1111

1212
// if the user explicitly marks as nonexhaustive we shouldn't warn them
1313
#[non_exhaustive]
14-
enum Ep {
14+
pub enum Ep {
1515
A,
1616
B,
1717
#[doc(hidden)]
1818
_C,
1919
}
2020

2121
// marker variant does not have doc hidden attribute, should be ignored
22-
enum NoDocHidden {
22+
pub enum NoDocHidden {
2323
A,
2424
B,
2525
_C,
2626
}
2727

2828
// name of variant with doc hidden does not start with underscore
29-
enum NoUnderscore {
29+
pub enum NoUnderscore {
3030
A,
3131
B,
3232
#[doc(hidden)]
3333
C,
3434
}
3535

3636
// variant with doc hidden is not unit, should be ignored
37-
enum NotUnit {
37+
pub enum NotUnit {
3838
A,
3939
B,
4040
#[doc(hidden)]
4141
_C(bool),
4242
}
4343

4444
// variant with doc hidden is the only one, should be ignored
45-
enum OnlyMarker {
45+
pub enum OnlyMarker {
4646
#[doc(hidden)]
4747
_A,
4848
}
4949

5050
// variant with multiple markers, should be ignored
51-
enum MultipleMarkers {
51+
pub enum MultipleMarkers {
5252
A,
5353
#[doc(hidden)]
5454
_B,
@@ -58,13 +58,13 @@ enum MultipleMarkers {
5858

5959
// already non_exhaustive and no markers, should be ignored
6060
#[non_exhaustive]
61-
enum NonExhaustive {
61+
pub enum NonExhaustive {
6262
A,
6363
B,
6464
}
6565

6666
// marked is used, don't lint
67-
enum UsedHidden {
67+
pub enum UsedHidden {
6868
#[doc(hidden)]
6969
_A,
7070
B,
@@ -77,11 +77,9 @@ fn foo(x: &mut UsedHidden) {
7777
}
7878

7979
#[expect(clippy::manual_non_exhaustive)]
80-
enum ExpectLint {
80+
pub enum ExpectLint {
8181
A,
8282
B,
8383
#[doc(hidden)]
8484
_C,
8585
}
86-
87-
fn main() {}

tests/ui/manual_non_exhaustive_enum.stderr

+12-10
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,7 @@
11
error: this seems like a manual implementation of the non-exhaustive pattern
22
--> tests/ui/manual_non_exhaustive_enum.rs:4:1
33
|
4-
LL | enum E {
5-
| ^-----
6-
| |
7-
| _help: add the attribute: `#[non_exhaustive] enum E`
8-
| |
4+
LL | / pub enum E {
95
LL | |
106
LL | | A,
117
LL | | B,
@@ -21,15 +17,16 @@ LL | _C,
2117
| ^^
2218
= note: `-D clippy::manual-non-exhaustive` implied by `-D warnings`
2319
= help: to override `-D warnings` add `#[allow(clippy::manual_non_exhaustive)]`
20+
help: use the `#[non_exhaustive]` attribute instead
21+
|
22+
LL + #[non_exhaustive]
23+
LL | pub enum E {
24+
|
2425

2526
error: this seems like a manual implementation of the non-exhaustive pattern
2627
--> tests/ui/manual_non_exhaustive_enum.rs:29:1
2728
|
28-
LL | enum NoUnderscore {
29-
| ^----------------
30-
| |
31-
| _help: add the attribute: `#[non_exhaustive] enum NoUnderscore`
32-
| |
29+
LL | / pub enum NoUnderscore {
3330
LL | | A,
3431
LL | | B,
3532
LL | | #[doc(hidden)]
@@ -42,6 +39,11 @@ help: remove this variant
4239
|
4340
LL | C,
4441
| ^
42+
help: use the `#[non_exhaustive]` attribute instead
43+
|
44+
LL + #[non_exhaustive]
45+
LL | pub enum NoUnderscore {
46+
|
4547

4648
error: aborting due to 2 previous errors
4749

0 commit comments

Comments
 (0)