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