1 use clippy_utils
::diagnostics
::span_lint_and_then
;
2 use clippy_utils
::higher
::IfLetOrMatch
;
3 use clippy_utils
::msrvs
::{self, Msrv}
;
4 use clippy_utils
::peel_blocks
;
5 use clippy_utils
::source
::snippet_with_context
;
6 use clippy_utils
::ty
::is_type_diagnostic_item
;
7 use clippy_utils
::visitors
::{for_each_expr, Descend}
;
8 use if_chain
::if_chain
;
9 use rustc_data_structures
::fx
::FxHashSet
;
10 use rustc_errors
::Applicability
;
11 use rustc_hir
::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind}
;
12 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
13 use rustc_middle
::lint
::in_external_macro
;
14 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
15 use rustc_span
::symbol
::sym
;
17 use serde
::Deserialize
;
18 use std
::ops
::ControlFlow
;
20 declare_clippy_lint
! {
23 /// Warn of cases where `let...else` could be used
25 /// ### Why is this bad?
27 /// `let...else` provides a standard construct for this pattern
28 /// that people can easily recognize. It's also more compact.
33 /// # let w = Some(0);
34 /// let v = if let Some(v) = w { v } else { return };
40 /// # #![feature(let_else)]
42 /// # let w = Some(0);
43 /// let Some(v) = w else { return };
46 #[clippy::version = "1.67.0"]
49 "manual implementation of a let...else statement"
52 pub struct ManualLetElse
{
54 matches_behaviour
: MatchLintBehaviour
,
59 pub fn new(msrv
: Msrv
, matches_behaviour
: MatchLintBehaviour
) -> Self {
67 impl_lint_pass
!(ManualLetElse
=> [MANUAL_LET_ELSE
]);
69 impl<'tcx
> LateLintPass
<'tcx
> for ManualLetElse
{
70 fn check_stmt(&mut self, cx
: &LateContext
<'_
>, stmt
: &'tcx Stmt
<'tcx
>) {
71 let if_let_or_match
= if_chain
! {
72 if self.msrv
.meets(msrvs
::LET_ELSE
);
73 if !in_external_macro(cx
.sess(), stmt
.span
);
74 if let StmtKind
::Local(local
) = stmt
.kind
;
75 if let Some(init
) = local
.init
;
76 if local
.els
.is_none();
77 if local
.ty
.is_none();
78 if init
.span
.ctxt() == stmt
.span
.ctxt();
79 if let Some(if_let_or_match
) = IfLetOrMatch
::parse(cx
, init
);
87 match if_let_or_match
{
88 IfLetOrMatch
::IfLet(if_let_expr
, let_pat
, if_then
, if_else
) => if_chain
! {
89 if expr_is_simple_identity(let_pat
, if_then
);
90 if let Some(if_else
) = if_else
;
91 if expr_diverges(cx
, if_else
);
93 emit_manual_let_else(cx
, stmt
.span
, if_let_expr
, let_pat
, if_else
);
96 IfLetOrMatch
::Match(match_expr
, arms
, source
) => {
97 if self.matches_behaviour
== MatchLintBehaviour
::Never
{
100 if source
!= MatchSource
::Normal
{
103 // Any other number than two arms doesn't (neccessarily)
104 // have a trivial mapping to let else.
108 // Guards don't give us an easy mapping either
109 if arms
.iter().any(|arm
| arm
.guard
.is_some()) {
112 let check_types
= self.matches_behaviour
== MatchLintBehaviour
::WellKnownTypes
;
113 let diverging_arm_opt
= arms
116 .find(|(_
, arm
)| expr_diverges(cx
, arm
.body
) && pat_allowed_for_else(cx
, arm
.pat
, check_types
));
117 let Some((idx
, diverging_arm
)) = diverging_arm_opt
else { return; }
;
118 let pat_arm
= &arms
[1 - idx
];
119 if !expr_is_simple_identity(pat_arm
.pat
, pat_arm
.body
) {
123 emit_manual_let_else(cx
, stmt
.span
, match_expr
, pat_arm
.pat
, diverging_arm
.body
);
128 extract_msrv_attr
!(LateContext
);
131 fn emit_manual_let_else(cx
: &LateContext
<'_
>, span
: Span
, expr
: &Expr
<'_
>, pat
: &Pat
<'_
>, else_body
: &Expr
<'_
>) {
136 "this could be rewritten as `let...else`",
138 // This is far from perfect, for example there needs to be:
139 // * mut additions for the bindings
140 // * renamings of the bindings
141 // * unused binding collision detection with existing ones
142 // * putting patterns with at the top level | inside ()
143 // for this to be machine applicable.
144 let mut app
= Applicability
::HasPlaceholders
;
145 let (sn_pat
, _
) = snippet_with_context(cx
, pat
.span
, span
.ctxt(), "", &mut app
);
146 let (sn_expr
, _
) = snippet_with_context(cx
, expr
.span
, span
.ctxt(), "", &mut app
);
147 let (sn_else
, _
) = snippet_with_context(cx
, else_body
.span
, span
.ctxt(), "", &mut app
);
149 let else_bl
= if matches
!(else_body
.kind
, ExprKind
::Block(..)) {
152 format
!("{{ {sn_else} }}")
154 let sugg
= format
!("let {sn_pat} = {sn_expr} else {else_bl};");
155 diag
.span_suggestion(span
, "consider writing", sugg
, app
);
160 fn expr_diverges(cx
: &LateContext
<'_
>, expr
: &'_ Expr
<'_
>) -> bool
{
161 fn is_never(cx
: &LateContext
<'_
>, expr
: &'_ Expr
<'_
>) -> bool
{
162 if let Some(ty
) = cx
.typeck_results().expr_ty_opt(expr
) {
163 return ty
.is_never();
167 // We can't just call is_never on expr and be done, because the type system
168 // sometimes coerces the ! type to something different before we can get
169 // our hands on it. So instead, we do a manual search. We do fall back to
170 // is_never in some places when there is no better alternative.
171 for_each_expr(expr
, |ex
| {
173 ExprKind
::Continue(_
) | ExprKind
::Break(_
, _
) | ExprKind
::Ret(_
) => ControlFlow
::Break(()),
174 ExprKind
::Call(call
, _
) => {
175 if is_never(cx
, ex
) || is_never(cx
, call
) {
176 return ControlFlow
::Break(());
178 ControlFlow
::Continue(Descend
::Yes
)
180 ExprKind
::MethodCall(..) => {
181 if is_never(cx
, ex
) {
182 return ControlFlow
::Break(());
184 ControlFlow
::Continue(Descend
::Yes
)
186 ExprKind
::If(if_expr
, if_then
, if_else
) => {
187 let else_diverges
= if_else
.map_or(false, |ex
| expr_diverges(cx
, ex
));
188 let diverges
= expr_diverges(cx
, if_expr
) || (else_diverges
&& expr_diverges(cx
, if_then
));
190 return ControlFlow
::Break(());
192 ControlFlow
::Continue(Descend
::No
)
194 ExprKind
::Match(match_expr
, match_arms
, _
) => {
195 let diverges
= expr_diverges(cx
, match_expr
)
196 || match_arms
.iter().all(|arm
| {
197 let guard_diverges
= arm
.guard
.as_ref().map_or(false, |g
| expr_diverges(cx
, g
.body()));
198 guard_diverges
|| expr_diverges(cx
, arm
.body
)
201 return ControlFlow
::Break(());
203 ControlFlow
::Continue(Descend
::No
)
206 // Don't continue into loops or labeled blocks, as they are breakable,
207 // and we'd have to start checking labels.
208 ExprKind
::Block(_
, Some(_
)) | ExprKind
::Loop(..) => ControlFlow
::Continue(Descend
::No
),
211 _
=> ControlFlow
::Continue(Descend
::Yes
),
217 fn pat_allowed_for_else(cx
: &LateContext
<'_
>, pat
: &'_ Pat
<'_
>, check_types
: bool
) -> bool
{
218 // Check whether the pattern contains any bindings, as the
219 // binding might potentially be used in the body.
220 // TODO: only look for *used* bindings.
221 let mut has_bindings
= false;
222 pat
.each_binding_or_first(&mut |_
, _
, _
, _
| has_bindings
= true);
227 // If we shouldn't check the types, exit early.
232 // Check whether any possibly "unknown" patterns are included,
233 // because users might not know which values some enum has.
234 // Well-known enums are excepted, as we assume people know them.
235 // We do a deep check, to be able to disallow Err(En::Foo(_))
236 // for usage of the En::Foo variant, as we disallow En::Foo(_),
237 // but we allow Err(_).
238 let typeck_results
= cx
.typeck_results();
239 let mut has_disallowed
= false;
240 pat
.walk_always(|pat
| {
241 // Only do the check if the type is "spelled out" in the pattern
244 PatKind
::Struct(..) | PatKind
::TupleStruct(..) | PatKind
::Path(..)
248 let ty
= typeck_results
.pat_ty(pat
);
249 // Option and Result are allowed, everything else isn't.
250 if !(is_type_diagnostic_item(cx
, ty
, sym
::Option
) || is_type_diagnostic_item(cx
, ty
, sym
::Result
)) {
251 has_disallowed
= true;
257 /// Checks if the passed block is a simple identity referring to bindings created by the pattern
258 fn expr_is_simple_identity(pat
: &'_ Pat
<'_
>, expr
: &'_ Expr
<'_
>) -> bool
{
259 // We support patterns with multiple bindings and tuples, like:
260 // let ... = if let (Some(foo), bar) = g() { (foo, bar) } else { ... }
261 let peeled
= peel_blocks(expr
);
262 let paths
= match peeled
.kind
{
263 ExprKind
::Tup(exprs
) | ExprKind
::Array(exprs
) => exprs
,
264 ExprKind
::Path(_
) => std
::slice
::from_ref(peeled
),
267 let mut pat_bindings
= FxHashSet
::default();
268 pat
.each_binding_or_first(&mut |_ann
, _hir_id
, _sp
, ident
| {
269 pat_bindings
.insert(ident
);
271 if pat_bindings
.len() < paths
.len() {
276 if let ExprKind
::Path(QPath
::Resolved(_ty
, path
)) = path
.kind
;
277 if let [path_seg
] = path
.segments
;
279 if !pat_bindings
.remove(&path_seg
.ident
) {
290 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize)]
291 pub enum MatchLintBehaviour
{