Skip to content

Commit cf507a9

Browse files
committed
Do not lint reachable enums and enum variants used as functions in the same crate
1 parent 3e84ca8 commit cf507a9

5 files changed

+245
-38
lines changed

clippy_lints/src/empty_with_brackets.rs

+128-20
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
1-
use clippy_utils::diagnostics::span_lint_and_then;
1+
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
22
use clippy_utils::source::snippet_opt;
3-
use rustc_ast::ast::{Item, ItemKind, Variant, VariantData};
3+
use rustc_data_structures::fx::FxIndexMap;
44
use rustc_errors::Applicability;
5+
use rustc_hir::def::CtorOf;
6+
use rustc_hir::def::DefKind::Ctor;
7+
use rustc_hir::def::Res::Def;
8+
use rustc_hir::def_id::LocalDefId;
9+
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Path, QPath, Variant, VariantData};
510
use rustc_lexer::TokenKind;
6-
use rustc_lint::{EarlyContext, EarlyLintPass};
7-
use rustc_session::declare_lint_pass;
11+
use rustc_lint::{LateContext, LateLintPass};
12+
use rustc_middle::ty::TyCtxt;
13+
use rustc_session::impl_lint_pass;
814
use rustc_span::Span;
915

1016
declare_clippy_lint! {
@@ -70,10 +76,22 @@ declare_clippy_lint! {
7076
"finds enum variants with empty brackets"
7177
}
7278

73-
declare_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]);
79+
#[derive(Debug)]
80+
enum Usage {
81+
Unused { redundant_use_sites: Vec<Span> },
82+
Used,
83+
}
84+
85+
#[derive(Default)]
86+
pub struct EmptyWithBrackets {
87+
// Value holds `true` if the empty tuple variant was used as a function
88+
empty_tuple_enum_variants: FxIndexMap<LocalDefId, Usage>,
89+
}
90+
91+
impl_lint_pass!(EmptyWithBrackets => [EMPTY_STRUCTS_WITH_BRACKETS, EMPTY_ENUM_VARIANTS_WITH_BRACKETS]);
7492

75-
impl EarlyLintPass for EmptyWithBrackets {
76-
fn check_item(&mut self, cx: &EarlyContext<'_>, item: &Item) {
93+
impl LateLintPass<'_> for EmptyWithBrackets {
94+
fn check_item(&mut self, cx: &LateContext<'_>, item: &Item<'_>) {
7795
let span_after_ident = item.span.with_lo(item.ident.span.hi());
7896

7997
if let ItemKind::Struct(var_data, _) = &item.kind
@@ -97,22 +115,86 @@ impl EarlyLintPass for EmptyWithBrackets {
97115
}
98116
}
99117

100-
fn check_variant(&mut self, cx: &EarlyContext<'_>, variant: &Variant) {
118+
fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) {
119+
// Don't lint pub enums
120+
if cx.effective_visibilities.is_reachable(variant.def_id) {
121+
return;
122+
}
123+
101124
let span_after_ident = variant.span.with_lo(variant.ident.span.hi());
102125

103-
if has_brackets(&variant.data) && has_no_fields(cx, &variant.data, span_after_ident) {
104-
span_lint_and_then(
126+
if has_no_fields(cx, &variant.data, span_after_ident) {
127+
match variant.data {
128+
VariantData::Struct { .. } => {
129+
span_lint_and_then(
130+
cx,
131+
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
132+
span_after_ident,
133+
"enum variant has empty brackets",
134+
|diagnostic| {
135+
diagnostic.span_suggestion_hidden(
136+
span_after_ident,
137+
"remove the brackets",
138+
"",
139+
Applicability::MaybeIncorrect,
140+
);
141+
},
142+
);
143+
},
144+
VariantData::Tuple(.., local_def_id) => {
145+
self.empty_tuple_enum_variants
146+
.entry(local_def_id)
147+
.or_insert(Usage::Unused {
148+
redundant_use_sites: vec![],
149+
});
150+
},
151+
VariantData::Unit(..) => {},
152+
}
153+
}
154+
}
155+
156+
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
157+
if let Some(def_id) = check_expr_for_enum_as_function(expr) {
158+
if let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr)
159+
&& let Some(Usage::Unused {
160+
ref mut redundant_use_sites,
161+
}) = self.empty_tuple_enum_variants.get_mut(&def_id)
162+
{
163+
redundant_use_sites.push(parentheses_span);
164+
} else {
165+
self.empty_tuple_enum_variants.insert(def_id, Usage::Used);
166+
}
167+
}
168+
}
169+
170+
fn check_crate_post(&mut self, cx: &LateContext<'_>) {
171+
for (local_def_id, usage) in &self.empty_tuple_enum_variants {
172+
let Usage::Unused { redundant_use_sites } = usage else {
173+
continue;
174+
};
175+
let Node::Variant(variant) = cx.tcx.hir_node(
176+
cx.tcx
177+
.local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()),
178+
) else {
179+
continue;
180+
};
181+
let span = variant.span.with_lo(variant.ident.span.hi());
182+
span_lint_hir_and_then(
105183
cx,
106184
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
107-
span_after_ident,
185+
variant.hir_id,
186+
span,
108187
"enum variant has empty brackets",
109188
|diagnostic| {
110-
diagnostic.span_suggestion_hidden(
111-
span_after_ident,
112-
"remove the brackets",
113-
"",
114-
Applicability::MaybeIncorrect,
115-
);
189+
diagnostic.span_suggestion_hidden(span, "remove the brackets", "", Applicability::MaybeIncorrect);
190+
if !redundant_use_sites.is_empty() {
191+
let parentheses_spans = redundant_use_sites.iter().map(|span| (*span, String::new())).collect();
192+
diagnostic.multipart_suggestion(
193+
"remove the brackets",
194+
parentheses_spans,
195+
Applicability::MaybeIncorrect,
196+
);
197+
}
116198
},
117199
);
118200
}
@@ -123,11 +205,11 @@ fn has_no_ident_token(braces_span_str: &str) -> bool {
123205
!rustc_lexer::tokenize(braces_span_str).any(|t| t.kind == TokenKind::Ident)
124206
}
125207

126-
fn has_brackets(var_data: &VariantData) -> bool {
127-
!matches!(var_data, VariantData::Unit(_))
208+
fn has_brackets(var_data: &VariantData<'_>) -> bool {
209+
!matches!(var_data, VariantData::Unit(..))
128210
}
129211

130-
fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Span) -> bool {
212+
fn has_no_fields(cx: &LateContext<'_>, var_data: &VariantData<'_>, braces_span: Span) -> bool {
131213
if !var_data.fields().is_empty() {
132214
return false;
133215
}
@@ -142,6 +224,32 @@ fn has_no_fields(cx: &EarlyContext<'_>, var_data: &VariantData, braces_span: Spa
142224
has_no_ident_token(braces_span_str.as_ref())
143225
}
144226

227+
fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Span> {
228+
if let Node::Expr(parent) = tcx.parent_hir_node(expr.hir_id)
229+
&& let ExprKind::Call(callee, ..) = parent.kind
230+
&& callee.hir_id == expr.hir_id
231+
{
232+
Some(parent.span.with_lo(expr.span.hi()))
233+
} else {
234+
None
235+
}
236+
}
237+
238+
fn check_expr_for_enum_as_function(expr: &Expr<'_>) -> Option<LocalDefId> {
239+
if let ExprKind::Path(QPath::Resolved(
240+
_,
241+
Path {
242+
res: Def(Ctor(CtorOf::Variant, _), def_id),
243+
..
244+
},
245+
)) = expr.kind
246+
{
247+
def_id.as_local()
248+
} else {
249+
None
250+
}
251+
}
252+
145253
#[cfg(test)]
146254
mod unit_test {
147255
use super::*;

clippy_lints/src/lib.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1007,7 +1007,7 @@ pub fn register_lints(store: &mut rustc_lint::LintStore, conf: &'static Conf) {
10071007
})
10081008
});
10091009
store.register_early_pass(|| Box::new(crate_in_macro_def::CrateInMacroDef));
1010-
store.register_early_pass(|| Box::new(empty_with_brackets::EmptyWithBrackets));
1010+
store.register_late_pass(|_| Box::new(empty_with_brackets::EmptyWithBrackets::default()));
10111011
store.register_late_pass(|_| Box::new(unnecessary_owned_empty_strings::UnnecessaryOwnedEmptyStrings));
10121012
store.register_early_pass(|| Box::new(pub_use::PubUse));
10131013
store.register_late_pass(|_| Box::new(format_push_string::FormatPushString));

tests/ui/empty_enum_variants_with_brackets.fixed

+47-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
#![allow(dead_code)]
33

44
pub enum PublicTestEnum {
5-
NonEmptyBraces { x: i32, y: i32 }, // No error
6-
NonEmptyParentheses(i32, i32), // No error
7-
EmptyBraces, //~ ERROR: enum variant has empty brackets
8-
EmptyParentheses, //~ ERROR: enum variant has empty brackets
5+
// No errors as enum is reachable
6+
NonEmptyBraces { x: i32, y: i32 },
7+
NonEmptyParentheses(i32, i32),
8+
EmptyBraces {},
9+
EmptyParentheses(),
910
}
1011

1112
enum TestEnum {
@@ -16,6 +17,48 @@ enum TestEnum {
1617
AnotherEnum, // No error
1718
}
1819

20+
mod issue12551 {
21+
enum EvenOdd {
22+
// Used as functions -> no error
23+
Even(),
24+
Odd(),
25+
// Not used as a function
26+
Unknown, //~ ERROR: enum variant has empty brackets
27+
}
28+
29+
fn even_odd(x: i32) -> EvenOdd {
30+
(x % 2 == 0).then(EvenOdd::Even).unwrap_or_else(EvenOdd::Odd)
31+
}
32+
33+
fn natural_number(x: i32) -> NaturalOrNot {
34+
(x > 0)
35+
.then(NaturalOrNot::Natural)
36+
.unwrap_or_else(NaturalOrNot::NotNatural)
37+
}
38+
39+
enum NaturalOrNot {
40+
// Used as functions -> no error
41+
Natural(),
42+
NotNatural(),
43+
// Not used as a function
44+
Unknown, //~ ERROR: enum variant has empty brackets
45+
}
46+
47+
enum RedundantParenthesesFunctionCall {
48+
// Used as a function call but with redundant parentheses
49+
Parentheses, //~ ERROR: enum variant has empty brackets
50+
// Not used as a function
51+
NoParentheses,
52+
}
53+
54+
#[allow(clippy::no_effect)]
55+
fn redundant_parentheses_function_call() {
56+
// The parentheses in the below line are redundant.
57+
RedundantParenthesesFunctionCall::Parentheses;
58+
RedundantParenthesesFunctionCall::NoParentheses;
59+
}
60+
}
61+
1962
enum TestEnumWithFeatures {
2063
NonEmptyBraces {
2164
#[cfg(feature = "thisisneverenabled")]

tests/ui/empty_enum_variants_with_brackets.rs

+47-4
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22
#![allow(dead_code)]
33

44
pub enum PublicTestEnum {
5-
NonEmptyBraces { x: i32, y: i32 }, // No error
6-
NonEmptyParentheses(i32, i32), // No error
7-
EmptyBraces {}, //~ ERROR: enum variant has empty brackets
8-
EmptyParentheses(), //~ ERROR: enum variant has empty brackets
5+
// No errors as enum is reachable
6+
NonEmptyBraces { x: i32, y: i32 },
7+
NonEmptyParentheses(i32, i32),
8+
EmptyBraces {},
9+
EmptyParentheses(),
910
}
1011

1112
enum TestEnum {
@@ -16,6 +17,48 @@ enum TestEnum {
1617
AnotherEnum, // No error
1718
}
1819

20+
mod issue12551 {
21+
enum EvenOdd {
22+
// Used as functions -> no error
23+
Even(),
24+
Odd(),
25+
// Not used as a function
26+
Unknown(), //~ ERROR: enum variant has empty brackets
27+
}
28+
29+
fn even_odd(x: i32) -> EvenOdd {
30+
(x % 2 == 0).then(EvenOdd::Even).unwrap_or_else(EvenOdd::Odd)
31+
}
32+
33+
fn natural_number(x: i32) -> NaturalOrNot {
34+
(x > 0)
35+
.then(NaturalOrNot::Natural)
36+
.unwrap_or_else(NaturalOrNot::NotNatural)
37+
}
38+
39+
enum NaturalOrNot {
40+
// Used as functions -> no error
41+
Natural(),
42+
NotNatural(),
43+
// Not used as a function
44+
Unknown(), //~ ERROR: enum variant has empty brackets
45+
}
46+
47+
enum RedundantParenthesesFunctionCall {
48+
// Used as a function call but with redundant parentheses
49+
Parentheses(), //~ ERROR: enum variant has empty brackets
50+
// Not used as a function
51+
NoParentheses,
52+
}
53+
54+
#[allow(clippy::no_effect)]
55+
fn redundant_parentheses_function_call() {
56+
// The parentheses in the below line are redundant.
57+
RedundantParenthesesFunctionCall::Parentheses();
58+
RedundantParenthesesFunctionCall::NoParentheses;
59+
}
60+
}
61+
1962
enum TestEnumWithFeatures {
2063
NonEmptyBraces {
2164
#[cfg(feature = "thisisneverenabled")]
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
error: enum variant has empty brackets
2-
--> tests/ui/empty_enum_variants_with_brackets.rs:7:16
2+
--> tests/ui/empty_enum_variants_with_brackets.rs:15:16
33
|
44
LL | EmptyBraces {},
55
| ^^^
@@ -9,28 +9,41 @@ LL | EmptyBraces {},
99
= help: remove the brackets
1010

1111
error: enum variant has empty brackets
12-
--> tests/ui/empty_enum_variants_with_brackets.rs:8:21
12+
--> tests/ui/empty_enum_variants_with_brackets.rs:16:21
1313
|
1414
LL | EmptyParentheses(),
1515
| ^^
1616
|
1717
= help: remove the brackets
1818

1919
error: enum variant has empty brackets
20-
--> tests/ui/empty_enum_variants_with_brackets.rs:14:16
20+
--> tests/ui/empty_enum_variants_with_brackets.rs:26:16
2121
|
22-
LL | EmptyBraces {},
23-
| ^^^
22+
LL | Unknown(),
23+
| ^^
2424
|
2525
= help: remove the brackets
2626

2727
error: enum variant has empty brackets
28-
--> tests/ui/empty_enum_variants_with_brackets.rs:15:21
28+
--> tests/ui/empty_enum_variants_with_brackets.rs:44:16
2929
|
30-
LL | EmptyParentheses(),
31-
| ^^
30+
LL | Unknown(),
31+
| ^^
3232
|
3333
= help: remove the brackets
3434

35-
error: aborting due to 4 previous errors
35+
error: enum variant has empty brackets
36+
--> tests/ui/empty_enum_variants_with_brackets.rs:49:20
37+
|
38+
LL | Parentheses(),
39+
| ^^
40+
|
41+
= help: remove the brackets
42+
help: remove the brackets
43+
|
44+
LL - RedundantParenthesesFunctionCall::Parentheses();
45+
LL + RedundantParenthesesFunctionCall::Parentheses;
46+
|
47+
48+
error: aborting due to 5 previous errors
3649

0 commit comments

Comments
 (0)