Skip to content

Commit 020c7cf

Browse files
Fix empty_enum_variants_with_brackets misses removing brackets in patterns (#16160)
Closes #16157 changelog: [`empty_enum_variants_with_brackets`] fix missing to remove brackets in patterns
2 parents 498d4bf + 9642358 commit 020c7cf

File tree

4 files changed

+297
-90
lines changed

4 files changed

+297
-90
lines changed

clippy_lints/src/empty_with_brackets.rs

Lines changed: 121 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,18 @@
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;
5-
use rustc_hir::def::CtorOf;
67
use rustc_hir::def::DefKind::Ctor;
78
use rustc_hir::def::Res::Def;
9+
use rustc_hir::def::{CtorOf, DefKind};
810
use rustc_hir::def_id::LocalDefId;
9-
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Path, QPath, Variant, VariantData};
11+
use rustc_hir::{Expr, ExprKind, Item, ItemKind, Node, Pat, PatKind, Path, QPath, Variant, VariantData};
1012
use rustc_lint::{LateContext, LateLintPass};
11-
use rustc_middle::ty::TyCtxt;
13+
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,109 +120,80 @@ 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-
match self.empty_tuple_enum_variants.get_mut(&def_id) {
181-
Some(
182-
&mut (Usage::Unused {
183-
ref mut redundant_use_sites,
184-
}
185-
| Usage::NoDefinition {
186-
ref mut redundant_use_sites,
187-
}),
188-
) => {
189-
redundant_use_sites.push(parentheses_span);
190-
},
191-
None => {
192-
// The variant isn't in the IndexMap which means its definition wasn't encountered yet.
193-
self.empty_tuple_enum_variants.insert(
194-
def_id,
195-
Usage::NoDefinition {
196-
redundant_use_sites: vec![parentheses_span],
197-
},
198-
);
199-
},
200-
_ => {},
201-
}
202-
} else {
203-
// The parentheses are not redundant.
204-
self.empty_tuple_enum_variants.insert(def_id, Usage::Used);
160+
self.update_enum_variant_usage(def_id, span);
205161
}
206162
}
207163
}
208164

165+
fn check_pat(&mut self, cx: &LateContext<'_>, pat: &Pat<'_>) {
166+
if !pat.span.from_expansion()
167+
&& let Some((def_id, span)) = check_pat_for_enum_as_function(cx, pat)
168+
{
169+
self.update_enum_variant_usage(def_id, span);
170+
}
171+
}
172+
209173
fn check_crate_post(&mut self, cx: &LateContext<'_>) {
210-
for (local_def_id, usage) in &self.empty_tuple_enum_variants {
174+
for (&local_def_id, usage) in &self.empty_tuple_enum_variants {
211175
// Ignore all variants with Usage::Used or Usage::NoDefinition
212176
let Usage::Unused { redundant_use_sites } = usage else {
213177
continue;
214178
};
179+
215180
// Attempt to fetch the Variant from LocalDefId.
216-
let Node::Variant(variant) = cx.tcx.hir_node(
217-
cx.tcx
218-
.local_def_id_to_hir_id(cx.tcx.parent(local_def_id.to_def_id()).expect_local()),
219-
) else {
181+
let variant = if let Node::Variant(variant) = cx.tcx.hir_node_by_def_id(local_def_id) {
182+
variant
183+
} else if let Node::Variant(variant) = cx.tcx.hir_node_by_def_id(cx.tcx.local_parent(local_def_id)) {
184+
variant
185+
} else {
220186
continue;
221187
};
188+
222189
// Span of the parentheses in variant definition
223190
let span = variant.span.with_lo(variant.ident.span.hi());
191+
let span_inner = span
192+
.with_lo(SpanRangeExt::trim_start(span, cx).start + BytePos(1))
193+
.with_hi(span.hi() - BytePos(1));
194+
if span_contains_non_whitespace(cx, span_inner, false) {
195+
continue;
196+
}
224197
span_lint_hir_and_then(
225198
cx,
226199
EMPTY_ENUM_VARIANTS_WITH_BRACKETS,
@@ -252,6 +225,43 @@ impl LateLintPass<'_> for EmptyWithBrackets {
252225
}
253226
}
254227

228+
impl EmptyWithBrackets {
229+
fn add_enum_variant(&mut self, local_def_id: LocalDefId) {
230+
self.empty_tuple_enum_variants
231+
.entry(local_def_id)
232+
.and_modify(|entry| {
233+
// empty_tuple_enum_variants contains Usage::NoDefinition if the variant was called before
234+
// the definition was encountered. Now that there's a
235+
// definition, convert it to Usage::Unused.
236+
if let Usage::NoDefinition { redundant_use_sites } = entry {
237+
*entry = Usage::Unused {
238+
redundant_use_sites: redundant_use_sites.clone(),
239+
};
240+
}
241+
})
242+
.or_insert_with(|| Usage::Unused {
243+
redundant_use_sites: vec![],
244+
});
245+
}
246+
247+
fn update_enum_variant_usage(&mut self, def_id: LocalDefId, parentheses_span: Span) {
248+
match self.empty_tuple_enum_variants.entry(def_id) {
249+
IndexEntry::Occupied(mut e) => {
250+
if let Usage::Unused { redundant_use_sites } | Usage::NoDefinition { redundant_use_sites } = e.get_mut()
251+
{
252+
redundant_use_sites.push(parentheses_span);
253+
}
254+
},
255+
IndexEntry::Vacant(e) => {
256+
// The variant isn't in the IndexMap which means its definition wasn't encountered yet.
257+
e.insert(Usage::NoDefinition {
258+
redundant_use_sites: vec![parentheses_span],
259+
});
260+
},
261+
}
262+
}
263+
}
264+
255265
fn has_brackets(var_data: &VariantData<'_>) -> bool {
256266
!matches!(var_data, VariantData::Unit(..))
257267
}
@@ -277,17 +287,47 @@ fn call_parentheses_span(tcx: TyCtxt<'_>, expr: &Expr<'_>) -> Option<Span> {
277287
}
278288

279289
// Returns the LocalDefId of the variant being called as a function if it exists.
280-
fn check_expr_for_enum_as_function(expr: &Expr<'_>) -> Option<LocalDefId> {
281-
if let ExprKind::Path(QPath::Resolved(
282-
_,
283-
Path {
284-
res: Def(Ctor(CtorOf::Variant, _), def_id),
285-
..
290+
fn check_expr_for_enum_as_function(cx: &LateContext<'_>, expr: &Expr<'_>) -> Option<(LocalDefId, Span)> {
291+
match expr.kind {
292+
ExprKind::Path(QPath::Resolved(
293+
_,
294+
Path {
295+
res: Def(Ctor(CtorOf::Variant, _), def_id),
296+
span,
297+
..
298+
},
299+
)) => def_id.as_local().map(|id| (id, span.with_lo(expr.span.hi()))),
300+
ExprKind::Struct(qpath, ..)
301+
if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(qpath, expr.hir_id) =>
302+
{
303+
let ty = cx.tcx.type_of(def_id).instantiate_identity();
304+
if let ty::FnDef(ctor_def_id, _) = ty.kind() {
305+
def_id = *ctor_def_id;
306+
}
307+
308+
def_id.as_local().map(|id| (id, qpath.span().with_lo(expr.span.hi())))
286309
},
287-
)) = expr.kind
288-
{
289-
def_id.as_local()
290-
} else {
291-
None
310+
_ => None,
311+
}
312+
}
313+
314+
fn check_pat_for_enum_as_function(cx: &LateContext<'_>, pat: &Pat<'_>) -> Option<(LocalDefId, Span)> {
315+
match pat.kind {
316+
PatKind::TupleStruct(qpath, ..)
317+
if let Def(Ctor(CtorOf::Variant, _), def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id) =>
318+
{
319+
def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi())))
320+
},
321+
PatKind::Struct(qpath, ..)
322+
if let Def(DefKind::Variant, mut def_id) = cx.typeck_results().qpath_res(&qpath, pat.hir_id) =>
323+
{
324+
let ty = cx.tcx.type_of(def_id).instantiate_identity();
325+
if let ty::FnDef(ctor_def_id, _) = ty.kind() {
326+
def_id = *ctor_def_id;
327+
}
328+
329+
def_id.as_local().map(|id| (id, qpath.span().with_lo(pat.span.hi())))
330+
},
331+
_ => None,
292332
}
293333
}

tests/ui/empty_enum_variants_with_brackets.fixed

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
#![warn(clippy::empty_enum_variants_with_brackets)]
22
#![allow(dead_code)]
3+
#![feature(more_qualified_paths)]
34

45
pub enum PublicTestEnum {
56
NonEmptyBraces { x: i32, y: i32 }, // No error
@@ -102,4 +103,62 @@ pub enum PubFoo {
102103
Variant3(),
103104
}
104105

106+
fn issue16157() {
107+
enum E {
108+
V,
109+
//~^ empty_enum_variants_with_brackets
110+
}
111+
112+
let E::V = E::V;
113+
114+
<E>::V = E::V;
115+
<E>::V = E::V;
116+
}
117+
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+
105164
fn main() {}

0 commit comments

Comments
 (0)