Skip to content

Commit 2a4be53

Browse files
committed
move expr_requires_coercion to clippy_utils & some other adjustments
1 parent a2f9861 commit 2a4be53

File tree

5 files changed

+111
-86
lines changed

5 files changed

+111
-86
lines changed

clippy_lints/src/matches/manual_utils.rs

Lines changed: 3 additions & 71 deletions
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 expr_has_type_coercion(cx, expr) {
76+
if expr_requires_coercion(cx, expr) {
7777
return None;
7878
}
7979

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

clippy_utils/src/lib.rs

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

tests/ui/manual_map_option.fixed

Lines changed: 10 additions & 1 deletion
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

Lines changed: 6 additions & 0 deletions
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

Lines changed: 4 additions & 13 deletions
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

0 commit comments

Comments
 (0)