Skip to content

Commit ff843ac

Browse files
committed
Auto merge of #10350 - J-ZhengLi:issue_10272, r=xFrednet
enhance [`ifs_same_cond`] to warn same immutable method calls as well fixes: #10272 --- changelog: Enhancement: [`ifs_same_cond`]: Now also detects immutable method calls. [#10350](#10350) <!-- changelog_checked -->
2 parents 945e42f + 011bb46 commit ff843ac

File tree

11 files changed

+196
-58
lines changed

11 files changed

+196
-58
lines changed

book/src/lint_configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ for the generic parameters for determining interior mutability
519519
**Default Value:** `["bytes::Bytes"]` (`Vec<String>`)
520520

521521
* [mutable_key_type](https://rust-lang.github.io/rust-clippy/master/index.html#mutable_key_type)
522+
* [ifs_same_cond](https://rust-lang.github.io/rust-clippy/master/index.html#ifs_same_cond)
522523

523524

524525
### allow-mixed-uninlined-format-args

clippy_lints/src/copies.rs

Lines changed: 62 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
use clippy_utils::diagnostics::{span_lint_and_note, span_lint_and_then};
22
use clippy_utils::source::{first_line_of_span, indent_of, reindent_multiline, snippet, snippet_opt};
3-
use clippy_utils::ty::needs_ordered_drop;
3+
use clippy_utils::ty::{is_interior_mut_ty, needs_ordered_drop};
44
use clippy_utils::visitors::for_each_expr;
55
use clippy_utils::{
6-
capture_local_usage, eq_expr_value, get_enclosing_block, hash_expr, hash_stmt, if_sequence, is_else_clause,
7-
is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq,
6+
capture_local_usage, def_path_def_ids, eq_expr_value, find_binding_init, get_enclosing_block, hash_expr, hash_stmt,
7+
if_sequence, is_else_clause, is_lint_allowed, path_to_local, search_same, ContainsName, HirEqInterExpr, SpanlessEq,
88
};
99
use core::iter;
1010
use core::ops::ControlFlow;
1111
use rustc_errors::Applicability;
12+
use rustc_hir::def_id::DefIdSet;
1213
use rustc_hir::intravisit;
1314
use rustc_hir::{BinOpKind, Block, Expr, ExprKind, HirId, HirIdSet, Stmt, StmtKind};
1415
use rustc_lint::{LateContext, LateLintPass};
15-
use rustc_session::{declare_lint_pass, declare_tool_lint};
16+
use rustc_middle::query::Key;
17+
use rustc_session::{declare_tool_lint, impl_lint_pass};
1618
use rustc_span::hygiene::walk_chain;
1719
use rustc_span::source_map::SourceMap;
1820
use rustc_span::{BytePos, Span, Symbol};
@@ -159,18 +161,40 @@ declare_clippy_lint! {
159161
"`if` statement with shared code in all blocks"
160162
}
161163

162-
declare_lint_pass!(CopyAndPaste => [
164+
pub struct CopyAndPaste {
165+
ignore_interior_mutability: Vec<String>,
166+
ignored_ty_ids: DefIdSet,
167+
}
168+
169+
impl CopyAndPaste {
170+
pub fn new(ignore_interior_mutability: Vec<String>) -> Self {
171+
Self {
172+
ignore_interior_mutability,
173+
ignored_ty_ids: DefIdSet::new(),
174+
}
175+
}
176+
}
177+
178+
impl_lint_pass!(CopyAndPaste => [
163179
IFS_SAME_COND,
164180
SAME_FUNCTIONS_IN_IF_CONDITION,
165181
IF_SAME_THEN_ELSE,
166182
BRANCHES_SHARING_CODE
167183
]);
168184

169185
impl<'tcx> LateLintPass<'tcx> for CopyAndPaste {
186+
fn check_crate(&mut self, cx: &LateContext<'tcx>) {
187+
for ignored_ty in &self.ignore_interior_mutability {
188+
let path: Vec<&str> = ignored_ty.split("::").collect();
189+
for id in def_path_def_ids(cx, path.as_slice()) {
190+
self.ignored_ty_ids.insert(id);
191+
}
192+
}
193+
}
170194
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
171195
if !expr.span.from_expansion() && matches!(expr.kind, ExprKind::If(..)) && !is_else_clause(cx.tcx, expr) {
172196
let (conds, blocks) = if_sequence(expr);
173-
lint_same_cond(cx, &conds);
197+
lint_same_cond(cx, &conds, &self.ignored_ty_ids);
174198
lint_same_fns_in_if_cond(cx, &conds);
175199
let all_same =
176200
!is_lint_allowed(cx, IF_SAME_THEN_ELSE, expr.hir_id) && lint_if_same_then_else(cx, &conds, &blocks);
@@ -547,9 +571,39 @@ fn check_for_warn_of_moved_symbol(cx: &LateContext<'_>, symbols: &[(HirId, Symbo
547571
})
548572
}
549573

574+
fn method_caller_is_mutable(cx: &LateContext<'_>, caller_expr: &Expr<'_>, ignored_ty_ids: &DefIdSet) -> bool {
575+
let caller_ty = cx.typeck_results().expr_ty(caller_expr);
576+
// Check if given type has inner mutability and was not set to ignored by the configuration
577+
let is_inner_mut_ty = is_interior_mut_ty(cx, caller_ty)
578+
&& !matches!(caller_ty.ty_adt_id(), Some(adt_id) if ignored_ty_ids.contains(&adt_id));
579+
580+
is_inner_mut_ty
581+
|| caller_ty.is_mutable_ptr()
582+
// `find_binding_init` will return the binding iff its not mutable
583+
|| path_to_local(caller_expr)
584+
.and_then(|hid| find_binding_init(cx, hid))
585+
.is_none()
586+
}
587+
550588
/// Implementation of `IFS_SAME_COND`.
551-
fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>]) {
552-
for (i, j) in search_same(conds, |e| hash_expr(cx, e), |lhs, rhs| eq_expr_value(cx, lhs, rhs)) {
589+
fn lint_same_cond(cx: &LateContext<'_>, conds: &[&Expr<'_>], ignored_ty_ids: &DefIdSet) {
590+
for (i, j) in search_same(
591+
conds,
592+
|e| hash_expr(cx, e),
593+
|lhs, rhs| {
594+
// Ignore eq_expr side effects iff one of the expressin kind is a method call
595+
// and the caller is not a mutable, including inner mutable type.
596+
if let ExprKind::MethodCall(_, caller, _, _) = lhs.kind {
597+
if method_caller_is_mutable(cx, caller, ignored_ty_ids) {
598+
false
599+
} else {
600+
SpanlessEq::new(cx).eq_expr(lhs, rhs)
601+
}
602+
} else {
603+
eq_expr_value(cx, lhs, rhs)
604+
}
605+
},
606+
) {
553607
span_lint_and_note(
554608
cx,
555609
IFS_SAME_COND,

clippy_lints/src/lib.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -656,7 +656,8 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
656656
store.register_late_pass(|_| Box::new(empty_enum::EmptyEnum));
657657
store.register_late_pass(|_| Box::new(invalid_upcast_comparisons::InvalidUpcastComparisons));
658658
store.register_late_pass(|_| Box::new(regex::Regex));
659-
store.register_late_pass(|_| Box::new(copies::CopyAndPaste));
659+
let ignore_interior_mutability = conf.ignore_interior_mutability.clone();
660+
store.register_late_pass(move |_| Box::new(copies::CopyAndPaste::new(ignore_interior_mutability.clone())));
660661
store.register_late_pass(|_| Box::new(copy_iterator::CopyIterator));
661662
store.register_late_pass(|_| Box::new(format::UselessFormat));
662663
store.register_late_pass(|_| Box::new(swap::Swap));

clippy_lints/src/mut_key.rs

Lines changed: 13 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use clippy_utils::diagnostics::span_lint;
2+
use clippy_utils::ty::is_interior_mut_ty;
23
use clippy_utils::{def_path_def_ids, trait_ref_of_method};
34
use rustc_data_structures::fx::FxHashSet;
45
use rustc_hir as hir;
56
use rustc_lint::{LateContext, LateLintPass};
6-
use rustc_middle::ty::TypeVisitableExt;
7-
use rustc_middle::ty::{Adt, Array, Ref, Slice, Tuple, Ty};
7+
use rustc_middle::query::Key;
8+
use rustc_middle::ty::{Adt, Ty};
89
use rustc_session::{declare_tool_lint, impl_lint_pass};
910
use rustc_span::def_id::LocalDefId;
1011
use rustc_span::source_map::Span;
@@ -153,53 +154,18 @@ impl MutableKeyType {
153154
let is_keyed_type = [sym::HashMap, sym::BTreeMap, sym::HashSet, sym::BTreeSet]
154155
.iter()
155156
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def.did()));
156-
if is_keyed_type && self.is_interior_mutable_type(cx, substs.type_at(0)) {
157-
span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
157+
if !is_keyed_type {
158+
return;
158159
}
159-
}
160-
}
161160

162-
/// Determines if a type contains interior mutability which would affect its implementation of
163-
/// [`Hash`] or [`Ord`].
164-
fn is_interior_mutable_type<'tcx>(&self, cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
165-
match *ty.kind() {
166-
Ref(_, inner_ty, mutbl) => mutbl == hir::Mutability::Mut || self.is_interior_mutable_type(cx, inner_ty),
167-
Slice(inner_ty) => self.is_interior_mutable_type(cx, inner_ty),
168-
Array(inner_ty, size) => {
169-
size.try_eval_target_usize(cx.tcx, cx.param_env)
170-
.map_or(true, |u| u != 0)
171-
&& self.is_interior_mutable_type(cx, inner_ty)
172-
},
173-
Tuple(fields) => fields.iter().any(|ty| self.is_interior_mutable_type(cx, ty)),
174-
Adt(def, substs) => {
175-
// Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
176-
// that of their type parameters. Note: we don't include `HashSet` and `HashMap`
177-
// because they have no impl for `Hash` or `Ord`.
178-
let def_id = def.did();
179-
let is_std_collection = [
180-
sym::Option,
181-
sym::Result,
182-
sym::LinkedList,
183-
sym::Vec,
184-
sym::VecDeque,
185-
sym::BTreeMap,
186-
sym::BTreeSet,
187-
sym::Rc,
188-
sym::Arc,
189-
]
190-
.iter()
191-
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def_id));
192-
let is_box = Some(def_id) == cx.tcx.lang_items().owned_box();
193-
if is_std_collection || is_box || self.ignore_mut_def_ids.contains(&def_id) {
194-
// The type is mutable if any of its type parameters are
195-
substs.types().any(|ty| self.is_interior_mutable_type(cx, ty))
196-
} else {
197-
!ty.has_escaping_bound_vars()
198-
&& cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
199-
&& !ty.is_freeze(cx.tcx, cx.param_env)
200-
}
201-
},
202-
_ => false,
161+
let subst_ty = substs.type_at(0);
162+
// Determines if a type contains interior mutability which would affect its implementation of
163+
// [`Hash`] or [`Ord`].
164+
if is_interior_mut_ty(cx, subst_ty)
165+
&& !matches!(subst_ty.ty_adt_id(), Some(adt_id) if self.ignore_mut_def_ids.contains(&adt_id))
166+
{
167+
span_lint(cx, MUTABLE_KEY_TYPE, span, "mutable key type");
168+
}
203169
}
204170
}
205171
}

clippy_lints/src/utils/conf.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ define_Conf! {
437437
///
438438
/// The maximum size of the `Err`-variant in a `Result` returned from a function
439439
(large_error_threshold: u64 = 128),
440-
/// Lint: MUTABLE_KEY_TYPE.
440+
/// Lint: MUTABLE_KEY_TYPE, IFS_SAME_COND.
441441
///
442442
/// A list of paths to types that should be treated like `Arc`, i.e. ignored but
443443
/// for the generic parameters for determining interior mutability

clippy_utils/src/ty.rs

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1121,3 +1121,47 @@ pub fn make_normalized_projection<'tcx>(
11211121
}
11221122
helper(tcx, param_env, make_projection(tcx, container_id, assoc_ty, substs)?)
11231123
}
1124+
1125+
/// Check if given type has inner mutability such as [`std::cell::Cell`] or [`std::cell::RefCell`]
1126+
/// etc.
1127+
pub fn is_interior_mut_ty<'tcx>(cx: &LateContext<'tcx>, ty: Ty<'tcx>) -> bool {
1128+
match *ty.kind() {
1129+
ty::Ref(_, inner_ty, mutbl) => mutbl == Mutability::Mut || is_interior_mut_ty(cx, inner_ty),
1130+
ty::Slice(inner_ty) => is_interior_mut_ty(cx, inner_ty),
1131+
ty::Array(inner_ty, size) => {
1132+
size.try_eval_target_usize(cx.tcx, cx.param_env)
1133+
.map_or(true, |u| u != 0)
1134+
&& is_interior_mut_ty(cx, inner_ty)
1135+
},
1136+
ty::Tuple(fields) => fields.iter().any(|ty| is_interior_mut_ty(cx, ty)),
1137+
ty::Adt(def, substs) => {
1138+
// Special case for collections in `std` who's impl of `Hash` or `Ord` delegates to
1139+
// that of their type parameters. Note: we don't include `HashSet` and `HashMap`
1140+
// because they have no impl for `Hash` or `Ord`.
1141+
let def_id = def.did();
1142+
let is_std_collection = [
1143+
sym::Option,
1144+
sym::Result,
1145+
sym::LinkedList,
1146+
sym::Vec,
1147+
sym::VecDeque,
1148+
sym::BTreeMap,
1149+
sym::BTreeSet,
1150+
sym::Rc,
1151+
sym::Arc,
1152+
]
1153+
.iter()
1154+
.any(|diag_item| cx.tcx.is_diagnostic_item(*diag_item, def_id));
1155+
let is_box = Some(def_id) == cx.tcx.lang_items().owned_box();
1156+
if is_std_collection || is_box {
1157+
// The type is mutable if any of its type parameters are
1158+
substs.types().any(|ty| is_interior_mut_ty(cx, ty))
1159+
} else {
1160+
!ty.has_escaping_bound_vars()
1161+
&& cx.tcx.layout_of(cx.param_env.and(ty)).is_ok()
1162+
&& !ty.is_freeze(cx.tcx, cx.param_env)
1163+
}
1164+
},
1165+
_ => false,
1166+
}
1167+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
ignore-interior-mutability = ["std::cell::Cell"]
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
#![warn(clippy::ifs_same_cond)]
2+
#![allow(clippy::if_same_then_else, clippy::comparison_chain)]
3+
4+
fn main() {}
5+
6+
fn issue10272() {
7+
use std::cell::Cell;
8+
9+
// Because the `ignore-interior-mutability` configuration
10+
// is set to ignore for `std::cell::Cell`, the following `get()` calls
11+
// should trigger warning
12+
let x = Cell::new(true);
13+
if x.get() {
14+
} else if !x.take() {
15+
} else if x.get() {
16+
} else {
17+
}
18+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
error: this `if` has the same condition as a previous `if`
2+
--> $DIR/ifs_same_cond.rs:15:15
3+
|
4+
LL | } else if x.get() {
5+
| ^^^^^^^
6+
|
7+
note: same as this
8+
--> $DIR/ifs_same_cond.rs:13:8
9+
|
10+
LL | if x.get() {
11+
| ^^^^^^^
12+
= note: `-D clippy::ifs-same-cond` implied by `-D warnings`
13+
14+
error: aborting due to previous error
15+

tests/ui/ifs_same_cond.rs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,30 @@ fn ifs_same_cond() {
4343
}
4444
}
4545

46+
fn issue10272() {
47+
let a = String::from("ha");
48+
if a.contains("ah") {
49+
} else if a.contains("ah") {
50+
// Trigger this lint
51+
} else if a.contains("ha") {
52+
} else if a == "wow" {
53+
}
54+
55+
let p: *mut i8 = std::ptr::null_mut();
56+
if p.is_null() {
57+
} else if p.align_offset(0) == 0 {
58+
} else if p.is_null() {
59+
// ok, p is mutable pointer
60+
} else {
61+
}
62+
63+
let x = std::cell::Cell::new(true);
64+
if x.get() {
65+
} else if !x.take() {
66+
} else if x.get() {
67+
// ok, x is interior mutable type
68+
} else {
69+
}
70+
}
71+
4672
fn main() {}

tests/ui/ifs_same_cond.stderr

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,5 +35,17 @@ note: same as this
3535
LL | if 2 * a == 1 {
3636
| ^^^^^^^^^^
3737

38-
error: aborting due to 3 previous errors
38+
error: this `if` has the same condition as a previous `if`
39+
--> $DIR/ifs_same_cond.rs:49:15
40+
|
41+
LL | } else if a.contains("ah") {
42+
| ^^^^^^^^^^^^^^^^
43+
|
44+
note: same as this
45+
--> $DIR/ifs_same_cond.rs:48:8
46+
|
47+
LL | if a.contains("ah") {
48+
| ^^^^^^^^^^^^^^^^
49+
50+
error: aborting due to 4 previous errors
3951

0 commit comments

Comments
 (0)