Skip to content

Commit d614470

Browse files
committed
More suggestions from flip1995
1 parent 422d9f8 commit d614470

File tree

4 files changed

+32
-39
lines changed

4 files changed

+32
-39
lines changed

clippy_lints/src/option_if_let_else.rs

Lines changed: 21 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@ use rustc_hir::intravisit::{NestedVisitorMap, Visitor};
77
use rustc_hir::*;
88
use rustc_lint::{LateContext, LateLintPass};
99
use rustc_middle::hir::map::Map;
10-
use rustc_middle::ty::TyCtxt;
1110
use rustc_session::{declare_lint_pass, declare_tool_lint};
1211

12+
use std::marker::PhantomData;
13+
1314
declare_clippy_lint! {
1415
/// **What it does:**
1516
/// Lints usage of `if let Some(v) = ... { y } else { x }` which is more
@@ -28,6 +29,11 @@ declare_clippy_lint! {
2829
/// an expensive function, then it would be more efficient to use
2930
/// `Option::map_or_else`, but this lint would suggest `Option::map_or`.
3031
///
32+
/// Also, this lint uses a deliberately conservative metric for checking
33+
/// if the inside of either body contains breaks or continues which will
34+
/// cause it to not suggest a fix if either block contains a loop with
35+
/// continues or breaks contained within the loop.
36+
///
3137
/// **Example:**
3238
///
3339
/// ```rust
@@ -86,25 +92,21 @@ struct OptionIfLetElseOccurence {
8692
}
8793

8894
struct ReturnBreakContinueVisitor<'tcx> {
89-
tcx: TyCtxt<'tcx>,
9095
seen_return_break_continue: bool,
96+
phantom_data: PhantomData<&'tcx bool>,
9197
}
9298
impl<'tcx> ReturnBreakContinueVisitor<'tcx> {
93-
fn new(tcx: TyCtxt<'tcx>) -> ReturnBreakContinueVisitor<'tcx> {
99+
fn new() -> ReturnBreakContinueVisitor<'tcx> {
94100
ReturnBreakContinueVisitor {
95101
seen_return_break_continue: false,
96-
tcx,
102+
phantom_data: PhantomData,
97103
}
98104
}
99-
100-
fn seen_return_break_continue(&self) -> bool {
101-
self.seen_return_break_continue
102-
}
103105
}
104106
impl<'tcx> Visitor<'tcx> for ReturnBreakContinueVisitor<'tcx> {
105107
type Map = Map<'tcx>;
106108
fn nested_visit_map(&mut self) -> NestedVisitorMap<Self::Map> {
107-
NestedVisitorMap::OnlyBodies(self.tcx.hir())
109+
NestedVisitorMap::None
108110
}
109111

110112
fn visit_expr(&mut self, ex: &'tcx Expr<'tcx>) {
@@ -116,22 +118,20 @@ impl<'tcx> Visitor<'tcx> for ReturnBreakContinueVisitor<'tcx> {
116118
ExprKind::Ret(..) | ExprKind::Break(..) | ExprKind::Continue(..) => {
117119
self.seen_return_break_continue = true;
118120
},
119-
// Do nothing if encountering a loop, because internal breaks and
120-
// continues shouldn't be flagged
121-
ExprKind::Loop(..) => {},
121+
// Something special could be done here to handle while or for loop
122+
// desugaring, as this will detect a break if there's a while loop
123+
// or a for loop inside the expression.
122124
_ => {
123125
rustc_hir::intravisit::walk_expr(self, ex);
124126
},
125127
}
126128
}
127129
}
128130

129-
fn contains_return_break_continue<'tcx>(cx: &LateContext<'_, 'tcx>, statements: &'tcx [Stmt<'tcx>]) -> bool {
130-
let mut recursive_visitor: ReturnBreakContinueVisitor<'tcx> = ReturnBreakContinueVisitor::new(cx.tcx);
131-
for statement in statements {
132-
recursive_visitor.visit_stmt(statement);
133-
}
134-
recursive_visitor.seen_return_break_continue()
131+
fn contains_return_break_continue<'tcx>(expression: &'tcx Expr<'tcx>) -> bool {
132+
let mut recursive_visitor: ReturnBreakContinueVisitor<'tcx> = ReturnBreakContinueVisitor::new();
133+
recursive_visitor.visit_expr(expression);
134+
recursive_visitor.seen_return_break_continue
135135
}
136136

137137
/// If this expression is the option if let/else construct we're detecting, then
@@ -146,16 +146,14 @@ fn detect_option_if_let_else<'a>(cx: &LateContext<'_, 'a>, expr: &'a Expr<'a>) -
146146
if !is_result_ok(cx, let_body); // Don't lint on Result::ok because a different lint does it already
147147
if let PatKind::TupleStruct(_, &[inner_pat], _) = &arms[0].pat.kind;
148148
if let PatKind::Binding(_, _, id, _) = &inner_pat.kind;
149+
if !contains_return_break_continue(arms[0].body);
150+
if !contains_return_break_continue(arms[1].body);
149151
then {
150152
let some_body = if let ExprKind::Block(Block { stmts: statements, expr: Some(expr), .. }, _)
151153
= &arms[0].body.kind {
152154
if let &[] = &statements {
153155
expr
154156
} else {
155-
// Don't trigger if there is a return, break, or continue inside
156-
if contains_return_break_continue(cx, statements) {
157-
return None;
158-
}
159157
&arms[0].body
160158
}
161159
} else {
@@ -166,9 +164,6 @@ fn detect_option_if_let_else<'a>(cx: &LateContext<'_, 'a>, expr: &'a Expr<'a>) -
166164
if let &[] = &statements {
167165
(expr, "map_or")
168166
} else {
169-
if contains_return_break_continue(cx, statements) {
170-
return None;
171-
}
172167
(&arms[1].body, "map_or_else")
173168
}
174169
} else {
@@ -179,7 +174,7 @@ fn detect_option_if_let_else<'a>(cx: &LateContext<'_, 'a>, expr: &'a Expr<'a>) -
179174
option: format!("{}", Sugg::hir(cx, let_body, "..")),
180175
method_sugg: format!("{}", method_sugg),
181176
some_expr: format!("|{}| {}", capture_name, Sugg::hir(cx, some_body, "..")),
182-
none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "||" }, Sugg::hir(cx, none_body, ".."))
177+
none_expr: format!("{}{}", if method_sugg == "map_or" { "" } else { "|| " }, Sugg::hir(cx, none_body, ".."))
183178
})
184179
} else {
185180
None

tests/ui/option_if_let_else.fixed

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ fn longer_body(arg: Option<u32>) -> u32 {
1313
}
1414

1515
fn test_map_or_else(arg: Option<u32>) {
16-
let _ = arg.map_or_else(||{
16+
let _ = arg.map_or_else(|| {
1717
let mut y = 1;
18-
for _ in 0..10 {
19-
y = (y + 2 / y) / 2;
20-
}
18+
y = (y + 2 / y) / 2;
19+
y = (y + 2 / y) / 2;
2120
y
2221
}, |x| x * x * x * x);
2322
}

tests/ui/option_if_let_else.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,8 @@ fn test_map_or_else(arg: Option<u32>) {
2323
x * x * x * x
2424
} else {
2525
let mut y = 1;
26-
for _ in 0..10 {
27-
y = (y + 2 / y) / 2;
28-
}
26+
y = (y + 2 / y) / 2;
27+
y = (y + 2 / y) / 2;
2928
y
3029
};
3130
}

tests/ui/option_if_let_else.stderr

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,16 @@ LL | | };
4444
|
4545
help: try
4646
|
47-
LL | let _ = arg.map_or_else(||{
47+
LL | let _ = arg.map_or_else(|| {
4848
LL | let mut y = 1;
49-
LL | for _ in 0..10 {
50-
LL | y = (y + 2 / y) / 2;
51-
LL | }
49+
LL | y = (y + 2 / y) / 2;
50+
LL | y = (y + 2 / y) / 2;
5251
LL | y
53-
...
52+
LL | }, |x| x * x * x * x);
53+
|
5454

5555
error: use Option::map_or instead of an if let/else
56-
--> $DIR/option_if_let_else.rs:52:13
56+
--> $DIR/option_if_let_else.rs:51:13
5757
|
5858
LL | let _ = if let Some(x) = optional { x + 2 } else { 5 };
5959
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ help: try: `optional.map_or(5, |x| x + 2)`

0 commit comments

Comments
 (0)