Skip to content

Commit 288cbed

Browse files
committed
Make empty_enum_variants_with_brackets to support empty variant with braces
1 parent bd0a5e4 commit 288cbed

File tree

4 files changed

+211
-80
lines changed

4 files changed

+211
-80
lines changed

clippy_lints/src/empty_with_brackets.rs

Lines changed: 87 additions & 79 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,98 +120,82 @@ 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

198175
fn check_crate_post(&mut self, cx: &LateContext<'_>) {
199-
for (local_def_id, usage) in &self.empty_tuple_enum_variants {
176+
for (&local_def_id, usage) in &self.empty_tuple_enum_variants {
200177
// Ignore all variants with Usage::Used or Usage::NoDefinition
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(
206-
cx.tcx
207-
.local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()),
208-
) else {
183+
let variant = if let Node::Variant(variant) = cx.tcx.hir_node_by_def_id(local_def_id) {
184+
variant
185+
} else if let Node::Variant(variant) = cx.tcx.hir_node_by_def_id(cx.tcx.local_parent(local_def_id)) {
186+
variant
187+
} else {
209188
continue;
210189
};
190+
211191
// Span of the parentheses in variant definition
212192
let span = variant.span.with_lo(variant.ident.span.hi());
193+
let span_inner = span
194+
.with_lo(SpanRangeExt::trim_start(span, cx).start + BytePos(1))
195+
.with_hi(span.hi() - BytePos(1));
196+
if span_contains_non_whitespace(cx, span_inner, false) {
197+
continue;
198+
}
213199
span_lint_hir_and_then(
214200
cx,
215201
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
@@ -242,28 +228,38 @@ impl LateLintPass<'_> for EmptyWithBrackets {
242228
}
243229

244230
impl EmptyWithBrackets {
231+
fn add_enum_variant(&mut self, local_def_id: LocalDefId) {
232+
self.empty_tuple_enum_variants
233+
.entry(local_def_id)
234+
.and_modify(|entry| {
235+
// empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before
236+
// the definition was encountered. Now that there's a
237+
// definition, convert it to Usage::Unused.
238+
if let Usage::NoDefinition { redundant_use_sites } = entry {
239+
*entry = Usage::Unused {
240+
redundant_use_sites: redundant_use_sites.clone(),
241+
};
242+
}
243+
})
244+
.or_insert_with(|| Usage::Unused {
245+
redundant_use_sites: vec![],
246+
});
247+
}
248+
245249
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,
250+
match self.empty_tuple_enum_variants.entry(def_id) {
251+
IndexEntry::Occupied(mut e) => {
252+
if let Usage::Unused { redundant_use_sites } | Usage::NoDefinition { redundant_use_sites } = e.get_mut()
253+
{
254+
redundant_use_sites.push(parentheses_span);
250255
}
251-
| Usage::NoDefinition {
252-
ref mut redundant_use_sites,
253-
}),
254-
) => {
255-
redundant_use_sites.push(parentheses_span);
256256
},
257-
None => {
257+
IndexEntry::Vacant(e) => {
258258
// 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-
);
259+
e.insert(Usage::NoDefinition {
260+
redundant_use_sites: vec![parentheses_span],
261+
});
265262
},
266-
_ => {},
267263
}
268264
}
269265
}
@@ -293,18 +289,27 @@ fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Span> {
293289
}
294290

295291
// 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-
..
292+
fn check_expr_for_enum_as_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(LocalDefId, Span)> {
293+
match expr.kind {
294+
ExprKind::Path(QPath::Resolved(
295+
_,
296+
Path {
297+
res: Def(Ctor(CtorOf::Variant, _), def_id),
298+
span,
299+
..
300+
},
301+
)) => def_id.as_local().map(|id| (id, span.with_lo(expr.span.hi()))),
302+
ExprKind::Struct(qpath, ..)
303+
if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(qpath, expr.hir_id) =>
304+
{
305+
let ty = cx.tcx.type_of(def_id).instantiate_identity();
306+
if let ty::FnDef(ctor_def_id, _) = ty.kind() {
307+
def_id = *ctor_def_id;
308+
}
309+
310+
def_id.as_local().map(|id| (id, qpath.span().with_lo(expr.span.hi())))
302311
},
303-
)) = expr.kind
304-
{
305-
def_id.as_local()
306-
} else {
307-
None
312+
_ => None,
308313
}
309314
}
310315

@@ -316,10 +321,13 @@ fn check_pat_for_enum_as_function(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option
316321
def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi())))
317322
},
318323
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() =>
324+
if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id) =>
322325
{
326+
let ty = cx.tcx.type_of(def_id).instantiate_identity();
327+
if let ty::FnDef(ctor_def_id, _) = ty.kind() {
328+
def_id = *ctor_def_id;
329+
}
330+
323331
def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi())))
324332
},
325333
_ => 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)