2 mod infallible_destructuring_match
;
9 mod match_like_matches
;
10 mod match_on_vec_items
;
13 mod match_single_binding
;
14 mod match_str_case_mismatch
;
16 mod match_wild_err_arm
;
19 mod redundant_pattern_match
;
20 mod rest_pat_in_fully_bound_struct
;
21 mod significant_drop_in_scrutinee
;
26 use clippy_utils
::msrvs
::{self, Msrv}
;
27 use clippy_utils
::source
::{snippet_opt, walk_span_to_context}
;
28 use clippy_utils
::{higher, in_constant, is_span_match}
;
29 use rustc_hir
::{Arm, Expr, ExprKind, Local, MatchSource, Pat}
;
30 use rustc_lexer
::{tokenize, TokenKind}
;
31 use rustc_lint
::{LateContext, LateLintPass, LintContext}
;
32 use rustc_middle
::lint
::in_external_macro
;
33 use rustc_session
::{declare_tool_lint, impl_lint_pass}
;
34 use rustc_span
::{Span, SpanData, SyntaxContext}
;
36 declare_clippy_lint
! {
38 /// Checks for matches with a single arm where an `if let`
39 /// will usually suffice.
41 /// ### Why is this bad?
42 /// Just readability – `if let` nests less than a `match`.
46 /// # fn bar(stool: &str) {}
47 /// # let x = Some("abc");
49 /// Some(ref foo) => bar(foo),
56 /// # fn bar(stool: &str) {}
57 /// # let x = Some("abc");
58 /// if let Some(ref foo) = x {
62 #[clippy::version = "pre 1.29.0"]
65 "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
68 declare_clippy_lint
! {
70 /// Checks for matches with two arms where an `if let else` will
73 /// ### Why is this bad?
74 /// Just readability – `if let` nests less than a `match`.
76 /// ### Known problems
77 /// Personal style preferences may differ.
83 /// # fn bar(foo: &usize) {}
84 /// # let other_ref: usize = 1;
85 /// # let x: Option<&usize> = Some(&1);
87 /// Some(ref foo) => bar(foo),
88 /// _ => bar(&other_ref),
92 /// Using `if let` with `else`:
95 /// # fn bar(foo: &usize) {}
96 /// # let other_ref: usize = 1;
97 /// # let x: Option<&usize> = Some(&1);
98 /// if let Some(ref foo) = x {
104 #[clippy::version = "pre 1.29.0"]
105 pub SINGLE_MATCH_ELSE
,
107 "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
110 declare_clippy_lint
! {
112 /// Checks for matches where all arms match a reference,
113 /// suggesting to remove the reference and deref the matched expression
114 /// instead. It also checks for `if let &foo = bar` blocks.
116 /// ### Why is this bad?
117 /// It just makes the code less readable. That reference
118 /// destructuring adds nothing to the code.
123 /// &A(ref y) => foo(y),
132 /// A(ref y) => foo(y),
137 #[clippy::version = "pre 1.29.0"]
140 "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
143 declare_clippy_lint
! {
145 /// Checks for matches where match expression is a `bool`. It
146 /// suggests to replace the expression with an `if...else` block.
148 /// ### Why is this bad?
149 /// It makes the code less readable.
155 /// let condition: bool = true;
156 /// match condition {
161 /// Use if/else instead:
165 /// let condition: bool = true;
172 #[clippy::version = "pre 1.29.0"]
175 "a `match` on a boolean expression instead of an `if..else` block"
178 declare_clippy_lint
! {
180 /// Checks for overlapping match arms.
182 /// ### Why is this bad?
183 /// It is likely to be an error and if not, makes the code
190 /// 1..=10 => println!("1 ... 10"),
191 /// 5..=15 => println!("5 ... 15"),
195 #[clippy::version = "pre 1.29.0"]
196 pub MATCH_OVERLAPPING_ARM
,
198 "a `match` with overlapping arms"
201 declare_clippy_lint
! {
203 /// Checks for arm which matches all errors with `Err(_)`
204 /// and take drastic actions like `panic!`.
206 /// ### Why is this bad?
207 /// It is generally a bad practice, similar to
208 /// catching all exceptions in java with `catch(Exception)`
212 /// let x: Result<i32, &str> = Ok(3);
214 /// Ok(_) => println!("ok"),
215 /// Err(_) => panic!("err"),
218 #[clippy::version = "pre 1.29.0"]
219 pub MATCH_WILD_ERR_ARM
,
221 "a `match` with `Err(_)` arm and take drastic actions"
224 declare_clippy_lint
! {
226 /// Checks for match which is used to add a reference to an
229 /// ### Why is this bad?
230 /// Using `as_ref()` or `as_mut()` instead is shorter.
234 /// let x: Option<()> = None;
236 /// let r: Option<&()> = match x {
238 /// Some(ref v) => Some(v),
244 /// let x: Option<()> = None;
246 /// let r: Option<&()> = x.as_ref();
248 #[clippy::version = "pre 1.29.0"]
251 "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
254 declare_clippy_lint
! {
256 /// Checks for wildcard enum matches using `_`.
258 /// ### Why is this bad?
259 /// New enum variants added by library updates can be missed.
261 /// ### Known problems
262 /// Suggested replacements may be incorrect if guards exhaustively cover some
263 /// variants, and also may not use correct path to enum if it's not present in the current scope.
267 /// # enum Foo { A(usize), B(usize) }
268 /// # let x = Foo::B(1);
277 /// # enum Foo { A(usize), B(usize) }
278 /// # let x = Foo::B(1);
284 #[clippy::version = "1.34.0"]
285 pub WILDCARD_ENUM_MATCH_ARM
,
287 "a wildcard enum match arm using `_`"
290 declare_clippy_lint
! {
292 /// Checks for wildcard enum matches for a single variant.
294 /// ### Why is this bad?
295 /// New enum variants added by library updates can be missed.
297 /// ### Known problems
298 /// Suggested replacements may not use correct path to enum
299 /// if it's not present in the current scope.
303 /// # enum Foo { A, B, C }
304 /// # let x = Foo::B;
314 /// # enum Foo { A, B, C }
315 /// # let x = Foo::B;
322 #[clippy::version = "1.45.0"]
323 pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS
,
325 "a wildcard enum match for a single variant"
328 declare_clippy_lint
! {
330 /// Checks for wildcard pattern used with others patterns in same match arm.
332 /// ### Why is this bad?
333 /// Wildcard pattern already covers any other pattern as it will match anyway.
334 /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
353 #[clippy::version = "1.42.0"]
354 pub WILDCARD_IN_OR_PATTERNS
,
356 "a wildcard pattern used with others patterns in same match arm"
359 declare_clippy_lint
! {
361 /// Checks for matches being used to destructure a single-variant enum
362 /// or tuple struct where a `let` will suffice.
364 /// ### Why is this bad?
365 /// Just readability – `let` doesn't nest, whereas a `match` does.
373 /// let wrapper = Wrapper::Data(42);
375 /// let data = match wrapper {
376 /// Wrapper::Data(i) => i,
380 /// The correct use would be:
386 /// let wrapper = Wrapper::Data(42);
387 /// let Wrapper::Data(data) = wrapper;
389 #[clippy::version = "pre 1.29.0"]
390 pub INFALLIBLE_DESTRUCTURING_MATCH
,
392 "a `match` statement with a single infallible arm instead of a `let`"
395 declare_clippy_lint
! {
397 /// Checks for useless match that binds to only one value.
399 /// ### Why is this bad?
400 /// Readability and needless complexity.
402 /// ### Known problems
403 /// Suggested replacements may be incorrect when `match`
404 /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
421 /// let (c, d) = (a, b);
423 #[clippy::version = "1.43.0"]
424 pub MATCH_SINGLE_BINDING
,
426 "a match with a single binding instead of using `let` statement"
429 declare_clippy_lint
! {
431 /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
433 /// ### Why is this bad?
434 /// Correctness and readability. It's like having a wildcard pattern after
435 /// matching all enum variants explicitly.
439 /// # struct A { a: i32 }
440 /// let a = A { a: 5 };
443 /// A { a: 5, .. } => {},
450 /// # struct A { a: i32 }
451 /// # let a = A { a: 5 };
453 /// A { a: 5 } => {},
457 #[clippy::version = "1.43.0"]
458 pub REST_PAT_IN_FULLY_BOUND_STRUCTS
,
460 "a match on a struct that binds all fields but still uses the wildcard pattern"
463 declare_clippy_lint
! {
465 /// Lint for redundant pattern matching over `Result`, `Option`,
466 /// `std::task::Poll` or `std::net::IpAddr`
468 /// ### Why is this bad?
469 /// It's more concise and clear to just use the proper
472 /// ### Known problems
473 /// This will change the drop order for the matched type. Both `if let` and
474 /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
475 /// value before entering the block. For most types this change will not matter, but for a few
476 /// types this will not be an acceptable change (e.g. locks). See the
477 /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
482 /// # use std::task::Poll;
483 /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
484 /// if let Ok(_) = Ok::<i32, i32>(42) {}
485 /// if let Err(_) = Err::<i32, i32>(42) {}
486 /// if let None = None::<()> {}
487 /// if let Some(_) = Some(42) {}
488 /// if let Poll::Pending = Poll::Pending::<()> {}
489 /// if let Poll::Ready(_) = Poll::Ready(42) {}
490 /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
491 /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
492 /// match Ok::<i32, i32>(42) {
498 /// The more idiomatic use would be:
501 /// # use std::task::Poll;
502 /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
503 /// if Ok::<i32, i32>(42).is_ok() {}
504 /// if Err::<i32, i32>(42).is_err() {}
505 /// if None::<()>.is_none() {}
506 /// if Some(42).is_some() {}
507 /// if Poll::Pending::<()>.is_pending() {}
508 /// if Poll::Ready(42).is_ready() {}
509 /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
510 /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
511 /// Ok::<i32, i32>(42).is_ok();
513 #[clippy::version = "1.31.0"]
514 pub REDUNDANT_PATTERN_MATCHING
,
516 "use the proper utility function avoiding an `if let`"
519 declare_clippy_lint
! {
521 /// Checks for `match` or `if let` expressions producing a
522 /// `bool` that could be written using `matches!`
524 /// ### Why is this bad?
525 /// Readability and needless complexity.
527 /// ### Known problems
528 /// This lint falsely triggers, if there are arms with
529 /// `cfg` attributes that remove an arm evaluating to `false`.
535 /// let a = match x {
540 /// let a = if let Some(0) = x {
550 /// let a = matches!(x, Some(0));
552 #[clippy::version = "1.47.0"]
553 pub MATCH_LIKE_MATCHES_MACRO
,
555 "a match that could be written with the matches! macro"
558 declare_clippy_lint
! {
560 /// Checks for `match` with identical arm bodies.
562 /// ### Why is this bad?
563 /// This is probably a copy & paste error. If arm bodies
564 /// are the same on purpose, you can factor them
565 /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
567 /// ### Known problems
568 /// False positive possible with order dependent `match`
570 /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
577 /// Baz => bar(), // <= oops
581 /// This should probably be
586 /// Baz => baz(), // <= fixed
590 /// or if the original code was not a typo:
593 /// Bar | Baz => bar(), // <= shows the intent better
597 #[clippy::version = "pre 1.29.0"]
600 "`match` with identical arm bodies"
603 declare_clippy_lint
! {
605 /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
606 /// when function signatures are the same.
608 /// ### Why is this bad?
609 /// This `match` block does nothing and might not be what the coder intended.
613 /// fn foo() -> Result<(), i32> {
615 /// Ok(val) => Ok(val),
616 /// Err(err) => Err(err),
620 /// fn bar() -> Option<i32> {
621 /// if let Some(val) = option {
629 /// Could be replaced as
632 /// fn foo() -> Result<(), i32> {
636 /// fn bar() -> Option<i32> {
640 #[clippy::version = "1.61.0"]
643 "`match` or match-like `if let` that are unnecessary"
646 declare_clippy_lint
! {
648 /// Finds nested `match` or `if let` expressions where the patterns may be "collapsed" together
649 /// without adding any branches.
651 /// Note that this lint is not intended to find _all_ cases where nested match patterns can be merged, but only
652 /// cases where merging would most likely make the code more readable.
654 /// ### Why is this bad?
655 /// It is unnecessarily verbose and complex.
659 /// fn func(opt: Option<Result<u64, String>>) {
660 /// let n = match opt {
661 /// Some(n) => match n {
671 /// fn func(opt: Option<Result<u64, String>>) {
672 /// let n = match opt {
673 /// Some(Ok(n)) => n,
678 #[clippy::version = "1.50.0"]
679 pub COLLAPSIBLE_MATCH
,
681 "Nested `match` or `if let` expressions where the patterns may be \"collapsed\" together."
684 declare_clippy_lint
! {
686 /// Finds patterns that reimplement `Option::unwrap_or` or `Result::unwrap_or`.
688 /// ### Why is this bad?
689 /// Concise code helps focusing on behavior instead of boilerplate.
693 /// let foo: Option<i32> = None;
702 /// let foo: Option<i32> = None;
703 /// foo.unwrap_or(1);
705 #[clippy::version = "1.49.0"]
706 pub MANUAL_UNWRAP_OR
,
708 "finds patterns that can be encoded more concisely with `Option::unwrap_or` or `Result::unwrap_or`"
711 declare_clippy_lint
! {
713 /// Checks for `match vec[idx]` or `match vec[n..m]`.
715 /// ### Why is this bad?
716 /// This can panic at runtime.
720 /// let arr = vec![0, 1, 2, 3];
724 /// 0 => println!("{}", 0),
725 /// 1 => println!("{}", 3),
732 /// let arr = vec![0, 1, 2, 3];
735 /// match arr.get(idx) {
736 /// Some(0) => println!("{}", 0),
737 /// Some(1) => println!("{}", 3),
741 #[clippy::version = "1.45.0"]
742 pub MATCH_ON_VEC_ITEMS
,
744 "matching on vector elements can panic"
747 declare_clippy_lint
! {
749 /// Checks for `match` expressions modifying the case of a string with non-compliant arms
751 /// ### Why is this bad?
752 /// The arm is unreachable, which is likely a mistake
756 /// # let text = "Foo";
757 /// match &*text.to_ascii_lowercase() {
765 /// # let text = "Foo";
766 /// match &*text.to_ascii_lowercase() {
772 #[clippy::version = "1.58.0"]
773 pub MATCH_STR_CASE_MISMATCH
,
775 "creation of a case altering match expression with non-compliant arms"
778 declare_clippy_lint
! {
780 /// Check for temporaries returned from function calls in a match scrutinee that have the
781 /// `clippy::has_significant_drop` attribute.
783 /// ### Why is this bad?
784 /// The `clippy::has_significant_drop` attribute can be added to types whose Drop impls have
785 /// an important side-effect, such as unlocking a mutex, making it important for users to be
786 /// able to accurately understand their lifetimes. When a temporary is returned in a function
787 /// call in a match scrutinee, its lifetime lasts until the end of the match block, which may
790 /// For `Mutex`es this can lead to a deadlock. This happens when the match scrutinee uses a
791 /// function call that returns a `MutexGuard` and then tries to lock again in one of the match
792 /// arms. In that case the `MutexGuard` in the scrutinee will not be dropped until the end of
793 /// the match block and thus will not unlock.
797 /// # use std::sync::Mutex;
798 /// # struct State {}
800 /// # fn foo(&self) -> bool {
803 /// # fn bar(&self) {}
805 /// let mutex = Mutex::new(State {});
807 /// match mutex.lock().unwrap().foo() {
809 /// mutex.lock().unwrap().bar(); // Deadlock!
814 /// println!("All done!");
818 /// # use std::sync::Mutex;
819 /// # struct State {}
821 /// # fn foo(&self) -> bool {
824 /// # fn bar(&self) {}
826 /// let mutex = Mutex::new(State {});
828 /// let is_foo = mutex.lock().unwrap().foo();
831 /// mutex.lock().unwrap().bar();
836 /// println!("All done!");
838 #[clippy::version = "1.60.0"]
839 pub SIGNIFICANT_DROP_IN_SCRUTINEE
,
841 "warns when a temporary of a type with a drop with a significant side-effect might have a surprising lifetime"
844 declare_clippy_lint
! {
846 /// Checks for usages of `Err(x)?`.
848 /// ### Why is this bad?
849 /// The `?` operator is designed to allow calls that
850 /// can fail to be easily chained. For example, `foo()?.bar()` or
851 /// `foo(bar()?)`. Because `Err(x)?` can't be used that way (it will
852 /// always return), it is more clear to write `return Err(x)`.
856 /// fn foo(fail: bool) -> Result<i32, String> {
863 /// Could be written:
866 /// fn foo(fail: bool) -> Result<i32, String> {
868 /// return Err("failed".into());
873 #[clippy::version = "1.38.0"]
876 "return errors explicitly rather than hiding them behind a `?`"
879 declare_clippy_lint
! {
881 /// Checks for usages of `match` which could be implemented using `map`
883 /// ### Why is this bad?
884 /// Using the `map` method is clearer and more concise.
889 /// Some(x) => Some(x + 1),
895 /// Some(0).map(|x| x + 1);
897 #[clippy::version = "1.52.0"]
900 "reimplementation of `map`"
903 declare_clippy_lint
! {
905 /// Checks for usages of `match` which could be implemented using `filter`
907 /// ### Why is this bad?
908 /// Using the `filter` method is clearer and more concise.
913 /// Some(x) => if x % 2 == 0 {
923 /// Some(0).filter(|&x| x % 2 == 0);
925 #[clippy::version = "1.66.0"]
928 "reimplementation of `filter`"
934 infallible_destructuring_match_linted
: bool
,
939 pub fn new(msrv
: Msrv
) -> Self {
947 impl_lint_pass
!(Matches
=> [
952 MATCH_OVERLAPPING_ARM
,
955 WILDCARD_ENUM_MATCH_ARM
,
956 MATCH_WILDCARD_FOR_SINGLE_VARIANTS
,
957 WILDCARD_IN_OR_PATTERNS
,
958 MATCH_SINGLE_BINDING
,
959 INFALLIBLE_DESTRUCTURING_MATCH
,
960 REST_PAT_IN_FULLY_BOUND_STRUCTS
,
961 REDUNDANT_PATTERN_MATCHING
,
962 MATCH_LIKE_MATCHES_MACRO
,
968 MATCH_STR_CASE_MISMATCH
,
969 SIGNIFICANT_DROP_IN_SCRUTINEE
,
975 impl<'tcx
> LateLintPass
<'tcx
> for Matches
{
976 fn check_expr(&mut self, cx
: &LateContext
<'tcx
>, expr
: &'tcx Expr
<'_
>) {
977 if in_external_macro(cx
.sess(), expr
.span
) {
980 let from_expansion
= expr
.span
.from_expansion();
982 if let ExprKind
::Match(ex
, arms
, source
) = expr
.kind
{
983 if source
== MatchSource
::Normal
&& !is_span_match(cx
, expr
.span
) {
986 if matches
!(source
, MatchSource
::Normal
| MatchSource
::ForLoopDesugar
) {
987 significant_drop_in_scrutinee
::check(cx
, expr
, ex
, arms
, source
);
990 collapsible_match
::check_match(cx
, arms
);
992 // These don't depend on a relationship between multiple arms
993 match_wild_err_arm
::check(cx
, ex
, arms
);
994 wild_in_or_pats
::check(cx
, arms
);
997 if source
== MatchSource
::TryDesugar
{
998 try_err
::check(cx
, expr
, ex
);
1001 if !from_expansion
&& !contains_cfg_arm(cx
, expr
, ex
, arms
) {
1002 if source
== MatchSource
::Normal
{
1003 if !(self.msrv
.meets(msrvs
::MATCHES_MACRO
) && match_like_matches
::check_match(cx
, expr
, ex
, arms
)) {
1004 match_same_arms
::check(cx
, arms
);
1007 redundant_pattern_match
::check_match(cx
, expr
, ex
, arms
);
1008 single_match
::check(cx
, ex
, arms
, expr
);
1009 match_bool
::check(cx
, ex
, arms
, expr
);
1010 overlapping_arms
::check(cx
, ex
, arms
);
1011 match_wild_enum
::check(cx
, ex
, arms
);
1012 match_as_ref
::check(cx
, ex
, arms
, expr
);
1013 needless_match
::check_match(cx
, ex
, arms
, expr
);
1014 match_on_vec_items
::check(cx
, ex
);
1015 match_str_case_mismatch
::check(cx
, ex
, arms
);
1017 if !in_constant(cx
, expr
.hir_id
) {
1018 manual_unwrap_or
::check(cx
, expr
, ex
, arms
);
1019 manual_map
::check_match(cx
, expr
, ex
, arms
);
1020 manual_filter
::check_match(cx
, ex
, arms
, expr
);
1023 if self.infallible_destructuring_match_linted
{
1024 self.infallible_destructuring_match_linted
= false;
1026 match_single_binding
::check(cx
, ex
, arms
, expr
);
1029 match_ref_pats
::check(cx
, ex
, arms
.iter().map(|el
| el
.pat
), expr
);
1031 } else if let Some(if_let
) = higher
::IfLet
::hir(cx
, expr
) {
1032 collapsible_match
::check_if_let(cx
, if_let
.let_pat
, if_let
.if_then
, if_let
.if_else
);
1033 if !from_expansion
{
1034 if let Some(else_expr
) = if_let
.if_else
{
1035 if self.msrv
.meets(msrvs
::MATCHES_MACRO
) {
1036 match_like_matches
::check_if_let(
1045 if !in_constant(cx
, expr
.hir_id
) {
1046 manual_map
::check_if_let(cx
, expr
, if_let
.let_pat
, if_let
.let_expr
, if_let
.if_then
, else_expr
);
1047 manual_filter
::check_if_let(
1057 redundant_pattern_match
::check_if_let(
1062 if_let
.if_else
.is_some(),
1064 needless_match
::check_if_let(cx
, expr
, &if_let
);
1066 } else if !from_expansion
{
1067 redundant_pattern_match
::check(cx
, expr
);
1071 fn check_local(&mut self, cx
: &LateContext
<'tcx
>, local
: &'tcx Local
<'_
>) {
1072 self.infallible_destructuring_match_linted
|=
1073 local
.els
.is_none() && infallible_destructuring_match
::check(cx
, local
);
1076 fn check_pat(&mut self, cx
: &LateContext
<'tcx
>, pat
: &'tcx Pat
<'_
>) {
1077 rest_pat_in_fully_bound_struct
::check(cx
, pat
);
1080 extract_msrv_attr
!(LateContext
);
1083 /// Checks if there are any arms with a `#[cfg(..)]` attribute.
1084 fn contains_cfg_arm(cx
: &LateContext
<'_
>, e
: &Expr
<'_
>, scrutinee
: &Expr
<'_
>, arms
: &[Arm
<'_
>]) -> bool
{
1085 let Some(scrutinee_span
) = walk_span_to_context(scrutinee
.span
, SyntaxContext
::root()) else {
1086 // Shouldn't happen, but treat this as though a `cfg` attribute were found
1090 let start
= scrutinee_span
.hi();
1091 let mut arm_spans
= arms
.iter().map(|arm
| {
1092 let data
= arm
.span
.data();
1093 (data
.ctxt
== SyntaxContext
::root()).then_some((data
.lo
, data
.hi
))
1095 let end
= e
.span
.hi();
1097 // Walk through all the non-code space before each match arm. The space trailing the final arm is
1098 // handled after the `try_fold` e.g.
1101 // _________^- everything between the scrutinee and arm1
1103 //|---^___________^ everything before arm2
1104 //| #[cfg(feature = "enabled")]
1105 //| arm2 => some_code(),
1106 //|---^____________________^ everything before arm3
1107 //| // some comment about arm3
1108 //| arm3 => some_code(),
1109 //|---^____________________^ everything after arm3
1110 //| #[cfg(feature = "disabled")]
1111 //| arm4 = some_code(),
1114 let found
= arm_spans
.try_fold(start
, |start
, range
| {
1115 let Some((end
, next_start
)) = range
else {
1116 // Shouldn't happen as macros can't expand to match arms, but treat this as though a `cfg` attribute were
1120 let span
= SpanData
{
1123 ctxt
: SyntaxContext
::root(),
1127 (!span_contains_cfg(cx
, span
)).then_some(next_start
).ok_or(())
1131 let span
= SpanData
{
1134 ctxt
: SyntaxContext
::root(),
1138 span_contains_cfg(cx
, span
)
1144 /// Checks if the given span contains a `#[cfg(..)]` attribute
1145 fn span_contains_cfg(cx
: &LateContext
<'_
>, s
: Span
) -> bool
{
1146 let Some(snip
) = snippet_opt(cx
, s
) else {
1147 // Assume true. This would require either an invalid span, or one which crosses file boundaries.
1150 let mut pos
= 0usize
;
1151 let mut iter
= tokenize(&snip
).map(|t
| {
1153 pos
+= t
.len
as usize;
1154 (t
.kind
, start
..pos
)
1157 // Search for the token sequence [`#`, `[`, `cfg`]
1158 while iter
.any(|(t
, _
)| matches
!(t
, TokenKind
::Pound
)) {
1159 let mut iter
= iter
.by_ref().skip_while(|(t
, _
)| {
1162 TokenKind
::Whitespace
| TokenKind
::LineComment { .. }
| TokenKind
::BlockComment { .. }
1165 if matches
!(iter
.next(), Some((TokenKind
::OpenBracket
, _
)))
1166 && matches
!(iter
.next(), Some((TokenKind
::Ident
, range
)) if &snip
[range
.clone()] == "cfg")