use crate::utils::{ contains_return, in_macro, is_type_diagnostic_item, match_qpath, paths, return_ty, snippet, span_lint_and_then, visitors::find_all_ret_expressions, }; use if_chain::if_chain; use rustc_errors::Applicability; use rustc_hir::intravisit::FnKind; use rustc_hir::{Body, ExprKind, FnDecl, HirId, ItemKind, Node}; use rustc_lint::{LateContext, LateLintPass}; use rustc_middle::ty::subst::GenericArgKind; use rustc_session::{declare_lint_pass, declare_tool_lint}; use rustc_span::Span; declare_clippy_lint! { /// **What it does:** Checks for private functions that only return `Ok` or `Some`. /// /// **Why is this bad?** It is not meaningful to wrap values when no `None` or `Err` is returned. /// /// **Known problems:** Since this lint changes function type signature, you may need to /// adjust some code at callee side. /// /// **Example:** /// /// ```rust /// fn get_cool_number(a: bool, b: bool) -> Option { /// if a && b { /// return Some(50); /// } /// if a { /// Some(0) /// } else { /// Some(10) /// } /// } /// ``` /// Use instead: /// ```rust /// fn get_cool_number(a: bool, b: bool) -> i32 { /// if a && b { /// return 50; /// } /// if a { /// 0 /// } else { /// 10 /// } /// } /// ``` pub UNNECESSARY_WRAPS, complexity, "functions that only return `Ok` or `Some`" } declare_lint_pass!(UnnecessaryWraps => [UNNECESSARY_WRAPS]); impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { fn check_fn( &mut self, cx: &LateContext<'tcx>, fn_kind: FnKind<'tcx>, fn_decl: &FnDecl<'tcx>, body: &Body<'tcx>, span: Span, hir_id: HirId, ) { match fn_kind { FnKind::ItemFn(.., visibility, _) | FnKind::Method(.., Some(visibility), _) => { if visibility.node.is_pub() { return; } }, FnKind::Closure(..) => return, _ => (), } if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { if matches!(item.kind, ItemKind::Impl{ of_trait: Some(_), ..} | ItemKind::Trait(..)) { return; } } let (return_type, path) = if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym!(option_type)) { ("Option", &paths::OPTION_SOME) } else if is_type_diagnostic_item(cx, return_ty(cx, hir_id), sym!(result_type)) { ("Result", &paths::RESULT_OK) } else { return; }; let mut suggs = Vec::new(); let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| { if_chain! { if !in_macro(ret_expr.span); if let ExprKind::Call(ref func, ref args) = ret_expr.kind; if let ExprKind::Path(ref qpath) = func.kind; if match_qpath(qpath, path); if args.len() == 1; if !contains_return(&args[0]); then { suggs.push((ret_expr.span, snippet(cx, args[0].span.source_callsite(), "..").to_string())); true } else { false } } }); if can_sugg && !suggs.is_empty() { span_lint_and_then( cx, UNNECESSARY_WRAPS, span, format!( "this function's return value is unnecessarily wrapped by `{}`", return_type ) .as_str(), |diag| { let inner_ty = return_ty(cx, hir_id) .walk() .skip(1) // skip `std::option::Option` or `std::result::Result` .take(1) // take the first outermost inner type .filter_map(|inner| match inner.unpack() { GenericArgKind::Type(inner_ty) => Some(inner_ty.to_string()), _ => None, }); inner_ty.for_each(|inner_ty| { diag.span_suggestion( fn_decl.output.span(), format!("remove `{}` from the return type...", return_type).as_str(), inner_ty, Applicability::MaybeIncorrect, ); }); diag.multipart_suggestion( "...and change the returning expressions", suggs, Applicability::MaybeIncorrect, ); }, ); } } }