| use clippy_utils::diagnostics::span_lint_and_sugg; |
| use clippy_utils::eager_or_lazy::switch_to_lazy_eval; |
| use clippy_utils::source::snippet_with_context; |
| use clippy_utils::ty::{expr_type_is_certain, implements_trait, is_type_diagnostic_item}; |
| use clippy_utils::{contains_return, is_default_equivalent, is_default_equivalent_call, last_path_segment}; |
| use if_chain::if_chain; |
| use rustc_errors::Applicability; |
| use rustc_lint::LateContext; |
| use rustc_middle::ty; |
| use rustc_span::Span; |
| use rustc_span::symbol::{self, sym, Symbol}; |
| use {rustc_ast as ast, rustc_hir as hir}; |
| |
| use super::{OR_FUN_CALL, UNWRAP_OR_DEFAULT}; |
| |
| /// Checks for the `OR_FUN_CALL` lint. |
| #[allow(clippy::too_many_lines)] |
| pub(super) fn check<'tcx>( |
| cx: &LateContext<'tcx>, |
| expr: &hir::Expr<'_>, |
| method_span: Span, |
| name: &str, |
| receiver: &'tcx hir::Expr<'_>, |
| args: &'tcx [hir::Expr<'_>], |
| ) { |
| /// Checks for `unwrap_or(T::new())`, `unwrap_or(T::default())`, |
| /// `or_insert(T::new())` or `or_insert(T::default())`. |
| /// Similarly checks for `unwrap_or_else(T::new)`, `unwrap_or_else(T::default)`, |
| /// `or_insert_with(T::new)` or `or_insert_with(T::default)`. |
| #[allow(clippy::too_many_arguments)] |
| fn check_unwrap_or_default( |
| cx: &LateContext<'_>, |
| name: &str, |
| receiver: &hir::Expr<'_>, |
| fun: &hir::Expr<'_>, |
| call_expr: Option<&hir::Expr<'_>>, |
| span: Span, |
| method_span: Span, |
| ) -> bool { |
| if !expr_type_is_certain(cx, receiver) { |
| return false; |
| } |
| |
| let is_new = |fun: &hir::Expr<'_>| { |
| if let hir::ExprKind::Path(ref qpath) = fun.kind { |
| let path = last_path_segment(qpath).ident.name; |
| matches!(path, sym::new) |
| } else { |
| false |
| } |
| }; |
| |
| let output_type_implements_default = |fun| { |
| let fun_ty = cx.typeck_results().expr_ty(fun); |
| if let ty::FnDef(def_id, args) = fun_ty.kind() { |
| let output_ty = cx.tcx.fn_sig(def_id).instantiate(cx.tcx, args).skip_binder().output(); |
| cx.tcx |
| .get_diagnostic_item(sym::Default) |
| .map_or(false, |default_trait_id| { |
| implements_trait(cx, output_ty, default_trait_id, &[]) |
| }) |
| } else { |
| false |
| } |
| }; |
| |
| let sugg = match (name, call_expr.is_some()) { |
| ("unwrap_or", true) | ("unwrap_or_else", false) => sym!(unwrap_or_default), |
| ("or_insert", true) | ("or_insert_with", false) => sym!(or_default), |
| _ => return false, |
| }; |
| |
| let receiver_ty = cx.typeck_results().expr_ty_adjusted(receiver).peel_refs(); |
| let has_suggested_method = receiver_ty.ty_adt_def().is_some_and(|adt_def| { |
| cx.tcx |
| .inherent_impls(adt_def.did()) |
| .iter() |
| .flat_map(|impl_id| cx.tcx.associated_items(impl_id).filter_by_name_unhygienic(sugg)) |
| .any(|assoc| { |
| assoc.fn_has_self_parameter |
| && cx.tcx.fn_sig(assoc.def_id).skip_binder().inputs().skip_binder().len() == 1 |
| }) |
| }); |
| if !has_suggested_method { |
| return false; |
| } |
| |
| // needs to target Default::default in particular or be *::new and have a Default impl |
| // available |
| if (is_new(fun) && output_type_implements_default(fun)) |
| || match call_expr { |
| Some(call_expr) => is_default_equivalent(cx, call_expr), |
| None => is_default_equivalent_call(cx, fun) || closure_body_returns_empty_to_string(cx, fun), |
| } |
| { |
| span_lint_and_sugg( |
| cx, |
| UNWRAP_OR_DEFAULT, |
| method_span.with_hi(span.hi()), |
| &format!("use of `{name}` to construct default value"), |
| "try", |
| format!("{sugg}()"), |
| Applicability::MachineApplicable, |
| ); |
| |
| true |
| } else { |
| false |
| } |
| } |
| |
| /// Checks for `*or(foo())`. |
| #[allow(clippy::too_many_arguments)] |
| fn check_general_case<'tcx>( |
| cx: &LateContext<'tcx>, |
| name: &str, |
| method_span: Span, |
| self_expr: &hir::Expr<'_>, |
| arg: &'tcx hir::Expr<'_>, |
| // `Some` if fn has second argument |
| second_arg: Option<&hir::Expr<'_>>, |
| span: Span, |
| // None if lambda is required |
| fun_span: Option<Span>, |
| ) { |
| // (path, fn_has_argument, methods, suffix) |
| const KNOW_TYPES: [(Symbol, bool, &[&str], &str); 4] = [ |
| (sym::BTreeEntry, false, &["or_insert"], "with"), |
| (sym::HashMapEntry, false, &["or_insert"], "with"), |
| (sym::Option, false, &["map_or", "ok_or", "or", "unwrap_or"], "else"), |
| (sym::Result, true, &["or", "unwrap_or"], "else"), |
| ]; |
| |
| if_chain! { |
| if KNOW_TYPES.iter().any(|k| k.2.contains(&name)); |
| |
| if switch_to_lazy_eval(cx, arg); |
| if !contains_return(arg); |
| |
| let self_ty = cx.typeck_results().expr_ty(self_expr); |
| |
| if let Some(&(_, fn_has_arguments, poss, suffix)) = |
| KNOW_TYPES.iter().find(|&&i| is_type_diagnostic_item(cx, self_ty, i.0)); |
| |
| if poss.contains(&name); |
| |
| then { |
| let ctxt = span.ctxt(); |
| let mut app = Applicability::HasPlaceholders; |
| let sugg = { |
| let (snippet_span, use_lambda) = match (fn_has_arguments, fun_span) { |
| (false, Some(fun_span)) => (fun_span, false), |
| _ => (arg.span, true), |
| }; |
| |
| let snip = snippet_with_context(cx, snippet_span, ctxt, "..", &mut app).0; |
| let snip = if use_lambda { |
| let l_arg = if fn_has_arguments { "_" } else { "" }; |
| format!("|{l_arg}| {snip}") |
| } else { |
| snip.into_owned() |
| }; |
| |
| if let Some(f) = second_arg { |
| let f = snippet_with_context(cx, f.span, ctxt, "..", &mut app).0; |
| format!("{snip}, {f}") |
| } else { |
| snip |
| } |
| }; |
| let span_replace_word = method_span.with_hi(span.hi()); |
| span_lint_and_sugg( |
| cx, |
| OR_FUN_CALL, |
| span_replace_word, |
| &format!("use of `{name}` followed by a function call"), |
| "try", |
| format!("{name}_{suffix}({sugg})"), |
| app, |
| ); |
| } |
| } |
| } |
| |
| let extract_inner_arg = |arg: &'tcx hir::Expr<'_>| { |
| if let hir::ExprKind::Block( |
| hir::Block { |
| stmts: [], |
| expr: Some(expr), |
| .. |
| }, |
| _, |
| ) = arg.kind |
| { |
| expr |
| } else { |
| arg |
| } |
| }; |
| |
| if let [arg] = args { |
| let inner_arg = extract_inner_arg(arg); |
| match inner_arg.kind { |
| hir::ExprKind::Call(fun, or_args) => { |
| let or_has_args = !or_args.is_empty(); |
| if or_has_args |
| || !check_unwrap_or_default(cx, name, receiver, fun, Some(inner_arg), expr.span, method_span) |
| { |
| let fun_span = if or_has_args { None } else { Some(fun.span) }; |
| check_general_case(cx, name, method_span, receiver, arg, None, expr.span, fun_span); |
| } |
| }, |
| hir::ExprKind::Path(..) | hir::ExprKind::Closure(..) => { |
| check_unwrap_or_default(cx, name, receiver, inner_arg, None, expr.span, method_span); |
| }, |
| hir::ExprKind::Index(..) | hir::ExprKind::MethodCall(..) => { |
| check_general_case(cx, name, method_span, receiver, arg, None, expr.span, None); |
| }, |
| _ => (), |
| } |
| } |
| |
| // `map_or` takes two arguments |
| if let [arg, lambda] = args { |
| let inner_arg = extract_inner_arg(arg); |
| if let hir::ExprKind::Call(fun, or_args) = inner_arg.kind { |
| let fun_span = if or_args.is_empty() { Some(fun.span) } else { None }; |
| check_general_case(cx, name, method_span, receiver, arg, Some(lambda), expr.span, fun_span); |
| } |
| } |
| } |
| |
| fn closure_body_returns_empty_to_string(cx: &LateContext<'_>, e: &hir::Expr<'_>) -> bool { |
| if let hir::ExprKind::Closure(&hir::Closure { body, .. }) = e.kind { |
| let body = cx.tcx.hir().body(body); |
| |
| if body.params.is_empty() |
| && let hir::Expr { kind, .. } = &body.value |
| && let hir::ExprKind::MethodCall(hir::PathSegment { ident, .. }, self_arg, _, _) = kind |
| && ident.name == sym::to_string |
| && let hir::Expr { kind, .. } = self_arg |
| && let hir::ExprKind::Lit(lit) = kind |
| && let ast::LitKind::Str(symbol::kw::Empty, _) = lit.node |
| { |
| return true; |
| } |
| } |
| |
| false |
| } |