]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | use crate::utils::{is_expn_of, is_type_diagnostic_item, match_panic_def_id, method_chain_args, span_lint_and_then}; |
2 | use if_chain::if_chain; | |
3 | use rustc_hir as hir; | |
4 | use rustc_lint::{LateContext, LateLintPass}; | |
5 | use rustc_middle::hir::map::Map; | |
6 | use rustc_middle::ty; | |
7 | use rustc_session::{declare_lint_pass, declare_tool_lint}; | |
8 | use rustc_span::{sym, Span}; | |
9 | ||
10 | declare_clippy_lint! { | |
11 | /// **What it does:** Checks for impls of `From<..>` that contain `panic!()` or `unwrap()` | |
12 | /// | |
13 | /// **Why is this bad?** `TryFrom` should be used if there's a possibility of failure. | |
14 | /// | |
15 | /// **Known problems:** None. | |
16 | /// | |
17 | /// **Example:** | |
18 | /// ```rust | |
19 | /// struct Foo(i32); | |
20 | /// | |
21 | /// // Bad | |
22 | /// impl From<String> for Foo { | |
23 | /// fn from(s: String) -> Self { | |
24 | /// Foo(s.parse().unwrap()) | |
25 | /// } | |
26 | /// } | |
27 | /// ``` | |
28 | /// | |
29 | /// ```rust | |
30 | /// // Good | |
31 | /// struct Foo(i32); | |
32 | /// | |
33 | /// use std::convert::TryFrom; | |
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 | /// ``` | |
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 ..` | |
55 | if_chain! { | |
56 | if let hir::ItemKind::Impl(impl_) = &item.kind; | |
57 | if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(item.def_id); | |
58 | if cx.tcx.is_diagnostic_item(sym::from_trait, impl_trait_ref.def_id); | |
59 | then { | |
60 | lint_impl_body(cx, item.span, impl_.items); | |
61 | } | |
62 | } | |
63 | } | |
64 | } | |
65 | ||
66 | fn lint_impl_body<'tcx>(cx: &LateContext<'tcx>, impl_span: Span, impl_items: &[hir::ImplItemRef<'_>]) { | |
67 | use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor}; | |
68 | use rustc_hir::{Expr, ExprKind, ImplItemKind, QPath}; | |
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> { | |
77 | type Map = Map<'tcx>; | |
78 | ||
79 | fn visit_expr(&mut self, expr: &'tcx Expr<'_>) { | |
80 | // check for `begin_panic` | |
81 | if_chain! { | |
82 | if let ExprKind::Call(ref func_expr, _) = expr.kind; | |
83 | if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind; | |
84 | if let Some(path_def_id) = path.res.opt_def_id(); | |
85 | if match_panic_def_id(self.lcx, path_def_id); | |
86 | if is_expn_of(expr.span, "unreachable").is_none(); | |
87 | then { | |
88 | self.result.push(expr.span); | |
89 | } | |
90 | } | |
91 | ||
92 | // check for `unwrap` | |
93 | if let Some(arglists) = method_chain_args(expr, &["unwrap"]) { | |
94 | let reciever_ty = self.typeck_results.expr_ty(&arglists[0][0]).peel_refs(); | |
95 | if is_type_diagnostic_item(self.lcx, reciever_ty, sym::option_type) | |
96 | || is_type_diagnostic_item(self.lcx, reciever_ty, sym::result_type) | |
97 | { | |
98 | self.result.push(expr.span); | |
99 | } | |
100 | } | |
101 | ||
102 | // and check sub-expressions | |
103 | intravisit::walk_expr(self, expr); | |
104 | } | |
105 | ||
106 | fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> { | |
107 | NestedVisitorMap::None | |
108 | } | |
109 | } | |
110 | ||
111 | for impl_item in impl_items { | |
112 | if_chain! { | |
113 | if impl_item.ident.name == sym::from; | |
114 | if let ImplItemKind::Fn(_, body_id) = | |
115 | cx.tcx.hir().impl_item(impl_item.id).kind; | |
116 | then { | |
117 | // check the body for `begin_panic` or `unwrap` | |
118 | let body = cx.tcx.hir().body(body_id); | |
119 | let mut fpu = FindPanicUnwrap { | |
120 | lcx: cx, | |
121 | typeck_results: cx.tcx.typeck(impl_item.id.def_id), | |
122 | result: Vec::new(), | |
123 | }; | |
124 | fpu.visit_expr(&body.value); | |
125 | ||
126 | // if we've found one, lint | |
127 | if !fpu.result.is_empty() { | |
128 | span_lint_and_then( | |
129 | cx, | |
130 | FALLIBLE_IMPL_FROM, | |
131 | impl_span, | |
132 | "consider implementing `TryFrom` instead", | |
133 | move |diag| { | |
134 | diag.help( | |
135 | "`From` is intended for infallible conversions only. \ | |
136 | Use `TryFrom` if there's a possibility for the conversion to fail"); | |
137 | diag.span_note(fpu.result, "potential failure(s)"); | |
138 | }); | |
139 | } | |
140 | } | |
141 | } | |
142 | } | |
143 | } |