Skip to content

Commit 00bb73f

Browse files
committed
fix [manual_map] not catching type adjustment
1 parent befb659 commit 00bb73f

File tree

4 files changed

+250
-10
lines changed

4 files changed

+250
-10
lines changed

clippy_lints/src/matches/manual_utils.rs

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,17 +3,19 @@ use crate::matches::MATCH_AS_REF;
33
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};
6+
use clippy_utils::visitors::for_each_expr;
67
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,
8-
peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
8+
can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res, path_to_local,
9+
path_to_local_id, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while, CaptureKind,
910
};
1011
use rustc_ast::util::parser::PREC_POSTFIX;
1112
use rustc_errors::Applicability;
1213
use rustc_hir::def::Res;
1314
use rustc_hir::LangItem::{OptionNone, OptionSome};
14-
use rustc_hir::{BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath};
15+
use rustc_hir::{BindingMode, Block, Expr, ExprKind, HirId, Mutability, Node, Pat, PatKind, Path, QPath};
1516
use rustc_lint::LateContext;
1617
use rustc_span::{sym, SyntaxContext};
18+
use std::ops::ControlFlow;
1719

1820
#[expect(clippy::too_many_arguments)]
1921
#[expect(clippy::too_many_lines)]
@@ -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 some_expr.expr_contains_ty_adjustments(cx) && expr_has_explicit_ty(cx, expr) {
7779
return None;
7880
}
7981

@@ -238,6 +240,17 @@ impl<'tcx> SomeExpr<'tcx> {
238240
let sugg = Sugg::hir_with_context(cx, self.expr, ctxt, "..", app);
239241
if self.needs_negated { !sugg } else { sugg }
240242
}
243+
244+
fn expr_contains_ty_adjustments(&self, cx: &LateContext<'tcx>) -> bool {
245+
for_each_expr(self.expr, |ex| {
246+
if cx.typeck_results().expr_adjustments(ex).is_empty() {
247+
ControlFlow::Continue(())
248+
} else {
249+
ControlFlow::Break(true)
250+
}
251+
})
252+
.unwrap_or_default()
253+
}
241254
}
242255

243256
// Try to parse into a recognized `Option` pattern.
@@ -274,3 +287,60 @@ pub(super) fn try_parse_pattern<'tcx>(
274287
fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
275288
is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone)
276289
}
290+
291+
/// Return `true` if the type of `match` or `if-let` is labeled explicitly.
292+
///
293+
/// Such as the init binding of:
294+
///
295+
/// ```ignore
296+
/// let x: &u8 = match Some(0) { .. };
297+
/// // ^^^^^^^^^^^^^^^^^^^^
298+
/// ```
299+
///
300+
/// Or the return of a function with return ty:
301+
///
302+
/// ```ignore
303+
/// fn foo() -> u8 {
304+
/// match Some(0) { .. }
305+
/// // ^^^^^^^^^^^^^^^^^^^^
306+
/// }
307+
/// ```
308+
fn expr_has_explicit_ty(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
309+
fn check_inner_(cx: &LateContext<'_>, hir_id: HirId) -> bool {
310+
match cx.tcx.parent_hir_node(hir_id) {
311+
Node::LetStmt(let_stmt) => let_stmt.ty.is_some(),
312+
Node::Expr(Expr {
313+
kind: ExprKind::Assign(assignee, ..),
314+
..
315+
}) => {
316+
if let Some(local_id) = path_to_local(assignee)
317+
&& let Node::LetStmt(let_stmt) = cx.tcx.parent_hir_node(local_id)
318+
{
319+
let_stmt.ty.is_some()
320+
} else {
321+
false
322+
}
323+
},
324+
// Assuming `true` because this branch should only be reached from
325+
// tracing the parent of a `Node::Block` that has return value,
326+
// meaning this is either `Fn` or `Const`, that always (?) has a labeled type.
327+
Node::Item(_) | Node::TraitItem(_) | Node::ImplItem(_) |
328+
// If this expr is being returned, then it's type must've been `labeled` on its owner function.
329+
Node::Expr(Expr {
330+
kind: ExprKind::Ret(_), ..
331+
}) => true,
332+
// The implicit return of a block, check if its a fn body or some init block.
333+
Node::Block(Block {
334+
expr: Some(_), hir_id, ..
335+
}) => check_inner_(cx, cx.tcx.parent_hir_id(*hir_id)),
336+
_ => false,
337+
}
338+
}
339+
340+
// Sanity check
341+
assert!(
342+
matches!(expr.kind, ExprKind::Match(..) | ExprKind::If(..)),
343+
"must used on `match` or `if-let` expressions"
344+
);
345+
check_inner_(cx, expr.hir_id)
346+
}

tests/ui/manual_map_option_2.fixed

Lines changed: 74 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,71 @@ fn main() {
5459
let _ = Some(0).map(|x| unsafe { f(x) });
5560
let _ = Some(0).map(|x| unsafe { f(x) });
5661
}
62+
63+
mod issue_12659 {
64+
trait DummyTrait {}
65+
66+
fn foo<T: DummyTrait, F: Fn() -> Result<T, ()>>(f: F) {
67+
// Don't lint
68+
let _: Option<Result<Box<dyn DummyTrait>, ()>> = match Some(0) {
69+
Some(_) => Some(match f() {
70+
Ok(res) => Ok(Box::new(res)),
71+
_ => Err(()),
72+
}),
73+
None => None,
74+
};
75+
76+
let _: Option<Box<&[u8]>> = match Some(()) {
77+
Some(_) => Some(Box::new(b"1234")),
78+
None => None,
79+
};
80+
81+
let x = String::new();
82+
let _: Option<Box<&str>> = match Some(()) {
83+
Some(_) => Some(Box::new(&x)),
84+
None => None,
85+
};
86+
87+
let _: Option<&str> = match Some(()) {
88+
Some(_) => Some(&x),
89+
None => None,
90+
};
91+
92+
//~v ERROR: manual implementation of `Option::map`
93+
let _ = Some(0).map(|_| match f() {
94+
Ok(res) => Ok(Box::new(res)),
95+
_ => Err(()),
96+
});
97+
}
98+
99+
fn with_fn_ret(s: &Option<String>) -> Option<(String, &str)> {
100+
// Don't lint, `map` doesn't work as the return type is adjusted.
101+
match s {
102+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
103+
None => None,
104+
}
105+
}
106+
107+
fn with_fn_ret_2(s: &Option<String>) -> Option<(String, &str)> {
108+
if true {
109+
// Don't lint, `map` doesn't work as the return type is adjusted.
110+
return match s {
111+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
112+
None => None,
113+
};
114+
}
115+
None
116+
}
117+
118+
#[allow(clippy::needless_late_init)]
119+
fn with_fn_ret_3<'a>(s: &'a Option<String>) -> Option<(String, &'a str)> {
120+
let x: Option<(String, &'a str)>;
121+
x = {
122+
match s {
123+
Some(x) => Some({ if let Some(ref s) = s { (x.clone(), s) } else { panic!() } }),
124+
None => None,
125+
}
126+
};
127+
x
128+
}
129+
}

tests/ui/manual_map_option_2.rs

Lines changed: 77 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,74 @@ fn main() {
6974
None => None,
7075
};
7176
}
77+
78+
mod issue_12659 {
79+
trait DummyTrait {}
80+
81+
fn foo<T: DummyTrait, F: Fn() -> Result<T, ()>>(f: F) {
82+
// Don't lint
83+
let _: Option<Result<Box<dyn DummyTrait>, ()>> = match Some(0) {
84+
Some(_) => Some(match f() {
85+
Ok(res) => Ok(Box::new(res)),
86+
_ => Err(()),
87+
}),
88+
None => None,
89+
};
90+
91+
let _: Option<Box<&[u8]>> = match Some(()) {
92+
Some(_) => Some(Box::new(b"1234")),
93+
None => None,
94+
};
95+
96+
let x = String::new();
97+
let _: Option<Box<&str>> = match Some(()) {
98+
Some(_) => Some(Box::new(&x)),
99+
None => None,
100+
};
101+
102+
let _: Option<&str> = match Some(()) {
103+
Some(_) => Some(&x),
104+
None => None,
105+
};
106+
107+
//~v ERROR: manual implementation of `Option::map`
108+
let _ = match Some(0) {
109+
Some(_) => Some(match f() {
110+
Ok(res) => Ok(Box::new(res)),
111+
_ => Err(()),
112+
}),
113+
None => None,
114+
};
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.stderr

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ LL | | };
3232
| |_____^ help: try: `s.as_ref().map(|x| { if let Some(ref s) = s { (x.clone(), s) } else { panic!() } })`
3333

3434
error: manual implementation of `Option::map`
35-
--> tests/ui/manual_map_option_2.rs:58:17
35+
--> tests/ui/manual_map_option_2.rs:63:17
3636
|
3737
LL | let _ = match Some(0) {
3838
| _________________^
@@ -42,7 +42,7 @@ LL | | };
4242
| |_________^ help: try: `Some(0).map(|x| f(x))`
4343

4444
error: manual implementation of `Option::map`
45-
--> tests/ui/manual_map_option_2.rs:63:13
45+
--> tests/ui/manual_map_option_2.rs:68:13
4646
|
4747
LL | let _ = match Some(0) {
4848
| _____________^
@@ -52,7 +52,7 @@ LL | | };
5252
| |_____^ help: try: `Some(0).map(|x| unsafe { f(x) })`
5353

5454
error: manual implementation of `Option::map`
55-
--> tests/ui/manual_map_option_2.rs:67:13
55+
--> tests/ui/manual_map_option_2.rs:72:13
5656
|
5757
LL | let _ = match Some(0) {
5858
| _____________^
@@ -61,5 +61,26 @@ LL | | None => None,
6161
LL | | };
6262
| |_____^ help: try: `Some(0).map(|x| unsafe { f(x) })`
6363

64-
error: aborting due to 5 previous errors
64+
error: manual implementation of `Option::map`
65+
--> tests/ui/manual_map_option_2.rs:108:17
66+
|
67+
LL | let _ = match Some(0) {
68+
| _________________^
69+
LL | | Some(_) => Some(match f() {
70+
LL | | Ok(res) => Ok(Box::new(res)),
71+
LL | | _ => Err(()),
72+
LL | | }),
73+
LL | | None => None,
74+
LL | | };
75+
| |_________^
76+
|
77+
help: try
78+
|
79+
LL ~ let _ = Some(0).map(|_| match f() {
80+
LL + Ok(res) => Ok(Box::new(res)),
81+
LL + _ => Err(()),
82+
LL ~ });
83+
|
84+
85+
error: aborting due to 6 previous errors
6586

0 commit comments

Comments
 (0)