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