]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/question_mark.rs
Merge branch 'macro-use' into HEAD
[rust.git] / clippy_lints / src / question_mark.rs
1 use rustc::lint::*;
2 use rustc::{declare_lint, lint_array};
3 use if_chain::if_chain;
4 use rustc::hir::*;
5 use rustc::hir::def::Def;
6 use crate::utils::sugg::Sugg;
7 use syntax::ptr::P;
8
9 use crate::utils::{match_def_path, match_type, span_lint_and_then};
10 use crate::utils::paths::*;
11
12 /// **What it does:** Checks for expressions that could be replaced by the question mark operator
13 ///
14 /// **Why is this bad?** Question mark usage is more idiomatic
15 ///
16 /// **Known problems:** None
17 ///
18 /// **Example:**
19 /// ```rust
20 /// if option.is_none() {
21 ///     return None;
22 /// }
23 /// ```
24 ///
25 /// Could be written:
26 ///
27 /// ```rust
28 /// option?;
29 /// ```
30 declare_clippy_lint!{
31     pub QUESTION_MARK,
32     style,
33     "checks for expressions that could be replaced by the question mark operator"
34 }
35
36 #[derive(Copy, Clone)]
37 pub struct QuestionMarkPass;
38
39 impl LintPass for QuestionMarkPass {
40     fn get_lints(&self) -> LintArray {
41         lint_array!(QUESTION_MARK)
42     }
43 }
44
45 impl QuestionMarkPass {
46     /// Check if the given expression on the given context matches the following structure:
47     ///
48     /// ```ignore
49     /// if option.is_none() {
50     ///    return None;
51     /// }
52     /// ```
53     ///
54     /// If it matches, it will suggest to use the question mark operator instead
55     fn check_is_none_and_early_return_none(cx: &LateContext, expr: &Expr) {
56         if_chain! {
57             if let ExprKind::If(ref if_expr, ref body, _) = expr.node;
58             if let ExprKind::MethodCall(ref segment, _, ref args) = if_expr.node;
59             if segment.ident.name == "is_none";
60             if Self::expression_returns_none(cx, body);
61             if let Some(subject) = args.get(0);
62             if Self::is_option(cx, subject);
63
64             then {
65                 span_lint_and_then(
66                     cx,
67                     QUESTION_MARK,
68                     expr.span,
69                     "this block may be rewritten with the `?` operator",
70                     |db| {
71                         let receiver_str = &Sugg::hir(cx, subject, "..");
72
73                         db.span_suggestion(
74                             expr.span,
75                             "replace_it_with",
76                             format!("{}?;", receiver_str),
77                         );
78                     }
79                 )
80             }
81         }
82     }
83
84     fn is_option(cx: &LateContext, expression: &Expr) -> bool {
85         let expr_ty = cx.tables.expr_ty(expression);
86
87         match_type(cx, expr_ty, &OPTION)
88     }
89
90     fn expression_returns_none(cx: &LateContext, expression: &Expr) -> bool {
91         match expression.node {
92             ExprKind::Block(ref block, _) => {
93                 if let Some(return_expression) = Self::return_expression(block) {
94                     return Self::expression_returns_none(cx, &return_expression);
95                 }
96
97                 false
98             },
99             ExprKind::Ret(Some(ref expr)) => {
100                 Self::expression_returns_none(cx, expr)
101             },
102             ExprKind::Path(ref qp) => {
103                 if let Def::VariantCtor(def_id, _) = cx.tables.qpath_def(qp, expression.hir_id) {
104                     return match_def_path(cx.tcx, def_id,  &OPTION_NONE);
105                 }
106
107                 false
108             },
109             _ => false
110         }
111     }
112
113     fn return_expression(block: &Block) -> Option<P<Expr>> {
114         // Check if last expression is a return statement. Then, return the expression
115         if_chain! {
116             if block.stmts.len() == 1;
117             if let Some(expr) = block.stmts.iter().last();
118             if let StmtKind::Semi(ref expr, _) = expr.node;
119             if let ExprKind::Ret(ref ret_expr) = expr.node;
120             if let &Some(ref ret_expr) = ret_expr;
121
122             then {
123                 return Some(ret_expr.clone());
124             }
125         }
126
127         // Check if the block has an implicit return expression
128         if let Some(ref ret_expr) = block.expr {
129             return Some(ret_expr.clone());
130         }
131
132         None
133     }
134 }
135
136 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for QuestionMarkPass {
137     fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr) {
138         Self::check_is_none_and_early_return_none(cx, expr);
139     }
140 }