1 use clippy_utils::diagnostics::span_lint_and_then;
2 use clippy_utils::source::snippet;
3 use clippy_utils::{contains_return, in_macro, match_qpath, paths, 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::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node};
8 use rustc_lint::{LateContext, LateLintPass};
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
11 use rustc_span::symbol::sym;
14 declare_clippy_lint! {
15 /// **What it does:** Checks for private functions that only return `Ok` or `Some`.
17 /// **Why is this bad?** It is not meaningful to wrap values when no `None` or `Err` is returned.
19 /// **Known problems:** There can be false positives if the function signature is designed to
20 /// fit some external requirement.
25 /// fn get_cool_number(a: bool, b: bool) -> Option<i32> {
38 /// fn get_cool_number(a: bool, b: bool) -> i32 {
49 pub UNNECESSARY_WRAPS,
51 "functions that only return `Ok` or `Some`"
54 declare_lint_pass!(UnnecessaryWraps => [UNNECESSARY_WRAPS]);
56 impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps {
59 cx: &LateContext<'tcx>,
60 fn_kind: FnKind<'tcx>,
61 fn_decl: &FnDecl<'tcx>,
66 // Abort if public function/method or closure.
68 FnKind::ItemFn(.., visibility) | FnKind::Method(.., Some(visibility)) => {
69 if visibility.node.is_pub() {
73 FnKind::Closure => return,
74 FnKind::Method(..) => (),
77 // Abort if the method is implementing a trait or of it a trait method.
78 if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) {
81 ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..)
87 // Get the wrapper and inner types, if can't, abort.
88 let (return_type_label, path, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() {
89 if cx.tcx.is_diagnostic_item(sym::option_type, adt_def.did) {
90 ("Option", &paths::OPTION_SOME, subst.type_at(0))
91 } else if cx.tcx.is_diagnostic_item(sym::result_type, adt_def.did) {
92 ("Result", &paths::RESULT_OK, subst.type_at(0))
100 // Check if all return expression respect the following condition and collect them.
101 let mut suggs = Vec::new();
102 let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| {
104 if !in_macro(ret_expr.span);
105 // Check if a function call.
106 if let ExprKind::Call(func, args) = ret_expr.kind;
107 // Get the Path of the function call.
108 if let ExprKind::Path(ref qpath) = func.kind;
109 // Check if OPTION_SOME or RESULT_OK, depending on return type.
110 if match_qpath(qpath, path);
112 // Make sure the function argument does not contain a return expression.
113 if !contains_return(&args[0]);
118 if inner_type.is_unit() {
121 snippet(cx, args[0].span.source_callsite(), "..").to_string()
132 if can_sugg && !suggs.is_empty() {
133 let (lint_msg, return_type_sugg_msg, return_type_sugg, body_sugg_msg) = if inner_type.is_unit() {
135 "this function's return value is unnecessary".to_string(),
136 "remove the return type...".to_string(),
137 snippet(cx, fn_decl.output.span(), "..").to_string(),
138 "...and then remove returned values",
143 "this function's return value is unnecessarily wrapped by `{}`",
146 format!("remove `{}` from the return type...", return_type_label),
147 inner_type.to_string(),
148 "...and then change returning expressions",
152 span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| {
153 diag.span_suggestion(
154 fn_decl.output.span(),
155 return_type_sugg_msg.as_str(),
157 Applicability::MaybeIncorrect,
159 diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect);