1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::source::snippet;
3 use clippy_utils::{contains_return, is_res_lang_ctor, path_res, return_ty, visitors::find_all_ret_expressions};
4 use if_chain::if_chain;
5 use rustc_errors::Applicability;
6 use rustc_hir::intravisit::FnKind;
7 use rustc_hir::LangItem::{OptionSome, ResultOk};
8 use rustc_hir::{Body, ExprKind, FnDecl, Impl, ItemKind, Node};
9 use rustc_lint::{LateContext, LateLintPass};
11 use rustc_session::{declare_tool_lint, impl_lint_pass};
12 use rustc_span::def_id::LocalDefId;
13 use rustc_span::symbol::sym;
16 declare_clippy_lint! {
18 /// Checks for private functions that only return `Ok` or `Some`.
20 /// ### Why is this bad?
21 /// It is not meaningful to wrap values when no `None` or `Err` is returned.
23 /// ### Known problems
24 /// There can be false positives if the function signature is designed to
25 /// fit some external requirement.
29 /// fn get_cool_number(a: bool, b: bool) -> Option<i32> {
42 /// fn get_cool_number(a: bool, b: bool) -> i32 {
53 #[clippy::version = "1.50.0"]
54 pub UNNECESSARY_WRAPS,
56 "functions that only return `Ok` or `Some`"
59 pub struct UnnecessaryWraps {
60 avoid_breaking_exported_api: bool,
63 impl_lint_pass!(UnnecessaryWraps => [UNNECESSARY_WRAPS]);
65 impl UnnecessaryWraps {
66 pub fn new(avoid_breaking_exported_api: bool) -> Self {
68 avoid_breaking_exported_api,
73 impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
76 cx: &LateContext<'tcx>,
77 fn_kind: FnKind<'tcx>,
78 fn_decl: &FnDecl<'tcx>,
83 // Abort if public function/method or closure.
85 FnKind::ItemFn(..) | FnKind::Method(..) => {
86 if self.avoid_breaking_exported_api && cx.effective_visibilities.is_exported(def_id) {
90 FnKind::Closure => return,
93 // Abort if the method is implementing a trait or of it a trait method.
94 let hir_id = cx.tcx.hir().local_def_id_to_hir_id(def_id);
95 if let Some(Node::Item(item)) = cx.tcx.hir().find_parent(hir_id) {
98 ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
104 // Get the wrapper and inner types, if can't, abort.
105 let (return_type_label, lang_item, inner_type) =
106 if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id.expect_owner()).kind() {
107 if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) {
108 ("Option", OptionSome, subst.type_at(0))
109 } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) {
110 ("Result", ResultOk, subst.type_at(0))
118 // Check if all return expression respect the following condition and collect them.
119 let mut suggs = Vec::new();
120 let can_sugg = find_all_ret_expressions(cx, body.value, |ret_expr| {
122 if !ret_expr.span.from_expansion();
123 // Check if a function call.
124 if let ExprKind::Call(func, [arg]) = ret_expr.kind;
125 if is_res_lang_ctor(cx, path_res(cx, func), lang_item);
126 // Make sure the function argument does not contain a return expression.
127 if !contains_return(arg);
132 if inner_type.is_unit() {
135 snippet(cx, arg.span.source_callsite(), "..").to_string()
146 if can_sugg && !suggs.is_empty() {
147 let (lint_msg, return_type_sugg_msg, return_type_sugg, body_sugg_msg) = if inner_type.is_unit() {
149 "this function's return value is unnecessary".to_string(),
150 "remove the return type...".to_string(),
151 snippet(cx, fn_decl.output.span(), "..").to_string(),
152 "...and then remove returned values",
156 format!("this function's return value is unnecessarily wrapped by `{return_type_label}`"),
157 format!("remove `{return_type_label}` from the return type..."),
158 inner_type.to_string(),
159 "...and then change returning expressions",
163 span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| {
164 diag.span_suggestion(
165 fn_decl.output.span(),
166 return_type_sugg_msg.as_str(),
168 Applicability::MaybeIncorrect,
170 diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect);