Skip to content

Commit 7cb7e28

Browse files
committed
Fix FP of identity_op when encountering Default::default()
1 parent 9e7782b commit 7cb7e28

File tree

4 files changed

+114
-5
lines changed

4 files changed

+114
-5
lines changed

clippy_lints/src/operators/identity_op.rs

Lines changed: 61 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::source::snippet_with_applicability;
44
use clippy_utils::{clip, peel_hir_expr_refs, unsext};
55
use rustc_errors::Applicability;
6-
use rustc_hir::{BinOpKind, Expr, ExprKind, Node};
6+
use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath};
77
use rustc_lint::LateContext;
88
use rustc_middle::ty;
9-
use rustc_span::Span;
9+
use rustc_span::{Span, sym};
1010

1111
use super::IDENTITY_OP;
1212

@@ -17,7 +17,7 @@ pub(crate) fn check<'tcx>(
1717
left: &'tcx Expr<'_>,
1818
right: &'tcx Expr<'_>,
1919
) {
20-
if !is_allowed(cx, op, left, right) {
20+
if !is_allowed(cx, expr, op, left, right) {
2121
return;
2222
}
2323

@@ -165,7 +165,14 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>)
165165
Parens::Needed
166166
}
167167

168-
fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool {
168+
fn is_allowed(cx: &LateContext<'_>, expr: &Expr<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Expr<'_>) -> bool {
169+
// Exclude case where the left or right side is a call to `Default::default()`
170+
// and the expression is not a let binding's init expression and the let binding has a type
171+
// annotation, or a function's return value.
172+
if (is_default_call(cx, left) || is_default_call(cx, right)) && !is_expr_with_type_annotation(cx, expr.hir_id) {
173+
return false;
174+
}
175+
169176
// This lint applies to integers and their references
170177
cx.typeck_results().expr_ty(left).peel_refs().is_integral()
171178
&& cx.typeck_results().expr_ty(right).peel_refs().is_integral()
@@ -175,6 +182,20 @@ fn is_allowed(cx: &LateContext<'_>, cmp: BinOpKind, left: &Expr<'_>, right: &Exp
175182
&& ConstEvalCtxt::new(cx).eval_simple(left) == Some(Constant::Int(1)))
176183
}
177184

185+
/// Check if the expression is a call to `Default::default()`
186+
fn is_default_call(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
187+
if let ExprKind::Call(func, []) = peel_hir_expr_refs(expr).0.kind
188+
&& let ExprKind::Path(qpath) = func.kind
189+
// Detect and ignore <Foo as Default>::default() because these calls do explicitly name the type.
190+
&& let QPath::Resolved(None, _path) = qpath
191+
&& let Some(def_id) = cx.qpath_res(&qpath, func.hir_id).opt_def_id()
192+
&& cx.tcx.is_diagnostic_item(sym::default_fn, def_id)
193+
{
194+
return true;
195+
}
196+
false
197+
}
198+
178199
fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) {
179200
let ecx = ConstEvalCtxt::new(cx);
180201
if match (ecx.eval_full_int(left), ecx.eval_full_int(right)) {
@@ -234,3 +255,39 @@ fn span_ineffective_operation(
234255
applicability,
235256
);
236257
}
258+
259+
/// Check if the expression is a let binding's init expression and the let binding has a type
260+
/// annotation. Also check if the expression is a function's return value.
261+
fn is_expr_with_type_annotation(cx: &LateContext<'_>, hir_id: HirId) -> bool {
262+
// Get the parent node of the expression
263+
if let Some((_, parent)) = cx.tcx.hir_parent_iter(hir_id).next() {
264+
match parent {
265+
Node::LetStmt(local) => {
266+
// Check if this expression is the init expression of the let binding
267+
if let Some(init) = local.init
268+
&& init.hir_id == hir_id
269+
{
270+
// Check if the let binding has an explicit type annotation
271+
return local.ty.is_some();
272+
}
273+
},
274+
Node::Block(block) => {
275+
// If the parent node is a block, we can make sure the expression is the last expression in the
276+
// block.
277+
return is_expr_with_type_annotation(cx, block.hir_id);
278+
},
279+
Node::Expr(expr) => {
280+
return is_expr_with_type_annotation(cx, expr.hir_id);
281+
},
282+
Node::Item(Item {
283+
kind: ItemKind::Fn { .. },
284+
..
285+
}) => {
286+
// Every function has a return type, so we can return true.
287+
return true;
288+
},
289+
_ => {},
290+
}
291+
}
292+
false
293+
}

tests/ui/identity_op.fixed

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,20 @@ fn issue_13470() {
312312
let _: u64 = 1u64 + ((x as i32 + y as i32) as u64);
313313
//~^ identity_op
314314
}
315+
316+
fn issue_14932() {
317+
let _ = 0usize + &Default::default(); // no error
318+
319+
0usize + &Default::default(); // no error
320+
321+
let _ = usize::default();
322+
//~^ identity_op
323+
324+
let _n: usize = Default::default();
325+
//~^ identity_op
326+
}
327+
328+
fn issue_14932_2() -> usize {
329+
Default::default()
330+
//~^ identity_op
331+
}

tests/ui/identity_op.rs

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,3 +312,20 @@ fn issue_13470() {
312312
let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64);
313313
//~^ identity_op
314314
}
315+
316+
fn issue_14932() {
317+
let _ = 0usize + &Default::default(); // no error
318+
319+
0usize + &Default::default(); // no error
320+
321+
let _ = 0usize + &usize::default();
322+
//~^ identity_op
323+
324+
let _n: usize = 0usize + &Default::default();
325+
//~^ identity_op
326+
}
327+
328+
fn issue_14932_2() -> usize {
329+
0usize + &Default::default()
330+
//~^ identity_op
331+
}

tests/ui/identity_op.stderr

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,5 +379,23 @@ error: this operation has no effect
379379
LL | let _: u64 = 1u64 + ((x as i32 + y as i32) as u64 + 0u64);
380380
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `((x as i32 + y as i32) as u64)`
381381

382-
error: aborting due to 63 previous errors
382+
error: this operation has no effect
383+
--> tests/ui/identity_op.rs:321:13
384+
|
385+
LL | let _ = 0usize + &usize::default();
386+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `usize::default()`
387+
388+
error: this operation has no effect
389+
--> tests/ui/identity_op.rs:324:21
390+
|
391+
LL | let _n: usize = 0usize + &Default::default();
392+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Default::default()`
393+
394+
error: this operation has no effect
395+
--> tests/ui/identity_op.rs:329:5
396+
|
397+
LL | 0usize + &Default::default()
398+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Default::default()`
399+
400+
error: aborting due to 66 previous errors
383401

0 commit comments

Comments
 (0)