Skip to content

Commit 18d1a59

Browse files
committed
move expr_requires_coercion to clippy_utils & some other adjustments
1 parent 635c375 commit 18d1a59

File tree

5 files changed

+112
-89
lines changed

5 files changed

+112
-89
lines changed

clippy_lints/src/matches/manual_utils.rs

Lines changed: 4 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,15 @@ 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, fn_def_id, 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,
7+
can_move_expr_to_closure, expr_requires_coercion, 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, 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::{self as hir, BindingMode, Expr, ExprKind, HirId, Mutability, Pat, PatKind, Path, QPath};
14+
use rustc_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};
1816
use rustc_span::{sym, SyntaxContext};
1917

2018
#[expect(clippy::too_many_arguments)]
@@ -75,7 +73,7 @@ where
7573
}
7674

7775
// `map` won't perform any adjustments.
78-
if expr_has_type_coercion(cx, expr) {
76+
if expr_requires_coercion(cx, expr) {
7977
return None;
8078
}
8179

@@ -274,71 +272,3 @@ pub(super) fn try_parse_pattern<'tcx>(
274272
fn is_none_expr(cx: &LateContext<'_>, expr: &Expr<'_>) -> bool {
275273
is_res_lang_ctor(cx, path_res(cx, peel_blocks(expr)), OptionNone)
276274
}
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-
}

clippy_utils/src/lib.rs

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

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)