Skip to content

Commit a5fcaea

Browse files
committed
fix [manual_map] not catching type adjustment
1 parent 06758d8 commit a5fcaea

File tree

4 files changed

+306
-19
lines changed

4 files changed

+306
-19
lines changed

clippy_lints/src/matches/manual_utils.rs

Lines changed: 81 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ use clippy_utils::source::{snippet_with_applicability, snippet_with_context};
44
use clippy_utils::sugg::Sugg;
55
use clippy_utils::ty::{is_copy, is_type_diagnostic_item, peel_mid_ty_refs_is_mutable, type_is_unsafe_function};
66
use clippy_utils::{
7-
can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local_id,
7+
can_move_expr_to_closure, fn_def_id, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local_id,
88
peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
99
};
1010
use rustc_ast::util::parser::PREC_UNAMBIGUOUS;
1111
use rustc_errors::Applicability;
1212
use rustc_hir::def::Res;
1313
use rustc_hir::LangItem::{OptionNone, OptionSome};
14-
use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath};
14+
use rustc_hir::{self as hir, BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath};
1515
use rustc_lint::LateContext;
16+
use rustc_middle::ty::adjustment::Adjust;
17+
use rustc_middle::ty::{TypeFlags, TypeVisitableExt};
1618
use rustc_span::{sym, SyntaxContext};
1719

1820
#[expect(clippy::too_many_arguments)]
@@ -73,7 +75,7 @@ where
7375
}
7476

7577
// `map` won't perform any adjustments.
76-
if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
78+
if expr_has_type_coercion(cx, expr) {
7779
return None;
7880
}
7981

@@ -124,6 +126,12 @@ where
124126
};
125127

126128
let closure_expr_snip = some_expr.to_snippet_with_context(cx, expr_ctxt, &mut app);
129+
let closure_body = if some_expr.needs_unsafe_block {
130+
format!("unsafe {}", closure_expr_snip.blockify())
131+
} else {
132+
closure_expr_snip.to_string()
133+
};
134+
127135
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
128136
if !some_expr.needs_unsafe_block
129137
&& let Some(func) = can_pass_as_func(cx, id, some_expr.expr)
@@ -145,20 +153,12 @@ where
145153
""
146154
};
147155

148-
if some_expr.needs_unsafe_block {
149-
format!("|{annotation}{some_binding}| unsafe {{ {closure_expr_snip} }}")
150-
} else {
151-
format!("|{annotation}{some_binding}| {closure_expr_snip}")
152-
}
156+
format!("|{annotation}{some_binding}| {closure_body}")
153157
}
154158
} else if !is_wild_none && explicit_ref.is_none() {
155159
// TODO: handle explicit reference annotations.
156160
let pat_snip = snippet_with_context(cx, some_pat.span, expr_ctxt, "..", &mut app).0;
157-
if some_expr.needs_unsafe_block {
158-
format!("|{pat_snip}| unsafe {{ {closure_expr_snip} }}")
159-
} else {
160-
format!("|{pat_snip}| {closure_expr_snip}")
161-
}
161+
format!("|{pat_snip}| {closure_body}")
162162
} else {
163163
// Refutable bindings and mixed reference annotations can't be handled by `map`.
164164
return None;
@@ -274,3 +274,71 @@ pub(super) fn try_parse_pattern<'tcx>(
274274
fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
275275
is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone)
276276
}
277+
278+
fn expr_ty_adjusted(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
279+
cx.typeck_results()
280+
.expr_adjustments(expr)
281+
.iter()
282+
// We do not care about exprs with `NeverToAny` adjustments, such as `panic!` call.
283+
.any(|adj| !matches!(adj.kind, Adjust::NeverToAny))
284+
}
285+
286+
fn expr_has_type_coercion<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
287+
if expr.span.from_expansion() {
288+
return false;
289+
}
290+
if expr_ty_adjusted(cx, expr) {
291+
return true;
292+
}
293+
294+
// Identify coercion sites and recursively check it those sites
295+
// actually has type adjustments.
296+
match expr.kind {
297+
// Function/method calls, including enum initialization.
298+
ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) if let Some(def_id) = fn_def_id(cx, expr) => {
299+
let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity();
300+
if !fn_sig.output().skip_binder().has_type_flags(TypeFlags::HAS_TY_PARAM) {
301+
return false;
302+
}
303+
let mut args_with_ty_param = fn_sig
304+
.inputs()
305+
.skip_binder()
306+
.iter()
307+
.zip(args)
308+
.filter_map(|(arg_ty, arg)| if arg_ty.has_type_flags(TypeFlags::HAS_TY_PARAM) {
309+
Some(arg)
310+
} else {
311+
None
312+
});
313+
args_with_ty_param.any(|arg| expr_has_type_coercion(cx, arg))
314+
},
315+
// Struct/union initialization.
316+
ExprKind::Struct(_, fields, _) => {
317+
fields.iter().map(|expr_field| expr_field.expr).any(|ex| expr_has_type_coercion(cx, ex))
318+
},
319+
// those two `ref` keywords cannot be removed
320+
#[allow(clippy::needless_borrow)]
321+
// Function results, including the final line of a block or a `return` expression.
322+
ExprKind::Block(hir::Block { expr: Some(ref ret_expr), .. }, _) |
323+
ExprKind::Ret(Some(ref ret_expr)) => expr_has_type_coercion(cx, ret_expr),
324+
325+
// ===== Coercion-propagation expressions =====
326+
327+
// Array, where the type is `[U; n]`.
328+
ExprKind::Array(elems) |
329+
// Tuple, `(U_0, U_1, ..., U_n)`.
330+
ExprKind::Tup(elems) => {
331+
elems.iter().any(|elem| expr_has_type_coercion(cx, elem))
332+
},
333+
// Array but with repeating syntax.
334+
ExprKind::Repeat(rep_elem, _) => expr_has_type_coercion(cx, rep_elem),
335+
// Others that may contain coercion sites.
336+
ExprKind::If(_, then, maybe_else) => {
337+
expr_has_type_coercion(cx, then) || maybe_else.is_some_and(|e| expr_has_type_coercion(cx, e))
338+
}
339+
ExprKind::Match(_, arms, _) => {
340+
arms.iter().map(|arm| arm.body).any(|body| expr_has_type_coercion(cx, body))
341+
}
342+
_ => false
343+
}
344+
}

tests/ui/manual_map_option_2.fixed

Lines changed: 92 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,14 @@ fn main() {
4040
None => None,
4141
};
4242

43-
// Lint. `s` is captured by reference, so no lifetime issues.
4443
let s = Some(String::new());
44+
// Lint. `s` is captured by reference, so no lifetime issues.
4545
let _ = s.as_ref().map(|x| { if let Some(ref s) = s { (x.clone(), s) } else { panic!() } });
46+
// Don't lint this, type of `s` is coercioned from `&String` to `&str`
47+
let x: Option<(String, &str)> = match &s {
48+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
49+
None => None,
50+
};
4651

4752
// Issue #7820
4853
unsafe fn f(x: u32) -> u32 {
@@ -54,3 +59,89 @@ fn main() {
5459
let _ = Some(0).map(|x| unsafe { f(x) });
5560
let _ = Some(0).map(|x| unsafe { f(x) });
5661
}
62+
63+
// issue #12659
64+
mod with_type_coercion {
65+
trait DummyTrait {}
66+
67+
fn foo<T: DummyTrait, F: Fn() -> Result<T, ()>>(f: F) {
68+
// Don't lint
69+
let _: Option<Result<Box<dyn DummyTrait>, ()>> = match Some(0) {
70+
Some(_) => Some(match f() {
71+
Ok(res) => Ok(Box::new(res)),
72+
_ => Err(()),
73+
}),
74+
None => None,
75+
};
76+
77+
let _: Option<Box<&[u8]>> = match Some(()) {
78+
Some(_) => Some(Box::new(b"1234")),
79+
None => None,
80+
};
81+
82+
let x = String::new();
83+
let _: Option<Box<&str>> = match Some(()) {
84+
Some(_) => Some(Box::new(&x)),
85+
None => None,
86+
};
87+
88+
let _: Option<&str> = match Some(()) {
89+
Some(_) => Some(&x),
90+
None => None,
91+
};
92+
93+
//~v ERROR: manual implementation of `Option::map`
94+
let _ = Some(0).map(|_| match f() {
95+
Ok(res) => Ok(Box::new(res)),
96+
_ => Err(()),
97+
});
98+
}
99+
100+
#[allow(clippy::redundant_allocation)]
101+
fn bar() {
102+
fn f(_: Option<Box<&[u8]>>) {}
103+
fn g(b: &[u8]) -> Box<&[u8]> {
104+
Box::new(b)
105+
}
106+
107+
let x: &[u8; 4] = b"1234";
108+
f(match Some(()) {
109+
Some(_) => Some(Box::new(x)),
110+
None => None,
111+
});
112+
113+
//~v ERROR: manual implementation of `Option::map`
114+
let _: Option<Box<&[u8]>> = Some(0).map(|_| g(x));
115+
}
116+
117+
fn with_fn_ret(s: &Option<String>) -> Option<(String, &str)> {
118+
// Don't lint, `map` doesn't work as the return type is adjusted.
119+
match s {
120+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
121+
None => None,
122+
}
123+
}
124+
125+
fn with_fn_ret_2(s: &Option<String>) -> Option<(String, &str)> {
126+
if true {
127+
// Don't lint, `map` doesn't work as the return type is adjusted.
128+
return match s {
129+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
130+
None => None,
131+
};
132+
}
133+
None
134+
}
135+
136+
#[allow(clippy::needless_late_init)]
137+
fn with_fn_ret_3<'a>(s: &'a Option<String>) -> Option<(String, &'a str)> {
138+
let x: Option<(String, &'a str)>;
139+
x = {
140+
match s {
141+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
142+
None => None,
143+
}
144+
};
145+
x
146+
}
147+
}

tests/ui/manual_map_option_2.rs

Lines changed: 98 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,17 @@ fn main() {
4343
None => None,
4444
};
4545

46-
// Lint. `s` is captured by reference, so no lifetime issues.
4746
let s = Some(String::new());
47+
// Lint. `s` is captured by reference, so no lifetime issues.
4848
let _ = match &s {
4949
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
5050
None => None,
5151
};
52+
// Don't lint this, type of `s` is coercioned from `&String` to `&str`
53+
let x: Option<(String, &str)> = match &s {
54+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
55+
None => None,
56+
};
5257

5358
// Issue #7820
5459
unsafe fn f(x: u32) -> u32 {
@@ -69,3 +74,95 @@ fn main() {
6974
None => None,
7075
};
7176
}
77+
78+
// issue #12659
79+
mod with_type_coercion {
80+
trait DummyTrait {}
81+
82+
fn foo<T: DummyTrait, F: Fn() -> Result<T, ()>>(f: F) {
83+
// Don't lint
84+
let _: Option<Result<Box<dyn DummyTrait>, ()>> = match Some(0) {
85+
Some(_) => Some(match f() {
86+
Ok(res) => Ok(Box::new(res)),
87+
_ => Err(()),
88+
}),
89+
None => None,
90+
};
91+
92+
let _: Option<Box<&[u8]>> = match Some(()) {
93+
Some(_) => Some(Box::new(b"1234")),
94+
None => None,
95+
};
96+
97+
let x = String::new();
98+
let _: Option<Box<&str>> = match Some(()) {
99+
Some(_) => Some(Box::new(&x)),
100+
None => None,
101+
};
102+
103+
let _: Option<&str> = match Some(()) {
104+
Some(_) => Some(&x),
105+
None => None,
106+
};
107+
108+
//~v ERROR: manual implementation of `Option::map`
109+
let _ = match Some(0) {
110+
Some(_) => Some(match f() {
111+
Ok(res) => Ok(Box::new(res)),
112+
_ => Err(()),
113+
}),
114+
None => None,
115+
};
116+
}
117+
118+
#[allow(clippy::redundant_allocation)]
119+
fn bar() {
120+
fn f(_: Option<Box<&[u8]>>) {}
121+
fn g(b: &[u8]) -> Box<&[u8]> {
122+
Box::new(b)
123+
}
124+
125+
let x: &[u8; 4] = b"1234";
126+
f(match Some(()) {
127+
Some(_) => Some(Box::new(x)),
128+
None => None,
129+
});
130+
131+
//~v ERROR: manual implementation of `Option::map`
132+
let _: Option<Box<&[u8]>> = match Some(0) {
133+
Some(_) => Some(g(x)),
134+
None => None,
135+
};
136+
}
137+
138+
fn with_fn_ret(s: &Option<String>) -> Option<(String, &str)> {
139+
// Don't lint, `map` doesn't work as the return type is adjusted.
140+
match s {
141+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
142+
None => None,
143+
}
144+
}
145+
146+
fn with_fn_ret_2(s: &Option<String>) -> Option<(String, &str)> {
147+
if true {
148+
// Don't lint, `map` doesn't work as the return type is adjusted.
149+
return match s {
150+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
151+
None => None,
152+
};
153+
}
154+
None
155+
}
156+
157+
#[allow(clippy::needless_late_init)]
158+
fn with_fn_ret_3<'a>(s: &'a Option<String>) -> Option<(String, &'a str)> {
159+
let x: Option<(String, &'a str)>;
160+
x = {
161+
match s {
162+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
163+
None => None,
164+
}
165+
};
166+
x
167+
}
168+
}

0 commit comments

Comments
 (0)