]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 XL |
1 | use clippy_utils::diagnostics::span_lint_and_then; |
2 | use clippy_utils::source::snippet; | |
a2a8927a | 3 | use clippy_utils::{contains_return, is_lang_ctor, return_ty, visitors::find_all_ret_expressions}; |
f20569fa XL |
4 | use if_chain::if_chain; |
5 | use rustc_errors::Applicability; | |
6 | use rustc_hir::intravisit::FnKind; | |
cdc7bbd5 | 7 | use rustc_hir::LangItem::{OptionSome, ResultOk}; |
f20569fa XL |
8 | use rustc_hir::{Body, ExprKind, FnDecl, HirId, Impl, ItemKind, Node}; |
9 | use rustc_lint::{LateContext, LateLintPass}; | |
10 | use rustc_middle::ty; | |
17df50a5 | 11 | use rustc_session::{declare_tool_lint, impl_lint_pass}; |
f20569fa XL |
12 | use rustc_span::symbol::sym; |
13 | use rustc_span::Span; | |
14 | ||
15 | declare_clippy_lint! { | |
94222f64 XL |
16 | /// ### What it does |
17 | /// Checks for private functions that only return `Ok` or `Some`. | |
f20569fa | 18 | /// |
94222f64 XL |
19 | /// ### Why is this bad? |
20 | /// It is not meaningful to wrap values when no `None` or `Err` is returned. | |
f20569fa | 21 | /// |
94222f64 XL |
22 | /// ### Known problems |
23 | /// There can be false positives if the function signature is designed to | |
f20569fa XL |
24 | /// fit some external requirement. |
25 | /// | |
94222f64 | 26 | /// ### Example |
f20569fa XL |
27 | /// ```rust |
28 | /// fn get_cool_number(a: bool, b: bool) -> Option<i32> { | |
29 | /// if a && b { | |
30 | /// return Some(50); | |
31 | /// } | |
32 | /// if a { | |
33 | /// Some(0) | |
34 | /// } else { | |
35 | /// Some(10) | |
36 | /// } | |
37 | /// } | |
38 | /// ``` | |
39 | /// Use instead: | |
40 | /// ```rust | |
41 | /// fn get_cool_number(a: bool, b: bool) -> i32 { | |
42 | /// if a && b { | |
43 | /// return 50; | |
44 | /// } | |
45 | /// if a { | |
46 | /// 0 | |
47 | /// } else { | |
48 | /// 10 | |
49 | /// } | |
50 | /// } | |
51 | /// ``` | |
a2a8927a | 52 | #[clippy::version = "1.50.0"] |
f20569fa XL |
53 | pub UNNECESSARY_WRAPS, |
54 | pedantic, | |
55 | "functions that only return `Ok` or `Some`" | |
56 | } | |
57 | ||
17df50a5 XL |
58 | pub struct UnnecessaryWraps { |
59 | avoid_breaking_exported_api: bool, | |
60 | } | |
61 | ||
62 | impl_lint_pass!(UnnecessaryWraps => [UNNECESSARY_WRAPS]); | |
63 | ||
64 | impl UnnecessaryWraps { | |
65 | pub fn new(avoid_breaking_exported_api: bool) -> Self { | |
66 | Self { | |
67 | avoid_breaking_exported_api, | |
68 | } | |
69 | } | |
70 | } | |
f20569fa XL |
71 | |
72 | impl<'tcx> LateLintPass<'tcx> for UnnecessaryWraps { | |
73 | fn check_fn( | |
74 | &mut self, | |
75 | cx: &LateContext<'tcx>, | |
76 | fn_kind: FnKind<'tcx>, | |
77 | fn_decl: &FnDecl<'tcx>, | |
78 | body: &Body<'tcx>, | |
79 | span: Span, | |
80 | hir_id: HirId, | |
81 | ) { | |
82 | // Abort if public function/method or closure. | |
83 | match fn_kind { | |
17df50a5 | 84 | FnKind::ItemFn(..) | FnKind::Method(..) => { |
94222f64 XL |
85 | let def_id = cx.tcx.hir().local_def_id(hir_id); |
86 | if self.avoid_breaking_exported_api && cx.access_levels.is_exported(def_id) { | |
f20569fa XL |
87 | return; |
88 | } | |
89 | }, | |
90 | FnKind::Closure => return, | |
f20569fa XL |
91 | } |
92 | ||
93 | // Abort if the method is implementing a trait or of it a trait method. | |
94 | if let Some(Node::Item(item)) = cx.tcx.hir().find(cx.tcx.hir().get_parent_node(hir_id)) { | |
95 | if matches!( | |
96 | item.kind, | |
97 | ItemKind::Impl(Impl { of_trait: Some(_), .. }) | ItemKind::Trait(..) | |
98 | ) { | |
99 | return; | |
100 | } | |
101 | } | |
102 | ||
103 | // Get the wrapper and inner types, if can't, abort. | |
cdc7bbd5 | 104 | let (return_type_label, lang_item, inner_type) = if let ty::Adt(adt_def, subst) = return_ty(cx, hir_id).kind() { |
ee023bcb | 105 | if cx.tcx.is_diagnostic_item(sym::Option, adt_def.did()) { |
cdc7bbd5 | 106 | ("Option", OptionSome, subst.type_at(0)) |
ee023bcb | 107 | } else if cx.tcx.is_diagnostic_item(sym::Result, adt_def.did()) { |
cdc7bbd5 | 108 | ("Result", ResultOk, subst.type_at(0)) |
f20569fa XL |
109 | } else { |
110 | return; | |
111 | } | |
112 | } else { | |
113 | return; | |
114 | }; | |
115 | ||
116 | // Check if all return expression respect the following condition and collect them. | |
117 | let mut suggs = Vec::new(); | |
118 | let can_sugg = find_all_ret_expressions(cx, &body.value, |ret_expr| { | |
119 | if_chain! { | |
a2a8927a | 120 | if !ret_expr.span.from_expansion(); |
f20569fa | 121 | // Check if a function call. |
cdc7bbd5 | 122 | if let ExprKind::Call(func, [arg]) = ret_expr.kind; |
f20569fa | 123 | // Check if OPTION_SOME or RESULT_OK, depending on return type. |
cdc7bbd5 XL |
124 | if let ExprKind::Path(qpath) = &func.kind; |
125 | if is_lang_ctor(cx, qpath, lang_item); | |
f20569fa | 126 | // Make sure the function argument does not contain a return expression. |
cdc7bbd5 | 127 | if !contains_return(arg); |
f20569fa XL |
128 | then { |
129 | suggs.push( | |
130 | ( | |
131 | ret_expr.span, | |
132 | if inner_type.is_unit() { | |
133 | "".to_string() | |
134 | } else { | |
cdc7bbd5 | 135 | snippet(cx, arg.span.source_callsite(), "..").to_string() |
f20569fa XL |
136 | } |
137 | ) | |
138 | ); | |
139 | true | |
140 | } else { | |
141 | false | |
142 | } | |
143 | } | |
144 | }); | |
145 | ||
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() { | |
148 | ( | |
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", | |
153 | ) | |
154 | } else { | |
155 | ( | |
156 | format!( | |
157 | "this function's return value is unnecessarily wrapped by `{}`", | |
158 | return_type_label | |
159 | ), | |
160 | format!("remove `{}` from the return type...", return_type_label), | |
161 | inner_type.to_string(), | |
162 | "...and then change returning expressions", | |
163 | ) | |
164 | }; | |
165 | ||
166 | span_lint_and_then(cx, UNNECESSARY_WRAPS, span, lint_msg.as_str(), |diag| { | |
167 | diag.span_suggestion( | |
168 | fn_decl.output.span(), | |
169 | return_type_sugg_msg.as_str(), | |
170 | return_type_sugg, | |
171 | Applicability::MaybeIncorrect, | |
172 | ); | |
173 | diag.multipart_suggestion(body_sugg_msg, suggs, Applicability::MaybeIncorrect); | |
174 | }); | |
175 | } | |
176 | } | |
177 | } |