Skip to content

Commit 1c64211

Browse files
authored
Fix manual is multiple of (#15205)
Fix several issues with `manual_is_multiple_of` - `&a % &b == 0` compiles, but requires dereferencing `b` when replacing with `a.is_multiple_of(b)`. - In `a % b == 0`, if type of `a` is not certain, `a.is_multiple_of(b)` might not be typable. - In `a % b == 0`, `a` and `b` must be unsigned integers, not any arbitrary types implementing `Rem` and outputing an integer. Those fixes have required increasing the precision of type certainty determination in the two first patches. Fixes #15203 Fixes #15204 changelog: [`manual_is_multiple_of`]: fix various false positive
2 parents ba947c5 + a8ab02c commit 1c64211

File tree

5 files changed

+268
-19
lines changed

5 files changed

+268
-19
lines changed

clippy_lints/src/operators/manual_is_multiple_of.rs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,12 @@ use clippy_utils::consts::is_zero_integer_const;
22
use clippy_utils::diagnostics::span_lint_and_sugg;
33
use clippy_utils::msrvs::{self, Msrv};
44
use clippy_utils::sugg::Sugg;
5+
use clippy_utils::ty::expr_type_is_certain;
56
use rustc_ast::BinOpKind;
67
use rustc_errors::Applicability;
78
use rustc_hir::{Expr, ExprKind};
89
use rustc_lint::LateContext;
9-
use rustc_middle::ty;
10+
use rustc_middle::ty::{self, Ty};
1011

1112
use super::MANUAL_IS_MULTIPLE_OF;
1213

@@ -22,9 +23,21 @@ pub(super) fn check<'tcx>(
2223
&& let Some(operand) = uint_compare_to_zero(cx, op, lhs, rhs)
2324
&& let ExprKind::Binary(operand_op, operand_left, operand_right) = operand.kind
2425
&& operand_op.node == BinOpKind::Rem
26+
&& matches!(
27+
cx.typeck_results().expr_ty_adjusted(operand_left).peel_refs().kind(),
28+
ty::Uint(_)
29+
)
30+
&& matches!(
31+
cx.typeck_results().expr_ty_adjusted(operand_right).peel_refs().kind(),
32+
ty::Uint(_)
33+
)
34+
&& expr_type_is_certain(cx, operand_left)
2535
{
2636
let mut app = Applicability::MachineApplicable;
27-
let divisor = Sugg::hir_with_applicability(cx, operand_right, "_", &mut app);
37+
let divisor = deref_sugg(
38+
Sugg::hir_with_applicability(cx, operand_right, "_", &mut app),
39+
cx.typeck_results().expr_ty_adjusted(operand_right),
40+
);
2841
span_lint_and_sugg(
2942
cx,
3043
MANUAL_IS_MULTIPLE_OF,
@@ -64,3 +77,11 @@ fn uint_compare_to_zero<'tcx>(
6477

6578
matches!(cx.typeck_results().expr_ty_adjusted(operand).kind(), ty::Uint(_)).then_some(operand)
6679
}
80+
81+
fn deref_sugg<'a>(sugg: Sugg<'a>, ty: Ty<'_>) -> Sugg<'a> {
82+
if let ty::Ref(_, target_ty, _) = ty.kind() {
83+
deref_sugg(sugg.deref(), *target_ty)
84+
} else {
85+
sugg
86+
}
87+
}

clippy_utils/src/ty/type_certainty/mod.rs

Lines changed: 58 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
//! be considered a bug.
1313
1414
use crate::paths::{PathNS, lookup_path};
15+
use rustc_ast::{LitFloatType, LitIntType, LitKind};
1516
use rustc_hir::def::{DefKind, Res};
1617
use rustc_hir::def_id::DefId;
1718
use rustc_hir::intravisit::{InferKind, Visitor, VisitorExt, walk_qpath, walk_ty};
18-
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, PathSegment, QPath, TyKind};
19+
use rustc_hir::{self as hir, AmbigArg, Expr, ExprKind, GenericArgs, HirId, Node, Param, PathSegment, QPath, TyKind};
1920
use rustc_lint::LateContext;
2021
use rustc_middle::ty::{self, AdtDef, GenericArgKind, Ty};
2122
use rustc_span::Span;
@@ -24,30 +25,32 @@ mod certainty;
2425
use certainty::{Certainty, Meet, join, meet};
2526

2627
pub fn expr_type_is_certain(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
27-
expr_type_certainty(cx, expr).is_certain()
28+
expr_type_certainty(cx, expr, false).is_certain()
2829
}
2930

30-
fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
31+
/// Determine the type certainty of `expr`. `in_arg` indicates that the expression happens within
32+
/// the evaluation of a function or method call argument.
33+
fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>, in_arg: bool) -> Certainty {
3134
let certainty = match &expr.kind {
3235
ExprKind::Unary(_, expr)
3336
| ExprKind::Field(expr, _)
3437
| ExprKind::Index(expr, _, _)
35-
| ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr),
38+
| ExprKind::AddrOf(_, _, expr) => expr_type_certainty(cx, expr, in_arg),
3639

37-
ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr))),
40+
ExprKind::Array(exprs) => join(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))),
3841

3942
ExprKind::Call(callee, args) => {
40-
let lhs = expr_type_certainty(cx, callee);
43+
let lhs = expr_type_certainty(cx, callee, false);
4144
let rhs = if type_is_inferable_from_arguments(cx, expr) {
42-
meet(args.iter().map(|arg| expr_type_certainty(cx, arg)))
45+
meet(args.iter().map(|arg| expr_type_certainty(cx, arg, true)))
4346
} else {
4447
Certainty::Uncertain
4548
};
4649
lhs.join_clearing_def_ids(rhs)
4750
},
4851

4952
ExprKind::MethodCall(method, receiver, args, _) => {
50-
let mut receiver_type_certainty = expr_type_certainty(cx, receiver);
53+
let mut receiver_type_certainty = expr_type_certainty(cx, receiver, false);
5154
// Even if `receiver_type_certainty` is `Certain(Some(..))`, the `Self` type in the method
5255
// identified by `type_dependent_def_id(..)` can differ. This can happen as a result of a `deref`,
5356
// for example. So update the `DefId` in `receiver_type_certainty` (if any).
@@ -59,24 +62,48 @@ fn expr_type_certainty(cx: &LateContext<'_>, expr: &Expr<'_>) -> Certainty {
5962
let lhs = path_segment_certainty(cx, receiver_type_certainty, method, false);
6063
let rhs = if type_is_inferable_from_arguments(cx, expr) {
6164
meet(
62-
std::iter::once(receiver_type_certainty).chain(args.iter().map(|arg| expr_type_certainty(cx, arg))),
65+
std::iter::once(receiver_type_certainty)
66+
.chain(args.iter().map(|arg| expr_type_certainty(cx, arg, true))),
6367
)
6468
} else {
6569
Certainty::Uncertain
6670
};
6771
lhs.join(rhs)
6872
},
6973

70-
ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr))),
74+
ExprKind::Tup(exprs) => meet(exprs.iter().map(|expr| expr_type_certainty(cx, expr, in_arg))),
7175

72-
ExprKind::Binary(_, lhs, rhs) => expr_type_certainty(cx, lhs).meet(expr_type_certainty(cx, rhs)),
76+
ExprKind::Binary(_, lhs, rhs) => {
77+
// If one of the side of the expression is uncertain, the certainty will come from the other side,
78+
// with no information on the type.
79+
match (
80+
expr_type_certainty(cx, lhs, in_arg),
81+
expr_type_certainty(cx, rhs, in_arg),
82+
) {
83+
(Certainty::Uncertain, Certainty::Certain(_)) | (Certainty::Certain(_), Certainty::Uncertain) => {
84+
Certainty::Certain(None)
85+
},
86+
(l, r) => l.meet(r),
87+
}
88+
},
7389

74-
ExprKind::Lit(_) => Certainty::Certain(None),
90+
ExprKind::Lit(lit) => {
91+
if !in_arg
92+
&& matches!(
93+
lit.node,
94+
LitKind::Int(_, LitIntType::Unsuffixed) | LitKind::Float(_, LitFloatType::Unsuffixed)
95+
)
96+
{
97+
Certainty::Uncertain
98+
} else {
99+
Certainty::Certain(None)
100+
}
101+
},
75102

76103
ExprKind::Cast(_, ty) => type_certainty(cx, ty),
77104

78105
ExprKind::If(_, if_expr, Some(else_expr)) => {
79-
expr_type_certainty(cx, if_expr).join(expr_type_certainty(cx, else_expr))
106+
expr_type_certainty(cx, if_expr, in_arg).join(expr_type_certainty(cx, else_expr, in_arg))
80107
},
81108

82109
ExprKind::Path(qpath) => qpath_certainty(cx, qpath, false),
@@ -188,6 +215,20 @@ fn qpath_certainty(cx: &LateContext<'_>, qpath: &QPath<'_>, resolves_to_type: bo
188215
certainty
189216
}
190217

218+
/// Tries to tell whether `param` resolves to something certain, e.g., a non-wildcard type if
219+
/// present. The certainty `DefId` is cleared before returning.
220+
fn param_certainty(cx: &LateContext<'_>, param: &Param<'_>) -> Certainty {
221+
let owner_did = cx.tcx.hir_enclosing_body_owner(param.hir_id);
222+
let Some(fn_decl) = cx.tcx.hir_fn_decl_by_hir_id(cx.tcx.local_def_id_to_hir_id(owner_did)) else {
223+
return Certainty::Uncertain;
224+
};
225+
let inputs = fn_decl.inputs;
226+
let body_params = cx.tcx.hir_body_owned_by(owner_did).params;
227+
std::iter::zip(body_params, inputs)
228+
.find(|(p, _)| p.hir_id == param.hir_id)
229+
.map_or(Certainty::Uncertain, |(_, ty)| type_certainty(cx, ty).clear_def_id())
230+
}
231+
191232
fn path_segment_certainty(
192233
cx: &LateContext<'_>,
193234
parent_certainty: Certainty,
@@ -240,15 +281,16 @@ fn path_segment_certainty(
240281

241282
// `get_parent` because `hir_id` refers to a `Pat`, and we're interested in the node containing the `Pat`.
242283
Res::Local(hir_id) => match cx.tcx.parent_hir_node(hir_id) {
243-
// An argument's type is always certain.
244-
Node::Param(..) => Certainty::Certain(None),
284+
// A parameter's type is not always certain, as it may come from an untyped closure definition,
285+
// or from a wildcard in a typed closure definition.
286+
Node::Param(param) => param_certainty(cx, param),
245287
// A local's type is certain if its type annotation is certain or it has an initializer whose
246288
// type is certain.
247289
Node::LetStmt(local) => {
248290
let lhs = local.ty.map_or(Certainty::Uncertain, |ty| type_certainty(cx, ty));
249291
let rhs = local
250292
.init
251-
.map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init));
293+
.map_or(Certainty::Uncertain, |init| expr_type_certainty(cx, init, false));
252294
let certainty = lhs.join(rhs);
253295
if resolves_to_type {
254296
certainty

tests/ui/manual_is_multiple_of.fixed

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,81 @@ fn f(a: u64, b: u64) {
2323
fn g(a: u64, b: u64) {
2424
let _ = a % b == 0;
2525
}
26+
27+
fn needs_deref(a: &u64, b: &u64) {
28+
let _ = a.is_multiple_of(*b); //~ manual_is_multiple_of
29+
}
30+
31+
fn closures(a: u64, b: u64) {
32+
// Do not lint, types are ambiguous at this point
33+
let cl = |a, b| a % b == 0;
34+
let _ = cl(a, b);
35+
36+
// Do not lint, types are ambiguous at this point
37+
let cl = |a: _, b: _| a % b == 0;
38+
let _ = cl(a, b);
39+
40+
// Type of `a` is enough
41+
let cl = |a: u64, b| a.is_multiple_of(b); //~ manual_is_multiple_of
42+
let _ = cl(a, b);
43+
44+
// Type of `a` is enough
45+
let cl = |a: &u64, b| a.is_multiple_of(b); //~ manual_is_multiple_of
46+
let _ = cl(&a, b);
47+
48+
// Type of `b` is not enough
49+
let cl = |a, b: u64| a % b == 0;
50+
let _ = cl(&a, b);
51+
}
52+
53+
fn any_rem<T: std::ops::Rem<Output = u32>>(a: T, b: T) {
54+
// An arbitrary `Rem` implementation should not lint
55+
let _ = a % b == 0;
56+
}
57+
58+
mod issue15103 {
59+
fn foo() -> Option<u64> {
60+
let mut n: u64 = 150_000_000;
61+
62+
(2..).find(|p| {
63+
while n.is_multiple_of(*p) {
64+
//~^ manual_is_multiple_of
65+
n /= p;
66+
}
67+
n <= 1
68+
})
69+
}
70+
71+
const fn generate_primes<const N: usize>() -> [u64; N] {
72+
let mut result = [0; N];
73+
if N == 0 {
74+
return result;
75+
}
76+
result[0] = 2;
77+
if N == 1 {
78+
return result;
79+
}
80+
let mut idx = 1;
81+
let mut p = 3;
82+
while idx < N {
83+
let mut j = 0;
84+
while j < idx && p % result[j] != 0 {
85+
j += 1;
86+
}
87+
if j == idx {
88+
result[idx] = p;
89+
idx += 1;
90+
}
91+
p += 1;
92+
}
93+
result
94+
}
95+
96+
fn bar() -> u32 {
97+
let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n.is_multiple_of(*i)).sum() };
98+
//~^ manual_is_multiple_of
99+
100+
let d = |n| (1..=n / 2).filter(|i| n % i == 0).sum();
101+
(1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum()
102+
}
103+
}

tests/ui/manual_is_multiple_of.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,81 @@ fn f(a: u64, b: u64) {
2323
fn g(a: u64, b: u64) {
2424
let _ = a % b == 0;
2525
}
26+
27+
fn needs_deref(a: &u64, b: &u64) {
28+
let _ = a % b == 0; //~ manual_is_multiple_of
29+
}
30+
31+
fn closures(a: u64, b: u64) {
32+
// Do not lint, types are ambiguous at this point
33+
let cl = |a, b| a % b == 0;
34+
let _ = cl(a, b);
35+
36+
// Do not lint, types are ambiguous at this point
37+
let cl = |a: _, b: _| a % b == 0;
38+
let _ = cl(a, b);
39+
40+
// Type of `a` is enough
41+
let cl = |a: u64, b| a % b == 0; //~ manual_is_multiple_of
42+
let _ = cl(a, b);
43+
44+
// Type of `a` is enough
45+
let cl = |a: &u64, b| a % b == 0; //~ manual_is_multiple_of
46+
let _ = cl(&a, b);
47+
48+
// Type of `b` is not enough
49+
let cl = |a, b: u64| a % b == 0;
50+
let _ = cl(&a, b);
51+
}
52+
53+
fn any_rem<T: std::ops::Rem<Output = u32>>(a: T, b: T) {
54+
// An arbitrary `Rem` implementation should not lint
55+
let _ = a % b == 0;
56+
}
57+
58+
mod issue15103 {
59+
fn foo() -> Option<u64> {
60+
let mut n: u64 = 150_000_000;
61+
62+
(2..).find(|p| {
63+
while n % p == 0 {
64+
//~^ manual_is_multiple_of
65+
n /= p;
66+
}
67+
n <= 1
68+
})
69+
}
70+
71+
const fn generate_primes<const N: usize>() -> [u64; N] {
72+
let mut result = [0; N];
73+
if N == 0 {
74+
return result;
75+
}
76+
result[0] = 2;
77+
if N == 1 {
78+
return result;
79+
}
80+
let mut idx = 1;
81+
let mut p = 3;
82+
while idx < N {
83+
let mut j = 0;
84+
while j < idx && p % result[j] != 0 {
85+
j += 1;
86+
}
87+
if j == idx {
88+
result[idx] = p;
89+
idx += 1;
90+
}
91+
p += 1;
92+
}
93+
result
94+
}
95+
96+
fn bar() -> u32 {
97+
let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n % i == 0).sum() };
98+
//~^ manual_is_multiple_of
99+
100+
let d = |n| (1..=n / 2).filter(|i| n % i == 0).sum();
101+
(1..1_000).filter(|&i| i == d(d(i)) && i != d(i)).sum()
102+
}
103+
}

tests/ui/manual_is_multiple_of.stderr

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,35 @@ error: manual implementation of `.is_multiple_of()`
3737
LL | let _ = 0 < a % b;
3838
| ^^^^^^^^^ help: replace with: `!a.is_multiple_of(b)`
3939

40-
error: aborting due to 6 previous errors
40+
error: manual implementation of `.is_multiple_of()`
41+
--> tests/ui/manual_is_multiple_of.rs:28:13
42+
|
43+
LL | let _ = a % b == 0;
44+
| ^^^^^^^^^^ help: replace with: `a.is_multiple_of(*b)`
45+
46+
error: manual implementation of `.is_multiple_of()`
47+
--> tests/ui/manual_is_multiple_of.rs:41:26
48+
|
49+
LL | let cl = |a: u64, b| a % b == 0;
50+
| ^^^^^^^^^^ help: replace with: `a.is_multiple_of(b)`
51+
52+
error: manual implementation of `.is_multiple_of()`
53+
--> tests/ui/manual_is_multiple_of.rs:45:27
54+
|
55+
LL | let cl = |a: &u64, b| a % b == 0;
56+
| ^^^^^^^^^^ help: replace with: `a.is_multiple_of(b)`
57+
58+
error: manual implementation of `.is_multiple_of()`
59+
--> tests/ui/manual_is_multiple_of.rs:63:19
60+
|
61+
LL | while n % p == 0 {
62+
| ^^^^^^^^^^ help: replace with: `n.is_multiple_of(*p)`
63+
64+
error: manual implementation of `.is_multiple_of()`
65+
--> tests/ui/manual_is_multiple_of.rs:97:58
66+
|
67+
LL | let d = |n: u32| -> u32 { (1..=n / 2).filter(|i| n % i == 0).sum() };
68+
| ^^^^^^^^^^ help: replace with: `n.is_multiple_of(*i)`
69+
70+
error: aborting due to 11 previous errors
4171

0 commit comments

Comments
 (0)