]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/fallible_impl_from.rs
Auto merge of #3558 - russelltg:new_without_default_merge, r=flip1995
[rust.git] / clippy_lints / src / fallible_impl_from.rs
1 // Copyright 2014-2018 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution.
3 //
4 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
5 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
6 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
7 // option. This file may not be copied, modified, or distributed
8 // except according to those terms.
9
10 use crate::utils::paths::{BEGIN_PANIC, BEGIN_PANIC_FMT, FROM_TRAIT, OPTION, RESULT};
11 use crate::utils::{is_expn_of, match_def_path, method_chain_args, opt_def_id, span_lint_and_then, walk_ptrs_ty};
12 use if_chain::if_chain;
13 use rustc::hir;
14 use rustc::lint::{LateContext, LateLintPass, LintArray, LintPass};
15 use rustc::ty;
16 use rustc::{declare_tool_lint, lint_array};
17 use syntax_pos::Span;
18
19 /// **What it does:** Checks for impls of `From<..>` that contain `panic!()` or `unwrap()`
20 ///
21 /// **Why is this bad?** `TryFrom` should be used if there's a possibility of failure.
22 ///
23 /// **Known problems:** None.
24 ///
25 /// **Example:**
26 /// ```rust
27 /// struct Foo(i32);
28 /// impl From<String> for Foo {
29 ///     fn from(s: String) -> Self {
30 ///         Foo(s.parse().unwrap())
31 ///     }
32 /// }
33 /// ```
34 declare_clippy_lint! {
35     pub FALLIBLE_IMPL_FROM,
36     nursery,
37     "Warn on impls of `From<..>` that contain `panic!()` or `unwrap()`"
38 }
39
40 pub struct FallibleImplFrom;
41
42 impl LintPass for FallibleImplFrom {
43     fn get_lints(&self) -> LintArray {
44         lint_array!(FALLIBLE_IMPL_FROM)
45     }
46 }
47
48 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for FallibleImplFrom {
49     fn check_item(&mut self, cx: &LateContext<'a, 'tcx>, item: &'tcx hir::Item) {
50         // check for `impl From<???> for ..`
51         let impl_def_id = cx.tcx.hir().local_def_id(item.id);
52         if_chain! {
53             if let hir::ItemKind::Impl(.., ref impl_items) = item.node;
54             if let Some(impl_trait_ref) = cx.tcx.impl_trait_ref(impl_def_id);
55             if match_def_path(cx.tcx, impl_trait_ref.def_id, &FROM_TRAIT);
56             then {
57                 lint_impl_body(cx, item.span, impl_items);
58             }
59         }
60     }
61 }
62
63 fn lint_impl_body<'a, 'tcx>(cx: &LateContext<'a, 'tcx>, impl_span: Span, impl_items: &hir::HirVec<hir::ImplItemRef>) {
64     use rustc::hir::intravisit::{self, NestedVisitorMap, Visitor};
65     use rustc::hir::*;
66
67     struct FindPanicUnwrap<'a, 'tcx: 'a> {
68         tcx: ty::TyCtxt<'a, 'tcx, 'tcx>,
69         tables: &'tcx ty::TypeckTables<'tcx>,
70         result: Vec<Span>,
71     }
72
73     impl<'a, 'tcx: 'a> Visitor<'tcx> for FindPanicUnwrap<'a, 'tcx> {
74         fn visit_expr(&mut self, expr: &'tcx Expr) {
75             // check for `begin_panic`
76             if_chain! {
77                 if let ExprKind::Call(ref func_expr, _) = expr.node;
78                 if let ExprKind::Path(QPath::Resolved(_, ref path)) = func_expr.node;
79                 if let Some(path_def_id) = opt_def_id(path.def);
80                 if match_def_path(self.tcx, path_def_id, &BEGIN_PANIC) ||
81                     match_def_path(self.tcx, path_def_id, &BEGIN_PANIC_FMT);
82                 if is_expn_of(expr.span, "unreachable").is_none();
83                 then {
84                     self.result.push(expr.span);
85                 }
86             }
87
88             // check for `unwrap`
89             if let Some(arglists) = method_chain_args(expr, &["unwrap"]) {
90                 let reciever_ty = walk_ptrs_ty(self.tables.expr_ty(&arglists[0][0]));
91                 if match_type(self.tcx, reciever_ty, &OPTION) || match_type(self.tcx, reciever_ty, &RESULT) {
92                     self.result.push(expr.span);
93                 }
94             }
95
96             // and check sub-expressions
97             intravisit::walk_expr(self, expr);
98         }
99
100         fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
101             NestedVisitorMap::None
102         }
103     }
104
105     for impl_item in impl_items {
106         if_chain! {
107             if impl_item.ident.name == "from";
108             if let ImplItemKind::Method(_, body_id) =
109                 cx.tcx.hir().impl_item(impl_item.id).node;
110             then {
111                 // check the body for `begin_panic` or `unwrap`
112                 let body = cx.tcx.hir().body(body_id);
113                 let impl_item_def_id = cx.tcx.hir().local_def_id(impl_item.id.node_id);
114                 let mut fpu = FindPanicUnwrap {
115                     tcx: cx.tcx,
116                     tables: cx.tcx.typeck_tables_of(impl_item_def_id),
117                     result: Vec::new(),
118                 };
119                 fpu.visit_expr(&body.value);
120
121                 // if we've found one, lint
122                 if !fpu.result.is_empty() {
123                     span_lint_and_then(
124                         cx,
125                         FALLIBLE_IMPL_FROM,
126                         impl_span,
127                         "consider implementing `TryFrom` instead",
128                         move |db| {
129                             db.help(
130                                 "`From` is intended for infallible conversions only. \
131                                  Use `TryFrom` if there's a possibility for the conversion to fail.");
132                             db.span_note(fpu.result, "potential failure(s)");
133                         });
134                 }
135             }
136         }
137     }
138 }
139
140 fn match_type(tcx: ty::TyCtxt<'_, '_, '_>, ty: ty::Ty<'_>, path: &[&str]) -> bool {
141     match ty.sty {
142         ty::Adt(adt, _) => match_def_path(tcx, adt.did, path),
143         _ => false,
144     }
145 }