Skip to content

Commit 7ebbf25

Browse files
committed
Make empty_enum_variants_with_brackets to support empty variant with braces
1 parent bd0a5e4 commit 7ebbf25

File tree

4 files changed

+211
-77
lines changed

4 files changed

+211
-77
lines changed

clippy_lints/src/empty_with_brackets.rs

Lines changed: 87 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
use clippy_utils::attrs::span_contains_cfg;
22
use clippy_utils::diagnostics::{span_lint_and_then, span_lint_hir_and_then};
3-
use rustc_data_structures::fx::FxIndexMap;
3+
use clippy_utils::source::SpanRangeExt;
4+
use clippy_utils::span_contains_non_whitespace;
5+
use rustc_data_structures::fx::{FxIndexMap, IndexEntry};
46
use rustc_errors::Applicability;
57
use rustc_hir::def::DefKind::Ctor;
68
use rustc_hir::def::Res::Def;
@@ -10,7 +12,7 @@ use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Pat, PatKind, Path, QPath,
1012
use rustc_lint::{LateContext, LateLintPass};
1113
use rustc_middle::ty::{self, TyCtxt};
1214
use rustc_session::impl_lint_pass;
13-
use rustc_span::Span;
15+
use rustc_span::{BytePos, Span};
1416

1517
declare_clippy_lint! {
1618
/// ### What it does
@@ -118,80 +120,55 @@ impl LateLintPass<'_> for EmptyWithBrackets {
118120
}
119121

120122
fn check_variant(&mut self, cx: &LateContext<'_>, variant: &Variant<'_>) {
121-
// FIXME: handle `$name {}`
122123
if !variant.span.from_expansion()
123124
&& !variant.ident.span.from_expansion()
124125
&& let span_after_ident = variant.span.with_lo(variant.ident.span.hi())
125126
&& has_no_fields(cx, &variant.data, span_after_ident)
126127
{
127128
match variant.data {
128129
VariantData::Struct { .. } => {
129-
// Empty struct variants can be linted immediately
130-
span_lint_and_then(
131-
cx,
132-
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
133-
span_after_ident,
134-
"enum variant has empty brackets",
135-
|diagnostic| {
136-
diagnostic.span_suggestion_hidden(
137-
span_after_ident,
138-
"remove the brackets",
139-
"",
140-
Applicability::MaybeIncorrect,
141-
);
142-
},
143-
);
130+
self.add_enum_variant(variant.def_id);
144131
},
145132
VariantData::Tuple(.., local_def_id) => {
146133
// Don't lint reachable tuple enums
147134
if cx.effective_visibilities.is_reachable(variant.def_id) {
148135
return;
149136
}
150-
if let Some(entry) = self.empty_tuple_enum_variants.get_mut(&local_def_id) {
151-
// empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before the
152-
// definition was encountered. Now that there's a definition, convert it
153-
// to Usage::Unused.
154-
if let Usage::NoDefinition { redundant_use_sites } = entry {
155-
*entry = Usage::Unused {
156-
redundant_use_sites: redundant_use_sites.clone(),
157-
};
158-
}
159-
} else {
160-
self.empty_tuple_enum_variants.insert(
161-
local_def_id,
162-
Usage::Unused {
163-
redundant_use_sites: vec![],
164-
},
165-
);
166-
}
137+
self.add_enum_variant(local_def_id);
167138
},
168139
VariantData::Unit(..) => {},
169140
}
170141
}
171142
}
172143

173144
fn check_expr(&mut self, cx: &LateContext<'_>, expr: &Expr<'_>) {
174-
if let Some(def_id) = check_expr_for_enum_as_function(expr) {
175-
if let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr) {
145+
if let Some((def_id, mut span)) = check_expr_for_enum_as_function(cx, expr) {
146+
if span.is_empty()
147+
&& let Some(parentheses_span) = call_parentheses_span(cx.tcx, expr)
148+
{
149+
span = parentheses_span;
150+
}
151+
152+
if span.is_empty() {
153+
// The parentheses are not redundant.
154+
self.empty_tuple_enum_variants.insert(def_id, Usage::Used);
155+
} else {
176156
// Do not count expressions from macro expansion as a redundant use site.
177157
if expr.span.from_expansion() {
178158
return;
179159
}
180-
self.update_enum_variant_usage(def_id, parentheses_span);
181-
} else {
182-
// The parentheses are not redundant.
183-
self.empty_tuple_enum_variants.insert(def_id, Usage::Used);
160+
self.update_enum_variant_usage(def_id, span);
184161
}
185162
}
186163
}
187164

188165
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
189-
if let Some((def_id, parentheses_span)) = check_pat_for_enum_as_function(cx, pat) {
166+
if let Some((def_id, span)) = check_pat_for_enum_as_function(cx, pat) {
190167
if pat.span.from_expansion() {
191168
return;
192169
}
193170

194-
self.update_enum_variant_usage(def_id, parentheses_span);
171+
self.update_enum_variant_usage(def_id, span);
195172
}
196173
}
197174

@@ -201,15 +178,27 @@ impl LateLintPass<'_> for EmptyWithBrackets {
201178
let Usage::Unused { redundant_use_sites } = usage else {
202179
continue;
203180
};
181+
204182
// Attempt to fetch the Variant from LocalDefId.
205-
let Node::Variant(variant) = cx.tcx.hir_node(
183+
let variant = if let Node::Variant(variant) = cx.tcx.hir_node(cx.tcx.local_def_id_to_hir_id(local_def_id)) {
184+
variant
185+
} else if let Node::Variant(variant) = cx.tcx.hir_node(
206186
cx.tcx
207187
.local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()),
208-
) else {
188+
) {
189+
variant
190+
} else {
209191
continue;
210192
};
193+
211194
// Span of the parentheses in variant definition
212195
let span = variant.span.with_lo(variant.ident.span.hi());
196+
let span_inner = span
197+
.with_lo(SpanRangeExt::trim_start(span, cx).start + BytePos(1))
198+
.with_hi(span.hi() - BytePos(1));
199+
if span_contains_non_whitespace(cx, span_inner, false) {
200+
continue;
201+
}
213202
span_lint_hir_and_then(
214203
cx,
215204
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
@@ -242,28 +231,38 @@ impl LateLintPass<'_> for EmptyWithBrackets {
242231
}
243232

244233
impl EmptyWithBrackets {
234+
fn add_enum_variant(&mut self, local_def_id: LocalDefId) {
235+
self.empty_tuple_enum_variants
236+
.entry(local_def_id)
237+
.and_modify(|entry| {
238+
// empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before
239+
// the definition was encountered. Now that there's a
240+
// definition, convert it to Usage::Unused.
241+
if let Usage::NoDefinition { redundant_use_sites } = entry {
242+
*entry = Usage::Unused {
243+
redundant_use_sites: redundant_use_sites.clone(),
244+
};
245+
}
246+
})
247+
.or_insert_with(|| Usage::Unused {
248+
redundant_use_sites: vec![],
249+
});
250+
}
251+
245252
fn update_enum_variant_usage(&mut self, def_id: LocalDefId, parentheses_span: Span) {
246-
match self.empty_tuple_enum_variants.get_mut(&def_id) {
247-
Some(
248-
&mut (Usage::Unused {
249-
ref mut redundant_use_sites,
253+
match self.empty_tuple_enum_variants.entry(def_id) {
254+
IndexEntry::Occupied(mut e) => {
255+
if let Usage::Unused { redundant_use_sites } | Usage::NoDefinition { redundant_use_sites } = e.get_mut()
256+
{
257+
redundant_use_sites.push(parentheses_span);
250258
}
251-
| Usage::NoDefinition {
252-
ref mut redundant_use_sites,
253-
}),
254-
) => {
255-
redundant_use_sites.push(parentheses_span);
256259
},
257-
None => {
260+
IndexEntry::Vacant(e) => {
258261
// The variant isn't in the IndexMap which means its definition wasn't encountered yet.
259-
self.empty_tuple_enum_variants.insert(
260-
def_id,
261-
Usage::NoDefinition {
262-
redundant_use_sites: vec![parentheses_span],
263-
},
264-
);
262+
e.insert(Usage::NoDefinition {
263+
redundant_use_sites: vec![parentheses_span],
264+
});
265265
},
266-
_ => {},
267266
}
268267
}
269268
}
@@ -293,18 +292,27 @@ fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Span> {
293292
}
294293

295294
// Returns the LocalDefId of the variant being called as a function if it exists.
296-
fn check_expr_for_enum_as_function(expr: &Expr<'_>) -> Option<LocalDefId> {
297-
if let ExprKind::Path(QPath::Resolved(
298-
_,
299-
Path {
300-
res: Def(Ctor(CtorOf::Variant, _), def_id),
301-
..
295+
fn check_expr_for_enum_as_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(LocalDefId, Span)> {
296+
match expr.kind {
297+
ExprKind::Path(QPath::Resolved(
298+
_,
299+
Path {
300+
res: Def(Ctor(CtorOf::Variant, _), def_id),
301+
span,
302+
..
303+
},
304+
)) => def_id.as_local().map(|id| (id, span.with_lo(expr.span.hi()))),
305+
ExprKind::Struct(qpath, ..)
306+
if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(qpath, expr.hir_id) =>
307+
{
308+
let ty = cx.tcx.type_of(def_id).instantiate_identity();
309+
if let ty::FnDef(ctor_def_id, _) = ty.kind() {
310+
def_id = *ctor_def_id;
311+
}
312+
313+
def_id.as_local().map(|id| (id, qpath.span().with_lo(expr.span.hi())))
302314
},
303-
)) = expr.kind
304-
{
305-
def_id.as_local()
306-
} else {
307-
None
315+
_ => None,
308316
}
309317
}
310318

@@ -316,10 +324,13 @@ fn check_pat_for_enum_as_function(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option
316324
def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi())))
317325
},
318326
PatKind::Struct(qpath, ..)
319-
if let Def(DefKind::Variant, def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id)
320-
&& let ty = cx.tcx.type_of(def_id).instantiate_identity()
321-
&& let ty::FnDef(def_id, _) = ty.kind() =>
327+
if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id) =>
322328
{
329+
let ty = cx.tcx.type_of(def_id).instantiate_identity();
330+
if let ty::FnDef(ctor_def_id, _) = ty.kind() {
331+
def_id = *ctor_def_id;
332+
}
333+
323334
def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi())))
324335
},
325336
_ => None,

tests/ui/empty_enum_variants_with_brackets.fixed

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,50 @@ fn issue16157() {
115115
<E>::V = E::V;
116116
}
117117

118+
fn variant_with_braces() {
119+
enum E {
120+
V,
121+
//~^ empty_enum_variants_with_brackets
122+
}
123+
E::V = E::V;
124+
E::V = E::V;
125+
<E>::V = <E>::V;
126+
127+
enum F {
128+
U,
129+
//~^ empty_enum_variants_with_brackets
130+
}
131+
F::U = F::U;
132+
<F>::U = F::U;
133+
}
134+
135+
fn variant_with_comments_and_cfg() {
136+
enum E {
137+
V(
138+
// This is a comment
139+
),
140+
}
141+
E::V() = E::V();
142+
143+
enum F {
144+
U {
145+
// This is a comment
146+
},
147+
}
148+
F::U {} = F::U {};
149+
150+
enum G {
151+
V(#[cfg(target_os = "cuda")] String),
152+
}
153+
G::V() = G::V();
154+
155+
enum H {
156+
U {
157+
#[cfg(target_os = "cuda")]
158+
value: String,
159+
},
160+
}
161+
H::U {} = H::U {};
162+
}
163+
118164
fn main() {}

tests/ui/empty_enum_variants_with_brackets.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,4 +115,50 @@ fn issue16157() {
115115
<E>::V {} = E::V();
116116
}
117117

118+
fn variant_with_braces() {
119+
enum E {
120+
V(),
121+
//~^ empty_enum_variants_with_brackets
122+
}
123+
E::V() = E::V();
124+
E::V() = E::V {};
125+
<E>::V {} = <E>::V {};
126+
127+
enum F {
128+
U {},
129+
//~^ empty_enum_variants_with_brackets
130+
}
131+
F::U {} = F::U {};
132+
<F>::U {} = F::U {};
133+
}
134+
135+
fn variant_with_comments_and_cfg() {
136+
enum E {
137+
V(
138+
// This is a comment
139+
),
140+
}
141+
E::V() = E::V();
142+
143+
enum F {
144+
U {
145+
// This is a comment
146+
},
147+
}
148+
F::U {} = F::U {};
149+
150+
enum G {
151+
V(#[cfg(target_os = "cuda")] String),
152+
}
153+
G::V() = G::V();
154+
155+
enum H {
156+
U {
157+
#[cfg(target_os = "cuda")]
158+
value: String,
159+
},
160+
}
161+
H::U {} = H::U {};
162+
}
163+
118164
fn main() {}

tests/ui/empty_enum_variants_with_brackets.stderr

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,5 +96,36 @@ LL ~ <E>::V = E::V;
9696
LL ~ <E>::V = E::V;
9797
|
9898

99-
error: aborting due to 9 previous errors
99+
error: enum variant has empty brackets
100+
--> tests/ui/empty_enum_variants_with_brackets.rs:120:10
101+
|
102+
LL | V(),
103+
| ^^
104+
|
105+
help: remove the brackets
106+
|
107+
LL ~ V,
108+
LL |
109+
LL | }
110+
LL ~ E::V = E::V;
111+
LL ~ E::V = E::V;
112+
LL ~ <E>::V = <E>::V;
113+
|
114+
115+
error: enum variant has empty brackets
116+
--> tests/ui/empty_enum_variants_with_brackets.rs:128:10
117+
|
118+
LL | U {},
119+
| ^^^
120+
|
121+
help: remove the brackets
122+
|
123+
LL ~ U,
124+
LL |
125+
LL | }
126+
LL ~ F::U = F::U;
127+
LL ~ <F>::U = F::U;
128+
|
129+
130+
error: aborting due to 11 previous errors
100131

0 commit comments

Comments
 (0)