|
| 1 | +use clippy_config::msrvs::{self, Msrv}; |
| 2 | +use clippy_utils::diagnostics::span_lint_and_then; |
| 3 | +use clippy_utils::source::{get_source_text, with_leading_whitespace, SpanRange}; |
| 4 | +use clippy_utils::ty::get_field_by_name; |
| 5 | +use clippy_utils::visitors::{for_each_expr, for_each_expr_with_closures}; |
| 6 | +use clippy_utils::{expr_use_ctxt, is_diag_item_method, is_diag_trait_item, path_to_local_id, ExprUseNode}; |
| 7 | +use core::ops::ControlFlow; |
| 8 | +use rustc_errors::Applicability; |
| 9 | +use rustc_hir::{BindingMode, BorrowKind, ByRef, ClosureKind, Expr, ExprKind, Mutability, Node, PatKind}; |
| 10 | +use rustc_lint::LateContext; |
| 11 | +use rustc_middle::ty::adjustment::{Adjust, Adjustment, AutoBorrow, AutoBorrowMutability}; |
| 12 | +use rustc_span::{sym, BytePos, Span, Symbol, DUMMY_SP}; |
| 13 | + |
| 14 | +use super::MANUAL_INSPECT; |
| 15 | + |
| 16 | +#[expect(clippy::too_many_lines)] |
| 17 | +pub(crate) fn check(cx: &LateContext<'_>, expr: &Expr<'_>, arg: &Expr<'_>, name: &str, name_span: Span, msrv: &Msrv) { |
| 18 | + if let ExprKind::Closure(c) = arg.kind |
| 19 | + && matches!(c.kind, ClosureKind::Closure) |
| 20 | + && let typeck = cx.typeck_results() |
| 21 | + && let Some(fn_id) = typeck.type_dependent_def_id(expr.hir_id) |
| 22 | + && (is_diag_trait_item(cx, fn_id, sym::Iterator) |
| 23 | + || (msrv.meets(msrvs::OPTION_RESULT_INSPECT) |
| 24 | + && (is_diag_item_method(cx, fn_id, sym::Option) || is_diag_item_method(cx, fn_id, sym::Result)))) |
| 25 | + && let body = cx.tcx.hir().body(c.body) |
| 26 | + && let [param] = body.params |
| 27 | + && let PatKind::Binding(BindingMode(ByRef::No, Mutability::Not), arg_id, _, None) = param.pat.kind |
| 28 | + && let arg_ty = typeck.node_type(arg_id) |
| 29 | + && let ExprKind::Block(block, _) = body.value.kind |
| 30 | + && let Some(final_expr) = block.expr |
| 31 | + && !block.stmts.is_empty() |
| 32 | + && path_to_local_id(final_expr, arg_id) |
| 33 | + && typeck.expr_adjustments(final_expr).is_empty() |
| 34 | + { |
| 35 | + let mut requires_copy = false; |
| 36 | + let mut requires_deref = false; |
| 37 | + |
| 38 | + // The number of unprocessed return expressions. |
| 39 | + let mut ret_count = 0u32; |
| 40 | + |
| 41 | + // The uses for which processing is delayed until after the visitor. |
| 42 | + let mut delayed = vec![]; |
| 43 | + |
| 44 | + let ctxt = arg.span.ctxt(); |
| 45 | + let can_lint = for_each_expr(block.stmts, |e| { |
| 46 | + if let ExprKind::Closure(c) = e.kind { |
| 47 | + // Nested closures don't need to treat returns specially. |
| 48 | + let _: Option<!> = for_each_expr_with_closures(cx, cx.tcx.hir().body(c.body).value, |e| { |
| 49 | + if path_to_local_id(e, arg_id) { |
| 50 | + let (kind, same_ctxt) = check_use(cx, e); |
| 51 | + match (kind, same_ctxt && e.span.ctxt() == ctxt) { |
| 52 | + (_, false) | (UseKind::Deref | UseKind::Return(..), true) => { |
| 53 | + requires_copy = true; |
| 54 | + requires_deref = true; |
| 55 | + }, |
| 56 | + (UseKind::AutoBorrowed, true) => {}, |
| 57 | + (UseKind::WillAutoDeref, true) => { |
| 58 | + requires_copy = true; |
| 59 | + }, |
| 60 | + (kind, true) => delayed.push(kind), |
| 61 | + } |
| 62 | + } |
| 63 | + ControlFlow::Continue(()) |
| 64 | + }); |
| 65 | + } else if matches!(e.kind, ExprKind::Ret(_)) { |
| 66 | + ret_count += 1; |
| 67 | + } else if path_to_local_id(e, arg_id) { |
| 68 | + let (kind, same_ctxt) = check_use(cx, e); |
| 69 | + match (kind, same_ctxt && e.span.ctxt() == ctxt) { |
| 70 | + (UseKind::Return(..), false) => { |
| 71 | + return ControlFlow::Break(()); |
| 72 | + }, |
| 73 | + (_, false) | (UseKind::Deref, true) => { |
| 74 | + requires_copy = true; |
| 75 | + requires_deref = true; |
| 76 | + }, |
| 77 | + (UseKind::AutoBorrowed, true) => {}, |
| 78 | + (UseKind::WillAutoDeref, true) => { |
| 79 | + requires_copy = true; |
| 80 | + }, |
| 81 | + (kind @ UseKind::Return(_), true) => { |
| 82 | + ret_count -= 1; |
| 83 | + delayed.push(kind); |
| 84 | + }, |
| 85 | + (kind, true) => delayed.push(kind), |
| 86 | + } |
| 87 | + } |
| 88 | + ControlFlow::Continue(()) |
| 89 | + }) |
| 90 | + .is_none(); |
| 91 | + |
| 92 | + if ret_count != 0 { |
| 93 | + // A return expression that didn't return the original value was found. |
| 94 | + return; |
| 95 | + } |
| 96 | + |
| 97 | + let mut edits = Vec::with_capacity(delayed.len() + 3); |
| 98 | + let mut addr_of_edits = Vec::with_capacity(delayed.len()); |
| 99 | + for x in delayed { |
| 100 | + match x { |
| 101 | + UseKind::Return(s) => edits.push((with_leading_whitespace(cx, s).set_span_pos(s), String::new())), |
| 102 | + UseKind::Borrowed(s) => { |
| 103 | + if let Some(src) = get_source_text(cx, s) |
| 104 | + && let Some(src) = src.as_str() |
| 105 | + && let trim_src = src.trim_start_matches(|x| matches!(x, ' ' | '\t' | '\n' | '\r' | '(')) |
| 106 | + && trim_src.starts_with('&') |
| 107 | + { |
| 108 | + let range = s.into_range(); |
| 109 | + #[expect(clippy::cast_possible_truncation)] |
| 110 | + let start = BytePos(range.start.0 + (src.len() - trim_src.len()) as u32); |
| 111 | + addr_of_edits.push(((start..BytePos(start.0 + 1)).set_span_pos(s), String::new())); |
| 112 | + } else { |
| 113 | + requires_copy = true; |
| 114 | + requires_deref = true; |
| 115 | + } |
| 116 | + }, |
| 117 | + UseKind::FieldAccess(name, e) => { |
| 118 | + let Some(mut ty) = get_field_by_name(cx.tcx, arg_ty.peel_refs(), name) else { |
| 119 | + requires_copy = true; |
| 120 | + continue; |
| 121 | + }; |
| 122 | + let mut prev_expr = e; |
| 123 | + |
| 124 | + for (_, parent) in cx.tcx.hir().parent_iter(e.hir_id) { |
| 125 | + if let Node::Expr(e) = parent { |
| 126 | + match e.kind { |
| 127 | + ExprKind::Field(_, name) |
| 128 | + if let Some(fty) = get_field_by_name(cx.tcx, ty.peel_refs(), name.name) => |
| 129 | + { |
| 130 | + ty = fty; |
| 131 | + prev_expr = e; |
| 132 | + continue; |
| 133 | + }, |
| 134 | + ExprKind::AddrOf(BorrowKind::Ref, ..) => break, |
| 135 | + _ if matches!( |
| 136 | + typeck.expr_adjustments(prev_expr).first(), |
| 137 | + Some(Adjustment { |
| 138 | + kind: Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Not)) |
| 139 | + | Adjust::Deref(_), |
| 140 | + .. |
| 141 | + }) |
| 142 | + ) => |
| 143 | + { |
| 144 | + break; |
| 145 | + }, |
| 146 | + _ => {}, |
| 147 | + } |
| 148 | + } |
| 149 | + requires_copy |= !ty.is_copy_modulo_regions(cx.tcx, cx.param_env); |
| 150 | + break; |
| 151 | + } |
| 152 | + }, |
| 153 | + // Already processed uses. |
| 154 | + UseKind::AutoBorrowed | UseKind::WillAutoDeref | UseKind::Deref => {}, |
| 155 | + } |
| 156 | + } |
| 157 | + |
| 158 | + if can_lint |
| 159 | + && (!requires_copy || arg_ty.is_copy_modulo_regions(cx.tcx, cx.param_env)) |
| 160 | + // This case could be handled, but a fair bit of care would need to be taken. |
| 161 | + && (!requires_deref || arg_ty.is_freeze(cx.tcx, cx.param_env)) |
| 162 | + { |
| 163 | + if requires_deref { |
| 164 | + edits.push((param.span.shrink_to_lo(), "&".into())); |
| 165 | + } else { |
| 166 | + edits.extend(addr_of_edits); |
| 167 | + } |
| 168 | + edits.push(( |
| 169 | + name_span, |
| 170 | + String::from(match name { |
| 171 | + "map" => "inspect", |
| 172 | + "map_err" => "inspect_err", |
| 173 | + _ => return, |
| 174 | + }), |
| 175 | + )); |
| 176 | + edits.push(( |
| 177 | + with_leading_whitespace(cx, final_expr.span).set_span_pos(final_expr.span), |
| 178 | + String::new(), |
| 179 | + )); |
| 180 | + let app = if edits.iter().any(|(s, _)| s.from_expansion()) { |
| 181 | + Applicability::MaybeIncorrect |
| 182 | + } else { |
| 183 | + Applicability::MachineApplicable |
| 184 | + }; |
| 185 | + span_lint_and_then(cx, MANUAL_INSPECT, name_span, "", |diag| { |
| 186 | + diag.multipart_suggestion("try", edits, app); |
| 187 | + }); |
| 188 | + } |
| 189 | + } |
| 190 | +} |
| 191 | + |
| 192 | +enum UseKind<'tcx> { |
| 193 | + AutoBorrowed, |
| 194 | + WillAutoDeref, |
| 195 | + Deref, |
| 196 | + Return(Span), |
| 197 | + Borrowed(Span), |
| 198 | + FieldAccess(Symbol, &'tcx Expr<'tcx>), |
| 199 | +} |
| 200 | + |
| 201 | +/// Checks how the value is used, and whether it was used in the same `SyntaxContext`. |
| 202 | +fn check_use<'tcx>(cx: &LateContext<'tcx>, e: &'tcx Expr<'_>) -> (UseKind<'tcx>, bool) { |
| 203 | + let use_cx = expr_use_ctxt(cx, e); |
| 204 | + if use_cx |
| 205 | + .adjustments |
| 206 | + .first() |
| 207 | + .is_some_and(|a| matches!(a.kind, Adjust::Deref(_))) |
| 208 | + { |
| 209 | + return (UseKind::AutoBorrowed, use_cx.same_ctxt); |
| 210 | + } |
| 211 | + let res = match use_cx.use_node(cx) { |
| 212 | + ExprUseNode::Return(_) => { |
| 213 | + if let ExprKind::Ret(Some(e)) = use_cx.node.expect_expr().kind { |
| 214 | + UseKind::Return(e.span) |
| 215 | + } else { |
| 216 | + return (UseKind::Return(DUMMY_SP), false); |
| 217 | + } |
| 218 | + }, |
| 219 | + ExprUseNode::FieldAccess(name) => UseKind::FieldAccess(name.name, use_cx.node.expect_expr()), |
| 220 | + ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0) |
| 221 | + if use_cx |
| 222 | + .adjustments |
| 223 | + .first() |
| 224 | + .is_some_and(|a| matches!(a.kind, Adjust::Borrow(AutoBorrow::Ref(_, AutoBorrowMutability::Not)))) => |
| 225 | + { |
| 226 | + UseKind::AutoBorrowed |
| 227 | + }, |
| 228 | + ExprUseNode::Callee | ExprUseNode::MethodArg(_, _, 0) => UseKind::WillAutoDeref, |
| 229 | + ExprUseNode::AddrOf(BorrowKind::Ref, _) => UseKind::Borrowed(use_cx.node.expect_expr().span), |
| 230 | + _ => UseKind::Deref, |
| 231 | + }; |
| 232 | + (res, use_cx.same_ctxt) |
| 233 | +} |
0 commit comments