|
1 | 1 | use clippy_utils::diagnostics::span_lint; |
2 | 2 | use clippy_utils::higher::IfLetOrMatch; |
| 3 | +use clippy_utils::ty::is_type_diagnostic_item; |
3 | 4 | use clippy_utils::visitors::{for_each_expr, Descend}; |
4 | 5 | use clippy_utils::{meets_msrv, msrvs, peel_blocks}; |
5 | 6 | use if_chain::if_chain; |
6 | 7 | use rustc_data_structures::fx::FxHashSet; |
7 | | -use rustc_hir::{Expr, ExprKind, MatchSource, Pat, QPath, Stmt, StmtKind}; |
| 8 | +use rustc_hir::{Expr, ExprKind, MatchSource, Pat, PatKind, QPath, Stmt, StmtKind}; |
8 | 9 | use rustc_lint::{LateContext, LateLintPass, LintContext}; |
9 | 10 | use rustc_middle::lint::in_external_macro; |
10 | 11 | use rustc_semver::RustcVersion; |
11 | 12 | use rustc_session::{declare_tool_lint, impl_lint_pass}; |
| 13 | +use rustc_span::symbol::sym; |
12 | 14 | use rustc_span::Span; |
13 | 15 | use std::ops::ControlFlow; |
14 | 16 |
|
@@ -108,7 +110,7 @@ impl<'tcx> LateLintPass<'tcx> for ManualLetElse { |
108 | 110 | } |
109 | 111 | if expr_is_simple_identity(arm.pat, arm.body) { |
110 | 112 | found_identity_arm = true; |
111 | | - } else if expr_diverges(cx, arm.body) && pat_has_no_bindings(arm.pat) { |
| 113 | + } else if expr_diverges(cx, arm.body) && pat_allowed_for_else(cx, arm.pat) { |
112 | 114 | found_diverging_arm = true; |
113 | 115 | } |
114 | 116 | } |
@@ -194,10 +196,39 @@ fn from_different_macros(span_a: Span, span_b: Span) -> bool { |
194 | 196 | data_for_comparison(span_a) != data_for_comparison(span_b) |
195 | 197 | } |
196 | 198 |
|
197 | | -fn pat_has_no_bindings(pat: &'_ Pat<'_>) -> bool { |
198 | | - let mut has_no_bindings = true; |
199 | | - pat.each_binding_or_first(&mut |_, _, _, _| has_no_bindings = false); |
200 | | - has_no_bindings |
| 199 | +fn pat_allowed_for_else(cx: &LateContext<'_>, pat: &'_ Pat<'_>) -> bool { |
| 200 | + // Check whether the pattern contains any bindings, as the |
| 201 | + // binding might potentially be used in the body. |
| 202 | + // TODO: only look for *used* bindings. |
| 203 | + let mut has_bindings = false; |
| 204 | + pat.each_binding_or_first(&mut |_, _, _, _| has_bindings = true); |
| 205 | + if has_bindings { |
| 206 | + return false; |
| 207 | + } |
| 208 | + |
| 209 | + // Check whether any possibly "unknown" patterns are included, |
| 210 | + // because users might not know which values some enum has. |
| 211 | + // Well-known enums are excepted, as we assume people know them. |
| 212 | + // We do a deep check, to be able to disallow Err(En::Foo(_)) |
| 213 | + // for usage of the En::Foo variant, as we disallow En::Foo(_), |
| 214 | + // but we allow Err(_). |
| 215 | + let typeck_results = cx.typeck_results(); |
| 216 | + let mut has_disallowed = false; |
| 217 | + pat.walk_always(|pat| { |
| 218 | + // Only do the check if the type is "spelled out" in the pattern |
| 219 | + if !matches!( |
| 220 | + pat.kind, |
| 221 | + PatKind::Struct(..) | PatKind::TupleStruct(..) | PatKind::Path(..) |
| 222 | + ) { |
| 223 | + return; |
| 224 | + }; |
| 225 | + let ty = typeck_results.pat_ty(pat); |
| 226 | + // Option and Result are allowed, everything else isn't. |
| 227 | + if !(is_type_diagnostic_item(cx, ty, sym::Option) || is_type_diagnostic_item(cx, ty, sym::Result)) { |
| 228 | + has_disallowed = true; |
| 229 | + } |
| 230 | + }); |
| 231 | + !has_disallowed |
201 | 232 | } |
202 | 233 |
|
203 | 234 | /// Checks if the passed block is a simple identity referring to bindings created by the pattern |
|
0 commit comments