]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/fallible_impl_from.rs
Auto merge of #5482 - phansch:diag, r=matthiaskrgr
[rust.git] / clippy_lints / src / fallible_impl_from.rs
1 use crate::utils::paths::{BEGIN_PANIC, BEGIN_PANIC_FMT, FROM_TRAIT};
2 use crate::utils::{
3     is_expn_of, is_type_diagnostic_item, match_def_path, method_chain_args, span_lint_and_then, walk_ptrs_ty,
4 };
5 use if_chain::if_chain;
6 use rustc_hir as hir;
7 use rustc_lint::{LateContext, LateLintPass};
8 use rustc_middle::hir::map::Map;
9 use rustc_middle::ty;
10 use rustc_session::{declare_lint_pass, declare_tool_lint};
11 use rustc_span::Span;
12
13 declare_clippy_lint! {
14     /// **What it does:** Checks for impls of `From<..>` that contain `panic!()` or `unwrap()`
15     ///
16     /// **Why is this bad?** `TryFrom` should be used if there's a possibility of failure.
17     ///
18     /// **Known problems:** None.
19     ///
20     /// **Example:**
21     /// ```rust
22     /// struct Foo(i32);
23     /// impl From<String> for Foo {
24     ///     fn from(s: String) -> Self {
25     ///         Foo(s.parse().unwrap())
26     ///     }
27     /// }
28     /// ```
29     pub FALLIBLE_IMPL_FROM,
30     nursery,
31     "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`"
32 }
33
34 declare_lint_pass!(FallibleImplFrom => [FALLIBLE_IMPL_FROM]);
35
36 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FallibleImplFrom {
37     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item<'_>) {
38         // check for `impl From<???> for ..`
39         let impl_def_id = cx.tcx.hir().local_def_id(item.hir_id);
40         if_chain! {
41             if let hir::ItemKind::Impl{ items: impl_items, .. } = item.kind;
42             if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(impl_def_id);
43             if match_def_path(cx, impl_trait_ref.def_id, &FROM_TRAIT);
44             then {
45                 lint_impl_body(cx, item.span, impl_items);
46             }
47         }
48     }
49 }
50
51 fn lint_impl_body<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, impl_span: Span, impl_items: &[hir::ImplItemRef<'_>]) {
52     use rustc_hir::intravisit::{self, NestedVisitorMap, Visitor};
53     use rustc_hir::{Expr, ExprKind, ImplItemKind, QPath};
54
55     struct FindPanicUnwrap<'a, 'tcx> {
56         lcx: &'a LateContext<'a, 'tcx>,
57         tables: &'tcx ty::TypeckTables<'tcx>,
58         result: Vec<Span>,
59     }
60
61     impl<'a, 'tcx> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
62         type Map = Map<'tcx>;
63
64         fn visit_expr(&mut self, expr: &'tcx Expr<'_>) {
65             // check for `begin_panic`
66             if_chain! {
67                 if let ExprKind::Call(ref func_expr, _) = expr.kind;
68                 if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.kind;
69                 if let Some(path_def_id) = path.res.opt_def_id();
70                 if match_def_path(self.lcx, path_def_id, &BEGIN_PANIC) ||
71                     match_def_path(self.lcx, path_def_id, &BEGIN_PANIC_FMT);
72                 if is_expn_of(expr.span, "unreachable").is_none();
73                 then {
74                     self.result.push(expr.span);
75                 }
76             }
77
78             // check for `unwrap`
79             if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
80                 let reciever_ty = walk_ptrs_ty(self.tables.expr_ty(&arglists[0][0]));
81                 if is_type_diagnostic_item(self.lcx, reciever_ty, sym!(option_type))
82                     || is_type_diagnostic_item(self.lcx, reciever_ty, sym!(result_type))
83                 {
84                     self.result.push(expr.span);
85                 }
86             }
87
88             // and check sub-expressions
89             intravisit::walk_expr(self, expr);
90         }
91
92         fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
93             NestedVisitorMap::None
94         }
95     }
96
97     for impl_item in impl_items {
98         if_chain! {
99             if impl_item.ident.name == sym!(from);
100             if let ImplItemKind::Fn(_, body_id) =
101                 cx.tcx.hir().impl_item(impl_item.id).kind;
102             then {
103                 // check the body for `begin_panic` or `unwrap`
104                 let body = cx.tcx.hir().body(body_id);
105                 let impl_item_def_id = cx.tcx.hir().local_def_id(impl_item.id.hir_id);
106                 let mut fpu = FindPanicUnwrap {
107                     lcx: cx,
108                     tables: cx.tcx.typeck_tables_of(impl_item_def_id),
109                     result: Vec::new(),
110                 };
111                 fpu.visit_expr(&body.value);
112
113                 // if we've found one, lint
114                 if !fpu.result.is_empty() {
115                     span_lint_and_then(
116                         cx,
117                         FALLIBLE_IMPL_FROM,
118                         impl_span,
119                         "consider implementing `TryFrom` instead",
120                         move |diag| {
121                             diag.help(
122                                 "`From` is intended for infallible conversions only. \
123                                  Use `TryFrom` if there's a possibility for the conversion to fail.");
124                             diag.span_note(fpu.result, "potential failure(s)");
125                         });
126                 }
127             }
128         }
129     }
130 }