Skip to content

Commit 6f90f58

Browse files
authored
Merge pull request #2410 from gnieto/task/questionMarkIso
Question mark lint
2 parents ee8d328 + 05ed421 commit 6f90f58

File tree

4 files changed

+216
-0
lines changed

4 files changed

+216
-0
lines changed

clippy_lints/src/lib.rs

+2
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ pub mod doc;
8989
pub mod double_comparison;
9090
pub mod double_parens;
9191
pub mod drop_forget_ref;
92+
pub mod question_mark;
9293
pub mod else_if_without_else;
9394
pub mod empty_enum;
9495
pub mod entry;
@@ -371,6 +372,7 @@ pub fn register_plugins(reg: &mut rustc_plugin::Registry) {
371372
reg.register_late_lint_pass(box replace_consts::ReplaceConsts);
372373
reg.register_late_lint_pass(box types::UnitArg);
373374
reg.register_late_lint_pass(box double_comparison::DoubleComparisonPass);
375+
reg.register_late_lint_pass(box question_mark::QuestionMarkPass);
374376

375377
reg.register_lint_group("clippy_restrictions", vec![
376378
arithmetic::FLOAT_ARITHMETIC,

clippy_lints/src/question_mark.rs

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

tests/ui/question_mark.rs

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
fn some_func(a: Option<u32>) -> Option<u32> {
2+
if a.is_none() {
3+
return None
4+
}
5+
6+
a
7+
}
8+
9+
pub enum SeemsOption<T> {
10+
Some(T),
11+
None
12+
}
13+
14+
impl<T> SeemsOption<T> {
15+
pub fn is_none(&self) -> bool {
16+
match *self {
17+
SeemsOption::None => true,
18+
SeemsOption::Some(_) => false,
19+
}
20+
}
21+
}
22+
23+
fn returns_something_similar_to_option(a: SeemsOption<u32>) -> SeemsOption<u32> {
24+
if a.is_none() {
25+
return SeemsOption::None;
26+
}
27+
28+
a
29+
}
30+
31+
pub struct SomeStruct {
32+
pub opt: Option<u32>,
33+
}
34+
35+
impl SomeStruct {
36+
pub fn func(&self) -> Option<u32> {
37+
if (self.opt).is_none() {
38+
return None;
39+
}
40+
41+
self.opt
42+
}
43+
}
44+
45+
fn main() {
46+
some_func(Some(42));
47+
some_func(None);
48+
49+
let some_struct = SomeStruct { opt: Some(54) };
50+
some_struct.func();
51+
52+
let so = SeemsOption::Some(45);
53+
returns_something_similar_to_option(so);
54+
}

tests/ui/question_mark.stderr

+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
error: this block may be rewritten with the `?` operator
2+
--> $DIR/question_mark.rs:2:2
3+
|
4+
2 | if a.is_none() {
5+
| _____^
6+
3 | | return None
7+
4 | | }
8+
| |_____^ help: replace_it_with: `a?;`
9+
|
10+
= note: `-D question-mark` implied by `-D warnings`
11+
12+
error: this block may be rewritten with the `?` operator
13+
--> $DIR/question_mark.rs:37:3
14+
|
15+
37 | if (self.opt).is_none() {
16+
| _________^
17+
38 | | return None;
18+
39 | | }
19+
| |_________^ help: replace_it_with: `(self.opt)?;`
20+
21+
error: aborting due to 2 previous errors
22+

0 commit comments

Comments
 (0)