| use clippy_utils::diagnostics::span_lint_and_then; |
| use clippy_utils::res::MaybeDef; |
| use clippy_utils::ty::is_never_like; |
| use clippy_utils::{is_in_test, is_inside_always_const_context, is_lint_allowed}; |
| use rustc_hir::Expr; |
| use rustc_hir::def::DefKind; |
| use rustc_lint::{LateContext, Lint}; |
| use rustc_middle::ty; |
| use rustc_span::sym; |
| |
| use super::{EXPECT_USED, UNWRAP_USED}; |
| |
| #[derive(Clone, Copy, Eq, PartialEq)] |
| pub(super) enum Variant { |
| Unwrap, |
| Expect, |
| } |
| |
| impl Variant { |
| fn method_name(self, is_err: bool) -> &'static str { |
| match (self, is_err) { |
| (Variant::Unwrap, true) => "unwrap_err", |
| (Variant::Unwrap, false) => "unwrap", |
| (Variant::Expect, true) => "expect_err", |
| (Variant::Expect, false) => "expect", |
| } |
| } |
| |
| fn lint(self) -> &'static Lint { |
| match self { |
| Variant::Unwrap => UNWRAP_USED, |
| Variant::Expect => EXPECT_USED, |
| } |
| } |
| } |
| |
| /// Lint usage of `unwrap` or `unwrap_err` for `Result` and `unwrap()` for `Option` (and their |
| /// `expect` counterparts). |
| #[allow(clippy::too_many_arguments)] |
| pub(super) fn check( |
| cx: &LateContext<'_>, |
| expr: &Expr<'_>, |
| recv: &Expr<'_>, |
| is_err: bool, |
| allow_unwrap_in_consts: bool, |
| allow_unwrap_in_tests: bool, |
| unwrap_allowed_ids: &rustc_data_structures::fx::FxHashSet<rustc_hir::def_id::DefId>, |
| unwrap_allowed_aliases: &[rustc_hir::def_id::DefId], |
| variant: Variant, |
| ) { |
| let ty = cx.typeck_results().expr_ty(recv).peel_refs(); |
| |
| let (kind, none_value, none_prefix) = match ty.opt_diag_name(cx) { |
| Some(sym::Option) if !is_err => ("an `Option`", "None", ""), |
| Some(sym::Result) |
| if let ty::Adt(_, substs) = ty.kind() |
| && let Some(t_or_e_ty) = substs[usize::from(!is_err)].as_type() => |
| { |
| if is_never_like(t_or_e_ty) { |
| return; |
| } |
| |
| ("a `Result`", if is_err { "Ok" } else { "Err" }, "an ") |
| }, |
| _ => return, |
| }; |
| |
| let method_suffix = if is_err { "_err" } else { "" }; |
| |
| if let ty::Adt(adt, _) = ty.kind() |
| && unwrap_allowed_ids.contains(&adt.did()) |
| { |
| return; |
| } |
| |
| for &def_id in unwrap_allowed_aliases { |
| let alias_ty = cx.tcx.type_of(def_id).instantiate_identity(); |
| if let (ty::Adt(adt, substs), ty::Adt(alias_adt, alias_substs)) = (ty.kind(), alias_ty.kind()) |
| && adt.did() == alias_adt.did() |
| { |
| let mut all_match = true; |
| for (arg, alias_arg) in substs.iter().zip(alias_substs.iter()) { |
| if let (Some(arg_ty), Some(alias_arg_ty)) = (arg.as_type(), alias_arg.as_type()) { |
| if matches!(alias_arg_ty.kind(), ty::Param(_)) { |
| continue; |
| } |
| if let (ty::Adt(arg_adt, _), ty::Adt(alias_arg_adt, _)) = |
| (arg_ty.peel_refs().kind(), alias_arg_ty.peel_refs().kind()) |
| { |
| if arg_adt.did() != alias_arg_adt.did() { |
| all_match = false; |
| break; |
| } |
| } else if arg_ty != alias_arg_ty { |
| all_match = false; |
| break; |
| } |
| } |
| } |
| if all_match { |
| return; |
| } |
| } |
| } |
| |
| if allow_unwrap_in_tests && is_in_test(cx.tcx, expr.hir_id) { |
| return; |
| } |
| |
| if allow_unwrap_in_consts && is_inside_always_const_context(cx.tcx, expr.hir_id) { |
| return; |
| } |
| |
| span_lint_and_then( |
| cx, |
| variant.lint(), |
| expr.span, |
| format!("used `{}()` on {kind} value", variant.method_name(is_err)), |
| |diag| { |
| diag.note(format!("if this value is {none_prefix}`{none_value}`, it will panic")); |
| |
| if variant == Variant::Unwrap && is_lint_allowed(cx, EXPECT_USED, expr.hir_id) { |
| diag.help(format!( |
| "consider using `expect{method_suffix}()` to provide a better panic message" |
| )); |
| } |
| }, |
| ); |
| } |
| |
| #[expect(clippy::too_many_arguments, clippy::fn_params_excessive_bools)] |
| pub(super) fn check_call( |
| cx: &LateContext<'_>, |
| expr: &Expr<'_>, |
| func: &Expr<'_>, |
| args: &[Expr<'_>], |
| allow_unwrap_in_consts: bool, |
| allow_unwrap_in_tests: bool, |
| allow_expect_in_consts: bool, |
| allow_expect_in_tests: bool, |
| unwrap_allowed_ids: &rustc_data_structures::fx::FxHashSet<rustc_hir::def_id::DefId>, |
| unwrap_allowed_aliases: &[rustc_hir::def_id::DefId], |
| ) { |
| let Some(recv) = args.first() else { |
| return; |
| }; |
| let Some((DefKind::AssocFn, def_id)) = cx.typeck_results().type_dependent_def(func.hir_id) else { |
| return; |
| }; |
| |
| match cx.tcx.item_name(def_id) { |
| sym::unwrap => { |
| check( |
| cx, |
| expr, |
| recv, |
| false, |
| allow_unwrap_in_consts, |
| allow_unwrap_in_tests, |
| unwrap_allowed_ids, |
| unwrap_allowed_aliases, |
| Variant::Unwrap, |
| ); |
| }, |
| sym::expect => { |
| check( |
| cx, |
| expr, |
| recv, |
| false, |
| allow_expect_in_consts, |
| allow_expect_in_tests, |
| unwrap_allowed_ids, |
| unwrap_allowed_aliases, |
| Variant::Expect, |
| ); |
| }, |
| clippy_utils::sym::unwrap_err => { |
| check( |
| cx, |
| expr, |
| recv, |
| true, |
| allow_unwrap_in_consts, |
| allow_unwrap_in_tests, |
| unwrap_allowed_ids, |
| unwrap_allowed_aliases, |
| Variant::Unwrap, |
| ); |
| }, |
| clippy_utils::sym::expect_err => { |
| check( |
| cx, |
| expr, |
| recv, |
| true, |
| allow_expect_in_consts, |
| allow_expect_in_tests, |
| unwrap_allowed_ids, |
| unwrap_allowed_aliases, |
| Variant::Expect, |
| ); |
| }, |
| _ => (), |
| } |
| } |