]>
Commit | Line | Data |
---|---|---|
cdc7bbd5 | 1 | use clippy_utils::diagnostics::span_lint_and_then; |
5099ac24 FG |
2 | use clippy_utils::macros::{is_panic, root_macro_call_first_node}; |
3 | use clippy_utils::method_chain_args; | |
cdc7bbd5 | 4 | use clippy_utils::ty::is_type_diagnostic_item; |
f20569fa XL |
5 | use if_chain::if_chain; |
6 | use rustc_hir as hir; | |
7 | use rustc_lint::{LateContext, LateLintPass}; | |
f20569fa XL |
8 | use rustc_middle::ty; |
9 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
10 | use rustc_span::{sym, Span}; | |
11 | ||
12 | declare_clippy_lint! { | |
94222f64 XL |
13 | /// ### What it does |
14 | /// Checks for impls of `From<..>` that contain `panic!()` or `unwrap()` | |
f20569fa | 15 | /// |
94222f64 XL |
16 | /// ### Why is this bad? |
17 | /// `TryFrom` should be used if there's a possibility of failure. | |
f20569fa | 18 | /// |
94222f64 | 19 | /// ### Example |
f20569fa XL |
20 | /// ```rust |
21 | /// struct Foo(i32); | |
22 | /// | |
f20569fa XL |
23 | /// impl From<String> for Foo { |
24 | /// fn from(s: String) -> Self { | |
25 | /// Foo(s.parse().unwrap()) | |
26 | /// } | |
27 | /// } | |
28 | /// ``` | |
29 | /// | |
923072b8 | 30 | /// Use instead: |
f20569fa | 31 | /// ```rust |
f20569fa XL |
32 | /// struct Foo(i32); |
33 | /// | |
f20569fa XL |
34 | /// impl TryFrom<String> for Foo { |
35 | /// type Error = (); | |
36 | /// fn try_from(s: String) -> Result<Self, Self::Error> { | |
37 | /// if let Ok(parsed) = s.parse() { | |
38 | /// Ok(Foo(parsed)) | |
39 | /// } else { | |
40 | /// Err(()) | |
41 | /// } | |
42 | /// } | |
43 | /// } | |
44 | /// ``` | |
a2a8927a | 45 | #[clippy::version = "pre 1.29.0"] |
f20569fa XL |
46 | pub FALLIBLE_IMPL_FROM, |
47 | nursery, | |
48 | "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`" | |
49 | } | |
50 | ||
51 | declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]); | |
52 | ||
53 | impl<'tcx> LateLintPass<'tcx> for FallibleImplFrom { | |
54 | fn check_item(&mut self, cx: &LateContext<'tcx>, item: &'tcx hir::Item<'_>) { | |
55 | // check for `impl From<???> for ..` | |
56 | if_chain! { | |
57 | if let hir::ItemKind::Impl(impl_) = &item.kind; | |
2b03887a | 58 | if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.owner_id); |
9c376795 | 59 | if cx.tcx.is_diagnostic_item(sym::From, impl_trait_ref.skip_binder().def_id); |
f20569fa XL |
60 | then { |
61 | lint_impl_body(cx, item.span, impl_.items); | |
62 | } | |
63 | } | |
64 | } | |
65 | } | |
66 | ||
487cf647 | 67 | fn lint_impl_body(cx: &LateContext<'_>, impl_span: Span, impl_items: &[hir::ImplItemRef]) { |
5099ac24 FG |
68 | use rustc_hir::intravisit::{self, Visitor}; |
69 | use rustc_hir::{Expr, ImplItemKind}; | |
f20569fa XL |
70 | |
71 | struct FindPanicUnwrap<'a, 'tcx> { | |
72 | lcx: &'a LateContext<'tcx>, | |
73 | typeck_results: &'tcx ty::TypeckResults<'tcx>, | |
74 | result: Vec<Span>, | |
75 | } | |
76 | ||
77 | impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> { | |
f20569fa | 78 | fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { |
5099ac24 FG |
79 | if let Some(macro_call) = root_macro_call_first_node(self.lcx, expr) { |
80 | if is_panic(self.lcx, macro_call.def_id) { | |
f20569fa XL |
81 | self.result.push(expr.span); |
82 | } | |
83 | } | |
84 | ||
85 | // check for `unwrap` | |
86 | if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { | |
f2b60f7d | 87 | let receiver_ty = self.typeck_results.expr_ty(arglists[0].0).peel_refs(); |
5e7ed085 FG |
88 | if is_type_diagnostic_item(self.lcx, receiver_ty, sym::Option) |
89 | || is_type_diagnostic_item(self.lcx, receiver_ty, sym::Result) | |
f20569fa XL |
90 | { |
91 | self.result.push(expr.span); | |
92 | } | |
93 | } | |
94 | ||
95 | // and check sub-expressions | |
96 | intravisit::walk_expr(self, expr); | |
97 | } | |
f20569fa XL |
98 | } |
99 | ||
100 | for impl_item in impl_items { | |
101 | if_chain! { | |
102 | if impl_item.ident.name == sym::from; | |
103 | if let ImplItemKind::Fn(_, body_id) = | |
104 | cx.tcx.hir().impl_item(impl_item.id).kind; | |
105 | then { | |
106 | // check the body for `begin_panic` or `unwrap` | |
107 | let body = cx.tcx.hir().body(body_id); | |
108 | let mut fpu = FindPanicUnwrap { | |
109 | lcx: cx, | |
2b03887a | 110 | typeck_results: cx.tcx.typeck(impl_item.id.owner_id.def_id), |
f20569fa XL |
111 | result: Vec::new(), |
112 | }; | |
f2b60f7d | 113 | fpu.visit_expr(body.value); |
f20569fa XL |
114 | |
115 | // if we've found one, lint | |
116 | if !fpu.result.is_empty() { | |
117 | span_lint_and_then( | |
118 | cx, | |
119 | FALLIBLE_IMPL_FROM, | |
120 | impl_span, | |
121 | "consider implementing `TryFrom` instead", | |
122 | move |diag| { | |
123 | diag.help( | |
124 | "`From` is intended for infallible conversions only. \ | |
125 | Use `TryFrom` if there's a possibility for the conversion to fail"); | |
126 | diag.span_note(fpu.result, "potential failure(s)"); | |
127 | }); | |
128 | } | |
129 | } | |
130 | } | |
131 | } | |
132 | } |