Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Lint if let Some and early return in question_mark lint #5266

Merged
merged 3 commits into from
Mar 4, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 55 additions & 5 deletions clippy_lints/src/question_mark.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use if_chain::if_chain;
use rustc_errors::Applicability;
use rustc_hir::def::{DefKind, Res};
use rustc_hir::{def, Block, Expr, ExprKind, StmtKind};
use rustc_hir::{def, BindingAnnotation, Block, Expr, ExprKind, MatchSource, PatKind, StmtKind};
use rustc_lint::{LateContext, LateLintPass};
use rustc_session::{declare_lint_pass, declare_tool_lint};

use crate::utils::paths::{OPTION, OPTION_NONE};
use crate::utils::sugg::Sugg;
use crate::utils::{higher, match_def_path, match_type, span_lint_and_then, SpanlessEq};
use crate::utils::{
higher, match_def_path, match_qpath, match_type, snippet_with_applicability, span_lint_and_then, SpanlessEq,
};

declare_clippy_lint! {
/// **What it does:** Checks for expressions that could be replaced by the question mark operator.
Expand Down Expand Up @@ -55,7 +57,8 @@ impl QuestionMark {
if Self::is_option(cx, subject);

then {
let receiver_str = &Sugg::hir(cx, subject, "..");
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, subject.span, "..", &mut applicability);
let mut replacement: Option<String> = None;
if let Some(else_) = else_ {
if_chain! {
Expand All @@ -82,9 +85,9 @@ impl QuestionMark {
|db| {
db.span_suggestion(
expr.span,
"replace_it_with",
"replace it with",
replacement_str,
Applicability::MaybeIncorrect, // snippet
applicability,
);
}
)
Expand All @@ -93,6 +96,52 @@ impl QuestionMark {
}
}

fn check_if_let_some_and_early_return_none(cx: &LateContext<'_, '_>, expr: &Expr<'_>) {
if_chain! {
if let ExprKind::Match(subject, arms, source) = &expr.kind;
if *source == MatchSource::IfLetDesugar { contains_else_clause: true };
if Self::is_option(cx, subject);

if let PatKind::TupleStruct(path1, fields, None) = &arms[0].pat.kind;
if match_qpath(path1, &["Some"]);
if let PatKind::Binding(annot, _, bind, _) = &fields[0].kind;
let by_ref = matches!(annot, BindingAnnotation::Ref | BindingAnnotation::RefMut);

if let ExprKind::Block(block, None) = &arms[0].body.kind;
if block.stmts.is_empty();
if let Some(trailing_expr) = &block.expr;
if let ExprKind::Path(path) = &trailing_expr.kind;
if match_qpath(path, &[&bind.as_str()]);

if let PatKind::Wild = arms[1].pat.kind;
if Self::expression_returns_none(cx, arms[1].body);
then {
let mut applicability = Applicability::MachineApplicable;
let receiver_str = snippet_with_applicability(cx, subject.span, "..", &mut applicability);
let replacement = format!(
"{}{}?",
receiver_str,
if by_ref { ".as_ref()" } else { "" },
);

span_lint_and_then(
cx,
QUESTION_MARK,
expr.span,
"this if-let-else may be rewritten with the `?` operator",
|db| {
db.span_suggestion(
expr.span,
"replace it with",
replacement,
applicability,
);
}
)
}
}
}

fn moves_by_default(cx: &LateContext<'_, '_>, expression: &Expr<'_>) -> bool {
let expr_ty = cx.tables.expr_ty(expression);

Expand Down Expand Up @@ -158,5 +207,6 @@ impl QuestionMark {
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for QuestionMark {
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
Self::check_is_none_and_early_return_none(cx, expr);
Self::check_if_let_some_and_early_return_none(cx, expr);
}
}
7 changes: 1 addition & 6 deletions clippy_lints/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1698,12 +1698,7 @@ fn detect_absurd_comparison<'a, 'tcx>(
return None;
}

let normalized = normalize_comparison(op, lhs, rhs);
let (rel, normalized_lhs, normalized_rhs) = if let Some(val) = normalized {
val
} else {
return None;
};
let (rel, normalized_lhs, normalized_rhs) = normalize_comparison(op, lhs, rhs)?;

let lx = detect_extreme_expr(cx, normalized_lhs);
let rx = detect_extreme_expr(cx, normalized_rhs);
Expand Down
113 changes: 113 additions & 0 deletions tests/ui/question_mark.fixed
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
// run-rustfix
#![allow(unreachable_code)]

fn some_func(a: Option<u32>) -> Option<u32> {
a?;

a
}

fn some_other_func(a: Option<u32>) -> Option<u32> {
if a.is_none() {
return None;
} else {
return Some(0);
}
unreachable!()
}

pub enum SeemsOption<T> {
Some(T),
None,
}

impl<T> SeemsOption<T> {
pub fn is_none(&self) -> bool {
match *self {
SeemsOption::None => true,
SeemsOption::Some(_) => false,
}
}
}

fn returns_something_similar_to_option(a: SeemsOption<u32>) -> SeemsOption<u32> {
if a.is_none() {
return SeemsOption::None;
}

a
}

pub struct CopyStruct {
pub opt: Option<u32>,
}

impl CopyStruct {
#[rustfmt::skip]
pub fn func(&self) -> Option<u32> {
(self.opt)?;

self.opt?;

let _ = Some(self.opt?);

let _ = self.opt?;

self.opt
}
}

#[derive(Clone)]
pub struct MoveStruct {
pub opt: Option<Vec<u32>>,
}

impl MoveStruct {
pub fn ref_func(&self) -> Option<Vec<u32>> {
self.opt.as_ref()?;

self.opt.clone()
}

pub fn mov_func_reuse(self) -> Option<Vec<u32>> {
self.opt.as_ref()?;

self.opt
}

pub fn mov_func_no_use(self) -> Option<Vec<u32>> {
self.opt.as_ref()?;
Some(Vec::new())
}

pub fn if_let_ref_func(self) -> Option<Vec<u32>> {
let v: &Vec<_> = self.opt.as_ref()?;

Some(v.clone())
}

pub fn if_let_mov_func(self) -> Option<Vec<u32>> {
let v = self.opt?;

Some(v)
}
}

fn main() {
some_func(Some(42));
some_func(None);
some_other_func(Some(42));

let copy_struct = CopyStruct { opt: Some(54) };
copy_struct.func();

let move_struct = MoveStruct {
opt: Some(vec![42, 1337]),
};
move_struct.ref_func();
move_struct.clone().mov_func_reuse();
move_struct.mov_func_no_use();

let so = SeemsOption::Some(45);
returns_something_similar_to_option(so);
}
30 changes: 30 additions & 0 deletions tests/ui/question_mark.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
// run-rustfix
#![allow(unreachable_code)]

fn some_func(a: Option<u32>) -> Option<u32> {
if a.is_none() {
return None;
Expand Down Expand Up @@ -58,6 +61,12 @@ impl CopyStruct {
self.opt
};

let _ = if let Some(x) = self.opt {
x
} else {
return None;
};

self.opt
}
}
Expand Down Expand Up @@ -90,11 +99,32 @@ impl MoveStruct {
}
Some(Vec::new())
}

pub fn if_let_ref_func(self) -> Option<Vec<u32>> {
let v: &Vec<_> = if let Some(ref v) = self.opt {
v
} else {
return None;
};

Some(v.clone())
}

pub fn if_let_mov_func(self) -> Option<Vec<u32>> {
let v = if let Some(v) = self.opt {
v
} else {
return None;
};

Some(v)
}
}

fn main() {
some_func(Some(42));
some_func(None);
some_other_func(Some(42));

let copy_struct = CopyStruct { opt: Some(54) };
copy_struct.func();
Expand Down
63 changes: 48 additions & 15 deletions tests/ui/question_mark.stderr
Original file line number Diff line number Diff line change
@@ -1,63 +1,96 @@
error: this block may be rewritten with the `?` operator
--> $DIR/question_mark.rs:2:5
--> $DIR/question_mark.rs:5:5
|
LL | / if a.is_none() {
LL | | return None;
LL | | }
| |_____^ help: replace_it_with: `a?;`
| |_____^ help: replace it with: `a?;`
|
= note: `-D clippy::question-mark` implied by `-D warnings`

error: this block may be rewritten with the `?` operator
--> $DIR/question_mark.rs:47:9
--> $DIR/question_mark.rs:50:9
|
LL | / if (self.opt).is_none() {
LL | | return None;
LL | | }
| |_________^ help: replace_it_with: `(self.opt)?;`
| |_________^ help: replace it with: `(self.opt)?;`

error: this block may be rewritten with the `?` operator
--> $DIR/question_mark.rs:51:9
--> $DIR/question_mark.rs:54:9
|
LL | / if self.opt.is_none() {
LL | | return None
LL | | }
| |_________^ help: replace_it_with: `self.opt?;`
| |_________^ help: replace it with: `self.opt?;`

error: this block may be rewritten with the `?` operator
--> $DIR/question_mark.rs:55:17
--> $DIR/question_mark.rs:58:17
|
LL | let _ = if self.opt.is_none() {
| _________________^
LL | | return None;
LL | | } else {
LL | | self.opt
LL | | };
| |_________^ help: replace_it_with: `Some(self.opt?)`
| |_________^ help: replace it with: `Some(self.opt?)`

error: this if-let-else may be rewritten with the `?` operator
--> $DIR/question_mark.rs:64:17
|
LL | let _ = if let Some(x) = self.opt {
| _________________^
LL | | x
LL | | } else {
LL | | return None;
LL | | };
| |_________^ help: replace it with: `self.opt?`

error: this block may be rewritten with the `?` operator
--> $DIR/question_mark.rs:72:9
--> $DIR/question_mark.rs:81:9
|
LL | / if self.opt.is_none() {
LL | | return None;
LL | | }
| |_________^ help: replace_it_with: `self.opt.as_ref()?;`
| |_________^ help: replace it with: `self.opt.as_ref()?;`

error: this block may be rewritten with the `?` operator
--> $DIR/question_mark.rs:80:9
--> $DIR/question_mark.rs:89:9
|
LL | / if self.opt.is_none() {
LL | | return None;
LL | | }
| |_________^ help: replace_it_with: `self.opt.as_ref()?;`
| |_________^ help: replace it with: `self.opt.as_ref()?;`

error: this block may be rewritten with the `?` operator
--> $DIR/question_mark.rs:88:9
--> $DIR/question_mark.rs:97:9
|
LL | / if self.opt.is_none() {
LL | | return None;
LL | | }
| |_________^ help: replace_it_with: `self.opt.as_ref()?;`
| |_________^ help: replace it with: `self.opt.as_ref()?;`

error: this if-let-else may be rewritten with the `?` operator
--> $DIR/question_mark.rs:104:26
|
LL | let v: &Vec<_> = if let Some(ref v) = self.opt {
| __________________________^
LL | | v
LL | | } else {
LL | | return None;
LL | | };
| |_________^ help: replace it with: `self.opt.as_ref()?`

error: this if-let-else may be rewritten with the `?` operator
--> $DIR/question_mark.rs:114:17
|
LL | let v = if let Some(v) = self.opt {
| _________________^
LL | | v
LL | | } else {
LL | | return None;
LL | | };
| |_________^ help: replace it with: `self.opt?`

error: aborting due to 7 previous errors
error: aborting due to 10 previous errors