Skip to content

Commit d7fd1c8

Browse files
authored
make [manual_map] ignore types that contain dyn (#12712)
fixes: #12659 [`manual_map`] and [`manual_filter`] shares the same check logic, but this issue doesn't seems like it could affect `manual_filter` (?) --- changelog: make [`manual_map`] ignore types that contain `dyn`
2 parents 8c01600 + 2a4be53 commit d7fd1c8

8 files changed

+344
-34
lines changed

clippy_lints/src/matches/manual_utils.rs

+11-13
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ 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-
CaptureKind, can_move_expr_to_closure, is_else_clause, is_lint_allowed, is_res_lang_ctor, path_res,
8-
path_to_local_id, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while,
7+
CaptureKind, can_move_expr_to_closure, expr_requires_coercion, is_else_clause, is_lint_allowed, is_res_lang_ctor,
8+
path_res, path_to_local_id, peel_blocks, peel_hir_expr_refs, peel_hir_expr_while,
99
};
1010
use rustc_ast::util::parser::ExprPrecedence;
1111
use rustc_errors::Applicability;
@@ -73,7 +73,7 @@ where
7373
}
7474

7575
// `map` won't perform any adjustments.
76-
if !cx.typeck_results().expr_adjustments(some_expr.expr).is_empty() {
76+
if expr_requires_coercion(cx, expr) {
7777
return None;
7878
}
7979

@@ -124,6 +124,12 @@ where
124124
};
125125

126126
let closure_expr_snip = some_expr.to_snippet_with_context(cx, expr_ctxt, &mut app);
127+
let closure_body = if some_expr.needs_unsafe_block {
128+
format!("unsafe {}", closure_expr_snip.blockify())
129+
} else {
130+
closure_expr_snip.to_string()
131+
};
132+
127133
let body_str = if let PatKind::Binding(annotation, id, some_binding, None) = some_pat.kind {
128134
if !some_expr.needs_unsafe_block
129135
&& let Some(func) = can_pass_as_func(cx, id, some_expr.expr)
@@ -145,20 +151,12 @@ where
145151
""
146152
};
147153

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-
}
154+
format!("|{annotation}{some_binding}| {closure_body}")
153155
}
154156
} else if !is_wild_none && explicit_ref.is_none() {
155157
// TODO: handle explicit reference annotations.
156158
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-
}
159+
format!("|{pat_snip}| {closure_body}")
162160
} else {
163161
// Refutable bindings and mixed reference annotations can't be handled by `map`.
164162
return None;

clippy_utils/src/lib.rs

+88-1
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ use rustc_middle::ty::fast_reject::SimplifiedType;
118118
use rustc_middle::ty::layout::IntegerExt;
119119
use rustc_middle::ty::{
120120
self as rustc_ty, Binder, BorrowKind, ClosureKind, EarlyBinder, FloatTy, GenericArgKind, GenericArgsRef, IntTy, Ty,
121-
TyCtxt, TypeVisitableExt, UintTy, UpvarCapture,
121+
TyCtxt, TypeFlags, TypeVisitableExt, UintTy, UpvarCapture,
122122
};
123123
use rustc_span::hygiene::{ExpnKind, MacroKind};
124124
use rustc_span::source_map::SourceMap;
@@ -3508,3 +3508,90 @@ pub fn leaks_droppable_temporary_with_limited_lifetime<'tcx>(cx: &LateContext<'t
35083508
})
35093509
.is_break()
35103510
}
3511+
3512+
/// Returns true if the specified `expr` requires coercion,
3513+
/// meaning that it either has a coercion or propagates a coercion from one of its sub expressions.
3514+
///
3515+
/// Similar to [`is_adjusted`], this not only checks if an expression's type was adjusted,
3516+
/// but also going through extra steps to see if it fits the description of [coercion sites].
3517+
///
3518+
/// You should used this when you want to avoid suggesting replacing an expression that is currently
3519+
/// a coercion site or coercion propagating expression with one that is not.
3520+
///
3521+
/// [coercion sites]: https://doc.rust-lang.org/stable/reference/type-coercions.html#coercion-sites
3522+
pub fn expr_requires_coercion<'tcx>(cx: &LateContext<'tcx>, expr: &Expr<'tcx>) -> bool {
3523+
let expr_ty_is_adjusted = cx
3524+
.typeck_results()
3525+
.expr_adjustments(expr)
3526+
.iter()
3527+
// ignore `NeverToAny` adjustments, such as `panic!` call.
3528+
.any(|adj| !matches!(adj.kind, Adjust::NeverToAny));
3529+
if expr_ty_is_adjusted {
3530+
return true;
3531+
}
3532+
3533+
// Identify coercion sites and recursively check if those sites
3534+
// actually have type adjustments.
3535+
match expr.kind {
3536+
ExprKind::Call(_, args) | ExprKind::MethodCall(_, _, args, _) if let Some(def_id) = fn_def_id(cx, expr) => {
3537+
let fn_sig = cx.tcx.fn_sig(def_id).instantiate_identity();
3538+
3539+
if !fn_sig.output().skip_binder().has_type_flags(TypeFlags::HAS_TY_PARAM) {
3540+
return false;
3541+
}
3542+
3543+
let self_arg_count = usize::from(matches!(expr.kind, ExprKind::MethodCall(..)));
3544+
let mut args_with_ty_param = {
3545+
fn_sig
3546+
.inputs()
3547+
.skip_binder()
3548+
.iter()
3549+
.skip(self_arg_count)
3550+
.zip(args)
3551+
.filter_map(|(arg_ty, arg)| {
3552+
if arg_ty.has_type_flags(TypeFlags::HAS_TY_PARAM) {
3553+
Some(arg)
3554+
} else {
3555+
None
3556+
}
3557+
})
3558+
};
3559+
args_with_ty_param.any(|arg| expr_requires_coercion(cx, arg))
3560+
},
3561+
// Struct/union initialization.
3562+
ExprKind::Struct(qpath, _, _) => {
3563+
let res = cx.typeck_results().qpath_res(qpath, expr.hir_id);
3564+
if let Some((_, v_def)) = adt_and_variant_of_res(cx, res) {
3565+
let generic_args = cx.typeck_results().node_args(expr.hir_id);
3566+
v_def
3567+
.fields
3568+
.iter()
3569+
.any(|field| field.ty(cx.tcx, generic_args).has_type_flags(TypeFlags::HAS_TY_PARAM))
3570+
} else {
3571+
false
3572+
}
3573+
},
3574+
// Function results, including the final line of a block or a `return` expression.
3575+
ExprKind::Block(
3576+
&Block {
3577+
expr: Some(ret_expr), ..
3578+
},
3579+
_,
3580+
)
3581+
| ExprKind::Ret(Some(ret_expr)) => expr_requires_coercion(cx, ret_expr),
3582+
3583+
// ===== Coercion-propagation expressions =====
3584+
ExprKind::Array(elems) | ExprKind::Tup(elems) => elems.iter().any(|elem| expr_requires_coercion(cx, elem)),
3585+
// Array but with repeating syntax.
3586+
ExprKind::Repeat(rep_elem, _) => expr_requires_coercion(cx, rep_elem),
3587+
// Others that may contain coercion sites.
3588+
ExprKind::If(_, then, maybe_else) => {
3589+
expr_requires_coercion(cx, then) || maybe_else.is_some_and(|e| expr_requires_coercion(cx, e))
3590+
},
3591+
ExprKind::Match(_, arms, _) => arms
3592+
.iter()
3593+
.map(|arm| arm.body)
3594+
.any(|body| expr_requires_coercion(cx, body)),
3595+
_ => false,
3596+
}
3597+
}

tests/ui/manual_map_option.fixed

+10-1
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,16 @@ fn main() {
113113
}
114114

115115
// #6811
116-
Some(0).map(|x| vec![x]);
116+
match Some(0) {
117+
Some(x) => Some(vec![x]),
118+
None => None,
119+
};
120+
121+
// Don't lint, coercion
122+
let x: Option<Vec<&[u8]>> = match Some(()) {
123+
Some(_) => Some(vec![b"1234"]),
124+
None => None,
125+
};
117126

118127
option_env!("").map(String::from);
119128

tests/ui/manual_map_option.rs

+6
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ fn main() {
170170
None => None,
171171
};
172172

173+
// Don't lint, coercion
174+
let x: Option<Vec<&[u8]>> = match Some(()) {
175+
Some(_) => Some(vec![b"1234"]),
176+
None => None,
177+
};
178+
173179
match option_env!("") {
174180
Some(x) => Some(String::from(x)),
175181
None => None,

tests/ui/manual_map_option.stderr

+4-13
Original file line numberDiff line numberDiff line change
@@ -156,16 +156,7 @@ LL | | };
156156
| |_____^ help: try: `Some((String::new(), "test")).as_ref().map(|(x, y)| (y, x))`
157157

158158
error: manual implementation of `Option::map`
159-
--> tests/ui/manual_map_option.rs:168:5
160-
|
161-
LL | / match Some(0) {
162-
LL | | Some(x) => Some(vec![x]),
163-
LL | | None => None,
164-
LL | | };
165-
| |_____^ help: try: `Some(0).map(|x| vec![x])`
166-
167-
error: manual implementation of `Option::map`
168-
--> tests/ui/manual_map_option.rs:173:5
159+
--> tests/ui/manual_map_option.rs:179:5
169160
|
170161
LL | / match option_env!("") {
171162
LL | | Some(x) => Some(String::from(x)),
@@ -174,7 +165,7 @@ LL | | };
174165
| |_____^ help: try: `option_env!("").map(String::from)`
175166

176167
error: manual implementation of `Option::map`
177-
--> tests/ui/manual_map_option.rs:193:12
168+
--> tests/ui/manual_map_option.rs:199:12
178169
|
179170
LL | } else if let Some(x) = Some(0) {
180171
| ____________^
@@ -185,7 +176,7 @@ LL | | };
185176
| |_____^ help: try: `{ Some(0).map(|x| x + 1) }`
186177

187178
error: manual implementation of `Option::map`
188-
--> tests/ui/manual_map_option.rs:201:12
179+
--> tests/ui/manual_map_option.rs:207:12
189180
|
190181
LL | } else if let Some(x) = Some(0) {
191182
| ____________^
@@ -195,5 +186,5 @@ LL | | None
195186
LL | | };
196187
| |_____^ help: try: `{ Some(0).map(|x| x + 1) }`
197188

198-
error: aborting due to 21 previous errors
189+
error: aborting due to 20 previous errors
199190

tests/ui/manual_map_option_2.fixed

+92-1
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+
}

0 commit comments

Comments
 (0)