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