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
::{Descend, Visitable}
;
8 use if_chain
::if_chain
;
9 use rustc_data_structures
::fx
::FxHashSet
;
10 use rustc_errors
::Applicability
;
11 use rustc_hir
::intravisit
::{walk_expr, Visitor}
;
12 use rustc_hir
::{Expr, ExprKind, HirId, ItemId, Local, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind, Ty}
;
13 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
14 use rustc_middle
::lint
::in_external_macro
;
15 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
16 use rustc_span
::symbol
::sym
;
18 use serde
::Deserialize
;
19 use std
::ops
::ControlFlow
;
21 declare_clippy_lint
! {
24 /// Warn of cases where `let...else` could be used
26 /// ### Why is this bad?
28 /// `let...else` provides a standard construct for this pattern
29 /// that people can easily recognize. It's also more compact.
34 /// # let w = Some(0);
35 /// let v = if let Some(v) = w { v } else { return };
41 /// # #![feature(let_else)]
43 /// # let w = Some(0);
44 /// let Some(v) = w else { return };
47 #[clippy::version = "1.67.0"]
50 "manual implementation of a let...else statement"
53 pub struct ManualLetElse
{
55 matches_behaviour
: MatchLintBehaviour
,
60 pub fn new(msrv
: Msrv
, matches_behaviour
: MatchLintBehaviour
) -> Self {
68 impl_lint_pass
!(ManualLetElse
=> [MANUAL_LET_ELSE
]);
70 impl<'tcx
> LateLintPass
<'tcx
> for ManualLetElse
{
71 fn check_stmt(&mut self, cx
: &LateContext
<'_
>, stmt
: &'tcx Stmt
<'tcx
>) {
72 let if_let_or_match
= if_chain
! {
73 if self.msrv
.meets(msrvs
::LET_ELSE
);
74 if !in_external_macro(cx
.sess(), stmt
.span
);
75 if let StmtKind
::Local(local
) = stmt
.kind
;
76 if let Some(init
) = local
.init
;
77 if local
.els
.is_none();
78 if local
.ty
.is_none();
79 if init
.span
.ctxt() == stmt
.span
.ctxt();
80 if let Some(if_let_or_match
) = IfLetOrMatch
::parse(cx
, init
);
88 match if_let_or_match
{
89 IfLetOrMatch
::IfLet(if_let_expr
, let_pat
, if_then
, if_else
) => if_chain
! {
90 if expr_is_simple_identity(let_pat
, if_then
);
91 if let Some(if_else
) = if_else
;
92 if expr_diverges(cx
, if_else
);
94 emit_manual_let_else(cx
, stmt
.span
, if_let_expr
, let_pat
, if_else
);
97 IfLetOrMatch
::Match(match_expr
, arms
, source
) => {
98 if self.matches_behaviour
== MatchLintBehaviour
::Never
{
101 if source
!= MatchSource
::Normal
{
104 // Any other number than two arms doesn't (neccessarily)
105 // have a trivial mapping to let else.
109 // Guards don't give us an easy mapping either
110 if arms
.iter().any(|arm
| arm
.guard
.is_some()) {
113 let check_types
= self.matches_behaviour
== MatchLintBehaviour
::WellKnownTypes
;
114 let diverging_arm_opt
= arms
117 .find(|(_
, arm
)| expr_diverges(cx
, arm
.body
) && pat_allowed_for_else(cx
, arm
.pat
, check_types
));
118 let Some((idx
, diverging_arm
)) = diverging_arm_opt
else { return; }
;
119 // If the non-diverging arm is the first one, its pattern can be reused in a let/else statement.
120 // However, if it arrives in second position, its pattern may cover some cases already covered
121 // by the diverging one.
122 // TODO: accept the non-diverging arm as a second position if patterns are disjointed.
126 let pat_arm
= &arms
[1 - idx
];
127 if !expr_is_simple_identity(pat_arm
.pat
, pat_arm
.body
) {
131 emit_manual_let_else(cx
, stmt
.span
, match_expr
, pat_arm
.pat
, diverging_arm
.body
);
136 extract_msrv_attr
!(LateContext
);
139 fn emit_manual_let_else(cx
: &LateContext
<'_
>, span
: Span
, expr
: &Expr
<'_
>, pat
: &Pat
<'_
>, else_body
: &Expr
<'_
>) {
144 "this could be rewritten as `let...else`",
146 // This is far from perfect, for example there needs to be:
147 // * mut additions for the bindings
148 // * renamings of the bindings
149 // * unused binding collision detection with existing ones
150 // * putting patterns with at the top level | inside ()
151 // for this to be machine applicable.
152 let mut app
= Applicability
::HasPlaceholders
;
153 let (sn_pat
, _
) = snippet_with_context(cx
, pat
.span
, span
.ctxt(), "", &mut app
);
154 let (sn_expr
, _
) = snippet_with_context(cx
, expr
.span
, span
.ctxt(), "", &mut app
);
155 let (sn_else
, _
) = snippet_with_context(cx
, else_body
.span
, span
.ctxt(), "", &mut app
);
157 let else_bl
= if matches
!(else_body
.kind
, ExprKind
::Block(..)) {
160 format
!("{{ {sn_else} }}")
162 let sn_bl
= if matches
!(pat
.kind
, PatKind
::Or(..)) {
163 format
!("({sn_pat})")
167 let sugg
= format
!("let {sn_bl} = {sn_expr} else {else_bl};");
168 diag
.span_suggestion(span
, "consider writing", sugg
, app
);
173 /// Check whether an expression is divergent. May give false negatives.
174 fn expr_diverges(cx
: &LateContext
<'_
>, expr
: &Expr
<'_
>) -> bool
{
175 struct V
<'cx
, 'tcx
> {
176 cx
: &'cx LateContext
<'tcx
>,
177 res
: ControlFlow
<(), Descend
>,
179 impl<'tcx
> Visitor
<'tcx
> for V
<'_
, '_
> {
180 fn visit_expr(&mut self, e
: &'tcx Expr
<'tcx
>) {
181 fn is_never(cx
: &LateContext
<'_
>, expr
: &'_ Expr
<'_
>) -> bool
{
182 if let Some(ty
) = cx
.typeck_results().expr_ty_opt(expr
) {
183 return ty
.is_never();
188 if self.res
.is_break() {
192 // We can't just call is_never on expr and be done, because the type system
193 // sometimes coerces the ! type to something different before we can get
194 // our hands on it. So instead, we do a manual search. We do fall back to
195 // is_never in some places when there is no better alternative.
196 self.res
= match e
.kind
{
197 ExprKind
::Continue(_
) | ExprKind
::Break(_
, _
) | ExprKind
::Ret(_
) => ControlFlow
::Break(()),
198 ExprKind
::Call(call
, _
) => {
199 if is_never(self.cx
, e
) || is_never(self.cx
, call
) {
200 ControlFlow
::Break(())
202 ControlFlow
::Continue(Descend
::Yes
)
205 ExprKind
::MethodCall(..) => {
206 if is_never(self.cx
, e
) {
207 ControlFlow
::Break(())
209 ControlFlow
::Continue(Descend
::Yes
)
212 ExprKind
::If(if_expr
, if_then
, if_else
) => {
213 let else_diverges
= if_else
.map_or(false, |ex
| expr_diverges(self.cx
, ex
));
215 expr_diverges(self.cx
, if_expr
) || (else_diverges
&& expr_diverges(self.cx
, if_then
));
217 ControlFlow
::Break(())
219 ControlFlow
::Continue(Descend
::No
)
222 ExprKind
::Match(match_expr
, match_arms
, _
) => {
223 let diverges
= expr_diverges(self.cx
, match_expr
)
224 || match_arms
.iter().all(|arm
| {
225 let guard_diverges
= arm
.guard
.as_ref().map_or(false, |g
| expr_diverges(self.cx
, g
.body()));
226 guard_diverges
|| expr_diverges(self.cx
, arm
.body
)
229 ControlFlow
::Break(())
231 ControlFlow
::Continue(Descend
::No
)
235 // Don't continue into loops or labeled blocks, as they are breakable,
236 // and we'd have to start checking labels.
237 ExprKind
::Block(_
, Some(_
)) | ExprKind
::Loop(..) => ControlFlow
::Continue(Descend
::No
),
240 _
=> ControlFlow
::Continue(Descend
::Yes
),
242 if let ControlFlow
::Continue(Descend
::Yes
) = self.res
{
247 fn visit_local(&mut self, local
: &'tcx Local
<'_
>) {
248 // Don't visit the else block of a let/else statement as it will not make
249 // the statement divergent even though the else block is divergent.
250 if let Some(init
) = local
.init
{
251 self.visit_expr(init
);
255 // Avoid unnecessary `walk_*` calls.
256 fn visit_ty(&mut self, _
: &'tcx Ty
<'tcx
>) {}
257 fn visit_pat(&mut self, _
: &'tcx Pat
<'tcx
>) {}
258 fn visit_qpath(&mut self, _
: &'tcx QPath
<'tcx
>, _
: HirId
, _
: Span
) {}
259 // Avoid monomorphising all `visit_*` functions.
260 fn visit_nested_item(&mut self, _
: ItemId
) {}
265 res
: ControlFlow
::Continue(Descend
::Yes
),
271 fn pat_allowed_for_else(cx
: &LateContext
<'_
>, pat
: &'_ Pat
<'_
>, check_types
: bool
) -> bool
{
272 // Check whether the pattern contains any bindings, as the
273 // binding might potentially be used in the body.
274 // TODO: only look for *used* bindings.
275 let mut has_bindings
= false;
276 pat
.each_binding_or_first(&mut |_
, _
, _
, _
| has_bindings
= true);
281 // If we shouldn't check the types, exit early.
286 // Check whether any possibly "unknown" patterns are included,
287 // because users might not know which values some enum has.
288 // Well-known enums are excepted, as we assume people know them.
289 // We do a deep check, to be able to disallow Err(En::Foo(_))
290 // for usage of the En::Foo variant, as we disallow En::Foo(_),
291 // but we allow Err(_).
292 let typeck_results
= cx
.typeck_results();
293 let mut has_disallowed
= false;
294 pat
.walk_always(|pat
| {
295 // Only do the check if the type is "spelled out" in the pattern
298 PatKind
::Struct(..) | PatKind
::TupleStruct(..) | PatKind
::Path(..)
302 let ty
= typeck_results
.pat_ty(pat
);
303 // Option and Result are allowed, everything else isn't.
304 if !(is_type_diagnostic_item(cx
, ty
, sym
::Option
) || is_type_diagnostic_item(cx
, ty
, sym
::Result
)) {
305 has_disallowed
= true;
311 /// Checks if the passed block is a simple identity referring to bindings created by the pattern
312 fn expr_is_simple_identity(pat
: &'_ Pat
<'_
>, expr
: &'_ Expr
<'_
>) -> bool
{
313 // We support patterns with multiple bindings and tuples, like:
314 // let ... = if let (Some(foo), bar) = g() { (foo, bar) } else { ... }
315 let peeled
= peel_blocks(expr
);
316 let paths
= match peeled
.kind
{
317 ExprKind
::Tup(exprs
) | ExprKind
::Array(exprs
) => exprs
,
318 ExprKind
::Path(_
) => std
::slice
::from_ref(peeled
),
321 let mut pat_bindings
= FxHashSet
::default();
322 pat
.each_binding_or_first(&mut |_ann
, _hir_id
, _sp
, ident
| {
323 pat_bindings
.insert(ident
);
325 if pat_bindings
.len() < paths
.len() {
330 if let ExprKind
::Path(QPath
::Resolved(_ty
, path
)) = path
.kind
;
331 if let [path_seg
] = path
.segments
;
333 if !pat_bindings
.remove(&path_seg
.ident
) {
344 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Deserialize)]
345 pub enum MatchLintBehaviour
{