Skip to content

Commit 0c7e034

Browse files
authored
Do not suggest using a if let chain if it is not supported (#15746)
This might be due to a low edition (< 2024) or too low a MSRV. In this case, we will suggest only `match`. Fixes #15744 changelog: [`unnecessary_unwrap`]: do not suggest using `if let` chains if this is not supported with the current edition or MSRV changelog:[`collapsible_if`]: Do not suggest using `if let` if this is not supported with the current edition or MSRV
2 parents e74d4bd + f107991 commit 0c7e034

File tree

8 files changed

+97
-23
lines changed

8 files changed

+97
-23
lines changed

book/src/lint_configuration.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -898,6 +898,7 @@ The minimum rust version that the project supports. Defaults to the `rust-versio
898898
* [`unchecked_time_subtraction`](https://rust-lang.github.io/rust-clippy/master/index.html#unchecked_time_subtraction)
899899
* [`uninlined_format_args`](https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args)
900900
* [`unnecessary_lazy_evaluations`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_lazy_evaluations)
901+
* [`unnecessary_unwrap`](https://rust-lang.github.io/rust-clippy/master/index.html#unnecessary_unwrap)
901902
* [`unnested_or_patterns`](https://rust-lang.github.io/rust-clippy/master/index.html#unnested_or_patterns)
902903
* [`unused_trait_names`](https://rust-lang.github.io/rust-clippy/master/index.html#unused_trait_names)
903904
* [`use_self`](https://rust-lang.github.io/rust-clippy/master/index.html#use_self)

clippy_config/src/conf.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -794,6 +794,7 @@ define_Conf! {
794794
unchecked_time_subtraction,
795795
uninlined_format_args,
796796
unnecessary_lazy_evaluations,
797+
unnecessary_unwrap,
797798
unnested_or_patterns,
798799
unused_trait_names,
799800
use_self,

clippy_lints/src/collapsible_if.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
use clippy_config::Conf;
22
use clippy_utils::diagnostics::span_lint_hir_and_then;
3-
use clippy_utils::msrvs::{self, Msrv};
3+
use clippy_utils::msrvs::Msrv;
44
use clippy_utils::source::{IntoSpan as _, SpanRangeExt, snippet, snippet_block_with_applicability};
5-
use clippy_utils::{span_contains_non_whitespace, sym, tokenize_with_text};
5+
use clippy_utils::{can_use_if_let_chains, span_contains_non_whitespace, sym, tokenize_with_text};
66
use rustc_ast::{BinOpKind, MetaItemInner};
77
use rustc_errors::Applicability;
88
use rustc_hir::{Block, Expr, ExprKind, StmtKind};
@@ -216,8 +216,7 @@ impl CollapsibleIf {
216216
}
217217

218218
fn eligible_condition(&self, cx: &LateContext<'_>, cond: &Expr<'_>) -> bool {
219-
!matches!(cond.kind, ExprKind::Let(..))
220-
|| (cx.tcx.sess.edition().at_least_rust_2024() && self.msrv.meets(cx, msrvs::LET_CHAINS))
219+
!matches!(cond.kind, ExprKind::Let(..)) || can_use_if_let_chains(cx, self.msrv)
221220
}
222221

223222
// Check that nothing significant can be found between the initial `{` of `inner_if` and

clippy_lints/src/lib.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -589,7 +589,7 @@ pub fn register_lint_passes(store: &mut rustc_lint::LintStore, conf: &'static Co
589589
store.register_late_pass(|_| Box::new(map_unit_fn::MapUnit));
590590
store.register_late_pass(|_| Box::new(inherent_impl::MultipleInherentImpl));
591591
store.register_late_pass(|_| Box::new(neg_cmp_op_on_partial_ord::NoNegCompOpForPartialOrd));
592-
store.register_late_pass(|_| Box::new(unwrap::Unwrap));
592+
store.register_late_pass(move |_| Box::new(unwrap::Unwrap::new(conf)));
593593
store.register_late_pass(move |_| Box::new(indexing_slicing::IndexingSlicing::new(conf)));
594594
store.register_late_pass(move |tcx| Box::new(non_copy_const::NonCopyConst::new(tcx, conf)));
595595
store.register_late_pass(|_| Box::new(redundant_clone::RedundantClone));

clippy_lints/src/unwrap.rs

Lines changed: 32 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
use clippy_config::Conf;
12
use clippy_utils::diagnostics::span_lint_hir_and_then;
2-
use clippy_utils::ty::is_type_diagnostic_item;
3+
use clippy_utils::msrvs::Msrv;
4+
use clippy_utils::ty::get_type_diagnostic_name;
35
use clippy_utils::usage::is_potentially_local_place;
4-
use clippy_utils::{higher, path_to_local, sym};
6+
use clippy_utils::{can_use_if_let_chains, higher, path_to_local, sym};
57
use rustc_errors::Applicability;
68
use rustc_hir::intravisit::{FnKind, Visitor, walk_expr, walk_fn};
79
use rustc_hir::{BinOpKind, Body, Expr, ExprKind, FnDecl, HirId, Node, UnOp};
@@ -10,7 +12,7 @@ use rustc_lint::{LateContext, LateLintPass};
1012
use rustc_middle::hir::nested_filter;
1113
use rustc_middle::mir::FakeReadCause;
1214
use rustc_middle::ty::{self, Ty, TyCtxt};
13-
use rustc_session::declare_lint_pass;
15+
use rustc_session::impl_lint_pass;
1416
use rustc_span::def_id::LocalDefId;
1517
use rustc_span::{Span, Symbol};
1618

@@ -72,10 +74,21 @@ declare_clippy_lint! {
7274
"checks for calls of `unwrap[_err]()` that will always fail"
7375
}
7476

77+
pub(crate) struct Unwrap {
78+
msrv: Msrv,
79+
}
80+
81+
impl Unwrap {
82+
pub fn new(conf: &'static Conf) -> Self {
83+
Self { msrv: conf.msrv }
84+
}
85+
}
86+
7587
/// Visitor that keeps track of which variables are unwrappable.
7688
struct UnwrappableVariablesVisitor<'a, 'tcx> {
7789
unwrappables: Vec<UnwrapInfo<'tcx>>,
7890
cx: &'a LateContext<'tcx>,
91+
msrv: Msrv,
7992
}
8093

8194
/// What kind of unwrappable this is.
@@ -133,12 +146,14 @@ fn collect_unwrap_info<'tcx>(
133146
invert: bool,
134147
is_entire_condition: bool,
135148
) -> Vec<UnwrapInfo<'tcx>> {
136-
fn is_relevant_option_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
137-
is_type_diagnostic_item(cx, ty, sym::Option) && matches!(method_name, sym::is_none | sym::is_some)
138-
}
139-
140-
fn is_relevant_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> bool {
141-
is_type_diagnostic_item(cx, ty, sym::Result) && matches!(method_name, sym::is_err | sym::is_ok)
149+
fn option_or_result_call(cx: &LateContext<'_>, ty: Ty<'_>, method_name: Symbol) -> Option<(UnwrappableKind, bool)> {
150+
match (get_type_diagnostic_name(cx, ty)?, method_name) {
151+
(sym::Option, sym::is_some) => Some((UnwrappableKind::Option, true)),
152+
(sym::Option, sym::is_none) => Some((UnwrappableKind::Option, false)),
153+
(sym::Result, sym::is_ok) => Some((UnwrappableKind::Result, true)),
154+
(sym::Result, sym::is_err) => Some((UnwrappableKind::Result, false)),
155+
_ => None,
156+
}
142157
}
143158

144159
match expr.kind {
@@ -157,15 +172,9 @@ fn collect_unwrap_info<'tcx>(
157172
if let Some(local_id) = path_to_local(receiver)
158173
&& let ty = cx.typeck_results().expr_ty(receiver)
159174
&& let name = method_name.ident.name
160-
&& (is_relevant_option_call(cx, ty, name) || is_relevant_result_call(cx, ty, name)) =>
175+
&& let Some((kind, unwrappable)) = option_or_result_call(cx, ty, name) =>
161176
{
162-
let unwrappable = matches!(name, sym::is_some | sym::is_ok);
163177
let safe_to_unwrap = unwrappable != invert;
164-
let kind = if is_type_diagnostic_item(cx, ty, sym::Option) {
165-
UnwrappableKind::Option
166-
} else {
167-
UnwrappableKind::Result
168-
};
169178

170179
vec![UnwrapInfo {
171180
local_id,
@@ -357,7 +366,11 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
357366
);
358367
} else {
359368
diag.span_label(unwrappable.check.span, "the check is happening here");
360-
diag.help("try using `if let` or `match`");
369+
if can_use_if_let_chains(self.cx, self.msrv) {
370+
diag.help("try using `if let` or `match`");
371+
} else {
372+
diag.help("try using `match`");
373+
}
361374
}
362375
},
363376
);
@@ -383,7 +396,7 @@ impl<'tcx> Visitor<'tcx> for UnwrappableVariablesVisitor<'_, 'tcx> {
383396
}
384397
}
385398

386-
declare_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]);
399+
impl_lint_pass!(Unwrap => [PANICKING_UNWRAP, UNNECESSARY_UNWRAP]);
387400

388401
impl<'tcx> LateLintPass<'tcx> for Unwrap {
389402
fn check_fn(
@@ -402,6 +415,7 @@ impl<'tcx> LateLintPass<'tcx> for Unwrap {
402415
let mut v = UnwrappableVariablesVisitor {
403416
unwrappables: Vec::new(),
404417
cx,
418+
msrv: self.msrv,
405419
};
406420

407421
walk_fn(&mut v, kind, decl, body.id(), fn_id);

clippy_utils/src/lib.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,7 @@ use visitors::{Visitable, for_each_unconsumed_temporary};
128128
use crate::ast_utils::unordered_over;
129129
use crate::consts::{ConstEvalCtxt, Constant, mir_to_const};
130130
use crate::higher::Range;
131+
use crate::msrvs::Msrv;
131132
use crate::ty::{adt_and_variant_of_res, can_partially_move_ty, expr_sig, is_copy, is_recursively_primitive_type};
132133
use crate::visitors::for_each_expr_without_closures;
133134

@@ -3649,3 +3650,8 @@ pub fn is_expr_async_block(expr: &Expr<'_>) -> bool {
36493650
})
36503651
)
36513652
}
3653+
3654+
/// Checks if the chosen edition and `msrv` allows using `if let` chains.
3655+
pub fn can_use_if_let_chains(cx: &LateContext<'_>, msrv: Msrv) -> bool {
3656+
cx.tcx.sess.edition().at_least_rust_2024() && msrv.meets(cx, msrvs::LET_CHAINS)
3657+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//@require-annotations-for-level: ERROR
2+
#![deny(clippy::unnecessary_unwrap)]
3+
4+
#[clippy::msrv = "1.85"]
5+
fn if_let_chains_unsupported(a: Option<u32>, b: Option<u32>) {
6+
if a.is_none() || b.is_none() {
7+
println!("a or b is not set");
8+
} else {
9+
println!("the value of a is {}", a.unwrap());
10+
//~^ unnecessary_unwrap
11+
//~| HELP: try using `match`
12+
}
13+
}
14+
15+
#[clippy::msrv = "1.88"]
16+
fn if_let_chains_supported(a: Option<u32>, b: Option<u32>) {
17+
if a.is_none() || b.is_none() {
18+
println!("a or b is not set");
19+
} else {
20+
println!("the value of a is {}", a.unwrap());
21+
//~^ unnecessary_unwrap
22+
//~| HELP: try using `if let` or `match`
23+
}
24+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
error: called `unwrap` on `a` after checking its variant with `is_none`
2+
--> tests/ui/checked_unwrap/if_let_chains.rs:9:42
3+
|
4+
LL | if a.is_none() || b.is_none() {
5+
| ----------- the check is happening here
6+
...
7+
LL | println!("the value of a is {}", a.unwrap());
8+
| ^^^^^^^^^^
9+
|
10+
= help: try using `match`
11+
note: the lint level is defined here
12+
--> tests/ui/checked_unwrap/if_let_chains.rs:2:9
13+
|
14+
LL | #![deny(clippy::unnecessary_unwrap)]
15+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^
16+
17+
error: called `unwrap` on `a` after checking its variant with `is_none`
18+
--> tests/ui/checked_unwrap/if_let_chains.rs:20:42
19+
|
20+
LL | if a.is_none() || b.is_none() {
21+
| ----------- the check is happening here
22+
...
23+
LL | println!("the value of a is {}", a.unwrap());
24+
| ^^^^^^^^^^
25+
|
26+
= help: try using `if let` or `match`
27+
28+
error: aborting due to 2 previous errors
29+

0 commit comments

Comments
 (0)