Skip to content

Commit bf1a276

Browse files
committed
make it support more cases
1 parent 7cb7e28 commit bf1a276

File tree

4 files changed

+109
-54
lines changed

4 files changed

+109
-54
lines changed

clippy_lints/src/operators/identity_op.rs

Lines changed: 49 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
use clippy_utils::consts::{ConstEvalCtxt, Constant, FullInt};
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::source::snippet_with_applicability;
4-
use clippy_utils::{clip, peel_hir_expr_refs, unsext};
4+
use clippy_utils::{ExprUseNode, clip, expr_use_ctxt, peel_hir_expr_refs, unsext};
55
use rustc_errors::Applicability;
6-
use rustc_hir::{BinOpKind, Expr, ExprKind, HirId, Item, ItemKind, Node, QPath};
6+
use rustc_hir::def::{DefKind, Res};
7+
use rustc_hir::{BinOpKind, Expr, ExprKind, Node, Path, QPath};
78
use rustc_lint::LateContext;
89
use rustc_middle::ty;
9-
use rustc_span::{Span, sym};
10+
use rustc_span::{Span, kw};
1011

1112
use super::IDENTITY_OP;
1213

@@ -165,11 +166,19 @@ fn needs_parenthesis(cx: &LateContext<'_>, binary: &Expr<'_>, child: &Expr<'_>)
165166
Parens::Needed
166167
}
167168

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) {
169+
fn is_allowed<'tcx>(
170+
cx: &LateContext<'tcx>,
171+
expr: &'tcx Expr<'tcx>,
172+
cmp: BinOpKind,
173+
left: &Expr<'tcx>,
174+
right: &Expr<'tcx>,
175+
) -> bool {
176+
// Exclude case where the left or right side is associated function call returns a type which is
177+
// `Self` that is not given explicitly, and the expression is not a let binding's init
178+
// expression and the let binding has a type annotation, or a function's return value.
179+
if (is_assoc_fn_without_type_instance(cx, left) || is_assoc_fn_without_type_instance(cx, right))
180+
&& !is_expr_used_with_type_annotation(cx, expr)
181+
{
173182
return false;
174183
}
175184

@@ -182,20 +191,6 @@ fn is_allowed(cx: &LateContext<'_>, expr: &Expr<'_>, cmp: BinOpKind, left: &Expr
182191
&& ConstEvalCtxt::new(cx).eval_simple(left) == Some(Constant::Int(1)))
183192
}
184193

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-
199194
fn check_remainder(cx: &LateContext<'_>, left: &Expr<'_>, right: &Expr<'_>, span: Span, arg: Span) {
200195
let ecx = ConstEvalCtxt::new(cx);
201196
if match (ecx.eval_full_int(left), ecx.eval_full_int(right)) {
@@ -256,38 +251,40 @@ fn span_ineffective_operation(
256251
);
257252
}
258253

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 { .. },
254+
fn is_expr_used_with_type_annotation<'tcx>(cx: &LateContext<'tcx>, expr: &'tcx Expr<'tcx>) -> bool {
255+
match expr_use_ctxt(cx, expr).use_node(cx) {
256+
ExprUseNode::LetStmt(letstmt) => letstmt.ty.is_some(),
257+
ExprUseNode::Return(_) => true,
258+
_ => false,
259+
}
260+
}
261+
262+
/// Check if the expression is an associated function without a type instance.
263+
/// Example:
264+
/// ```
265+
/// Default::default()
266+
/// // Or
267+
/// trait Def {
268+
/// fn def() -> Self;
269+
/// }
270+
/// Def::def()
271+
/// ```
272+
fn is_assoc_fn_without_type_instance<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
273+
if let ExprKind::Call(func, _) = peel_hir_expr_refs(expr).0.kind
274+
&& let ExprKind::Path(QPath::Resolved(
275+
// If it's not None, don't need to go further.
276+
None,
277+
Path {
278+
res: Res::Def(DefKind::AssocFn, def_id),
284279
..
285-
}) => {
286-
// Every function has a return type, so we can return true.
287-
return true;
288280
},
289-
_ => {},
290-
}
281+
)) = func.kind
282+
&& let output_ty = cx.tcx.fn_sig(def_id).instantiate_identity().skip_binder().output()
283+
&& let ty::Param(ty::ParamTy {
284+
name: kw::SelfUpper, ..
285+
}) = output_ty.kind()
286+
{
287+
return true;
291288
}
292289
false
293290
}

tests/ui/identity_op.fixed

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,30 @@ fn issue_14932() {
325325
//~^ identity_op
326326
}
327327

328+
// Expr's type can be inferred by the function's return type
328329
fn issue_14932_2() -> usize {
329330
Default::default()
330331
//~^ identity_op
331332
}
333+
334+
trait Def {
335+
fn def() -> Self;
336+
}
337+
338+
impl Def for usize {
339+
fn def() -> Self {
340+
0
341+
}
342+
}
343+
344+
fn issue_14932_3() {
345+
let _ = 0usize + &Def::def(); // no error
346+
347+
0usize + &Def::def(); // no error
348+
349+
let _ = usize::def();
350+
//~^ identity_op
351+
352+
let _n: usize = Def::def();
353+
//~^ identity_op
354+
}

tests/ui/identity_op.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,30 @@ fn issue_14932() {
325325
//~^ identity_op
326326
}
327327

328+
// Expr's type can be inferred by the function's return type
328329
fn issue_14932_2() -> usize {
329330
0usize + &Default::default()
330331
//~^ identity_op
331332
}
333+
334+
trait Def {
335+
fn def() -> Self;
336+
}
337+
338+
impl Def for usize {
339+
fn def() -> Self {
340+
0
341+
}
342+
}
343+
344+
fn issue_14932_3() {
345+
let _ = 0usize + &Def::def(); // no error
346+
347+
0usize + &Def::def(); // no error
348+
349+
let _ = 0usize + &usize::def();
350+
//~^ identity_op
351+
352+
let _n: usize = 0usize + &Def::def();
353+
//~^ identity_op
354+
}

tests/ui/identity_op.stderr

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -392,10 +392,22 @@ LL | let _n: usize = 0usize + &Default::default();
392392
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Default::default()`
393393

394394
error: this operation has no effect
395-
--> tests/ui/identity_op.rs:329:5
395+
--> tests/ui/identity_op.rs:330:5
396396
|
397397
LL | 0usize + &Default::default()
398398
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Default::default()`
399399

400-
error: aborting due to 66 previous errors
400+
error: this operation has no effect
401+
--> tests/ui/identity_op.rs:349:13
402+
|
403+
LL | let _ = 0usize + &usize::def();
404+
| ^^^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `usize::def()`
405+
406+
error: this operation has no effect
407+
--> tests/ui/identity_op.rs:352:21
408+
|
409+
LL | let _n: usize = 0usize + &Def::def();
410+
| ^^^^^^^^^^^^^^^^^^^^ help: consider reducing it to: `Def::def()`
411+
412+
error: aborting due to 68 previous errors
401413

0 commit comments

Comments
 (0)