]> git.lizzy.rs Git - rust.git/blob - src/tools/clippy/clippy_lints/src/matches/mod.rs
Merge commit 'dc5423ad448877e33cca28db2f1445c9c4473c75' into clippyup
[rust.git] / src / tools / clippy / clippy_lints / src / matches / mod.rs
1 use clippy_utils::source::{snippet_opt, walk_span_to_context};
2 use clippy_utils::{meets_msrv, msrvs};
3 use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
4 use rustc_lexer::{tokenize, TokenKind};
5 use rustc_lint::{LateContext, LateLintPass};
6 use rustc_semver::RustcVersion;
7 use rustc_session::{declare_tool_lint, impl_lint_pass};
8 use rustc_span::{Span, SpanData, SyntaxContext};
9
10 mod infalliable_detructuring_match;
11 mod match_as_ref;
12 mod match_bool;
13 mod match_like_matches;
14 mod match_ref_pats;
15 mod match_same_arms;
16 mod match_single_binding;
17 mod match_wild_enum;
18 mod match_wild_err_arm;
19 mod needless_match;
20 mod overlapping_arms;
21 mod redundant_pattern_match;
22 mod rest_pat_in_fully_bound_struct;
23 mod single_match;
24 mod wild_in_or_pats;
25
26 declare_clippy_lint! {
27     /// ### What it does
28     /// Checks for matches with a single arm where an `if let`
29     /// will usually suffice.
30     ///
31     /// ### Why is this bad?
32     /// Just readability – `if let` nests less than a `match`.
33     ///
34     /// ### Example
35     /// ```rust
36     /// # fn bar(stool: &str) {}
37     /// # let x = Some("abc");
38     /// // Bad
39     /// match x {
40     ///     Some(ref foo) => bar(foo),
41     ///     _ => (),
42     /// }
43     ///
44     /// // Good
45     /// if let Some(ref foo) = x {
46     ///     bar(foo);
47     /// }
48     /// ```
49     #[clippy::version = "pre 1.29.0"]
50     pub SINGLE_MATCH,
51     style,
52     "a `match` statement with a single nontrivial arm (i.e., where the other arm is `_ => {}`) instead of `if let`"
53 }
54
55 declare_clippy_lint! {
56     /// ### What it does
57     /// Checks for matches with two arms where an `if let else` will
58     /// usually suffice.
59     ///
60     /// ### Why is this bad?
61     /// Just readability – `if let` nests less than a `match`.
62     ///
63     /// ### Known problems
64     /// Personal style preferences may differ.
65     ///
66     /// ### Example
67     /// Using `match`:
68     ///
69     /// ```rust
70     /// # fn bar(foo: &usize) {}
71     /// # let other_ref: usize = 1;
72     /// # let x: Option<&usize> = Some(&1);
73     /// match x {
74     ///     Some(ref foo) => bar(foo),
75     ///     _ => bar(&other_ref),
76     /// }
77     /// ```
78     ///
79     /// Using `if let` with `else`:
80     ///
81     /// ```rust
82     /// # fn bar(foo: &usize) {}
83     /// # let other_ref: usize = 1;
84     /// # let x: Option<&usize> = Some(&1);
85     /// if let Some(ref foo) = x {
86     ///     bar(foo);
87     /// } else {
88     ///     bar(&other_ref);
89     /// }
90     /// ```
91     #[clippy::version = "pre 1.29.0"]
92     pub SINGLE_MATCH_ELSE,
93     pedantic,
94     "a `match` statement with two arms where the second arm's pattern is a placeholder instead of a specific match pattern"
95 }
96
97 declare_clippy_lint! {
98     /// ### What it does
99     /// Checks for matches where all arms match a reference,
100     /// suggesting to remove the reference and deref the matched expression
101     /// instead. It also checks for `if let &foo = bar` blocks.
102     ///
103     /// ### Why is this bad?
104     /// It just makes the code less readable. That reference
105     /// destructuring adds nothing to the code.
106     ///
107     /// ### Example
108     /// ```rust,ignore
109     /// // Bad
110     /// match x {
111     ///     &A(ref y) => foo(y),
112     ///     &B => bar(),
113     ///     _ => frob(&x),
114     /// }
115     ///
116     /// // Good
117     /// match *x {
118     ///     A(ref y) => foo(y),
119     ///     B => bar(),
120     ///     _ => frob(x),
121     /// }
122     /// ```
123     #[clippy::version = "pre 1.29.0"]
124     pub MATCH_REF_PATS,
125     style,
126     "a `match` or `if let` with all arms prefixed with `&` instead of deref-ing the match expression"
127 }
128
129 declare_clippy_lint! {
130     /// ### What it does
131     /// Checks for matches where match expression is a `bool`. It
132     /// suggests to replace the expression with an `if...else` block.
133     ///
134     /// ### Why is this bad?
135     /// It makes the code less readable.
136     ///
137     /// ### Example
138     /// ```rust
139     /// # fn foo() {}
140     /// # fn bar() {}
141     /// let condition: bool = true;
142     /// match condition {
143     ///     true => foo(),
144     ///     false => bar(),
145     /// }
146     /// ```
147     /// Use if/else instead:
148     /// ```rust
149     /// # fn foo() {}
150     /// # fn bar() {}
151     /// let condition: bool = true;
152     /// if condition {
153     ///     foo();
154     /// } else {
155     ///     bar();
156     /// }
157     /// ```
158     #[clippy::version = "pre 1.29.0"]
159     pub MATCH_BOOL,
160     pedantic,
161     "a `match` on a boolean expression instead of an `if..else` block"
162 }
163
164 declare_clippy_lint! {
165     /// ### What it does
166     /// Checks for overlapping match arms.
167     ///
168     /// ### Why is this bad?
169     /// It is likely to be an error and if not, makes the code
170     /// less obvious.
171     ///
172     /// ### Example
173     /// ```rust
174     /// let x = 5;
175     /// match x {
176     ///     1..=10 => println!("1 ... 10"),
177     ///     5..=15 => println!("5 ... 15"),
178     ///     _ => (),
179     /// }
180     /// ```
181     #[clippy::version = "pre 1.29.0"]
182     pub MATCH_OVERLAPPING_ARM,
183     style,
184     "a `match` with overlapping arms"
185 }
186
187 declare_clippy_lint! {
188     /// ### What it does
189     /// Checks for arm which matches all errors with `Err(_)`
190     /// and take drastic actions like `panic!`.
191     ///
192     /// ### Why is this bad?
193     /// It is generally a bad practice, similar to
194     /// catching all exceptions in java with `catch(Exception)`
195     ///
196     /// ### Example
197     /// ```rust
198     /// let x: Result<i32, &str> = Ok(3);
199     /// match x {
200     ///     Ok(_) => println!("ok"),
201     ///     Err(_) => panic!("err"),
202     /// }
203     /// ```
204     #[clippy::version = "pre 1.29.0"]
205     pub MATCH_WILD_ERR_ARM,
206     pedantic,
207     "a `match` with `Err(_)` arm and take drastic actions"
208 }
209
210 declare_clippy_lint! {
211     /// ### What it does
212     /// Checks for match which is used to add a reference to an
213     /// `Option` value.
214     ///
215     /// ### Why is this bad?
216     /// Using `as_ref()` or `as_mut()` instead is shorter.
217     ///
218     /// ### Example
219     /// ```rust
220     /// let x: Option<()> = None;
221     ///
222     /// // Bad
223     /// let r: Option<&()> = match x {
224     ///     None => None,
225     ///     Some(ref v) => Some(v),
226     /// };
227     ///
228     /// // Good
229     /// let r: Option<&()> = x.as_ref();
230     /// ```
231     #[clippy::version = "pre 1.29.0"]
232     pub MATCH_AS_REF,
233     complexity,
234     "a `match` on an Option value instead of using `as_ref()` or `as_mut`"
235 }
236
237 declare_clippy_lint! {
238     /// ### What it does
239     /// Checks for wildcard enum matches using `_`.
240     ///
241     /// ### Why is this bad?
242     /// New enum variants added by library updates can be missed.
243     ///
244     /// ### Known problems
245     /// Suggested replacements may be incorrect if guards exhaustively cover some
246     /// variants, and also may not use correct path to enum if it's not present in the current scope.
247     ///
248     /// ### Example
249     /// ```rust
250     /// # enum Foo { A(usize), B(usize) }
251     /// # let x = Foo::B(1);
252     /// // Bad
253     /// match x {
254     ///     Foo::A(_) => {},
255     ///     _ => {},
256     /// }
257     ///
258     /// // Good
259     /// match x {
260     ///     Foo::A(_) => {},
261     ///     Foo::B(_) => {},
262     /// }
263     /// ```
264     #[clippy::version = "1.34.0"]
265     pub WILDCARD_ENUM_MATCH_ARM,
266     restriction,
267     "a wildcard enum match arm using `_`"
268 }
269
270 declare_clippy_lint! {
271     /// ### What it does
272     /// Checks for wildcard enum matches for a single variant.
273     ///
274     /// ### Why is this bad?
275     /// New enum variants added by library updates can be missed.
276     ///
277     /// ### Known problems
278     /// Suggested replacements may not use correct path to enum
279     /// if it's not present in the current scope.
280     ///
281     /// ### Example
282     /// ```rust
283     /// # enum Foo { A, B, C }
284     /// # let x = Foo::B;
285     /// // Bad
286     /// match x {
287     ///     Foo::A => {},
288     ///     Foo::B => {},
289     ///     _ => {},
290     /// }
291     ///
292     /// // Good
293     /// match x {
294     ///     Foo::A => {},
295     ///     Foo::B => {},
296     ///     Foo::C => {},
297     /// }
298     /// ```
299     #[clippy::version = "1.45.0"]
300     pub MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
301     pedantic,
302     "a wildcard enum match for a single variant"
303 }
304
305 declare_clippy_lint! {
306     /// ### What it does
307     /// Checks for wildcard pattern used with others patterns in same match arm.
308     ///
309     /// ### Why is this bad?
310     /// Wildcard pattern already covers any other pattern as it will match anyway.
311     /// It makes the code less readable, especially to spot wildcard pattern use in match arm.
312     ///
313     /// ### Example
314     /// ```rust
315     /// // Bad
316     /// match "foo" {
317     ///     "a" => {},
318     ///     "bar" | _ => {},
319     /// }
320     ///
321     /// // Good
322     /// match "foo" {
323     ///     "a" => {},
324     ///     _ => {},
325     /// }
326     /// ```
327     #[clippy::version = "1.42.0"]
328     pub WILDCARD_IN_OR_PATTERNS,
329     complexity,
330     "a wildcard pattern used with others patterns in same match arm"
331 }
332
333 declare_clippy_lint! {
334     /// ### What it does
335     /// Checks for matches being used to destructure a single-variant enum
336     /// or tuple struct where a `let` will suffice.
337     ///
338     /// ### Why is this bad?
339     /// Just readability – `let` doesn't nest, whereas a `match` does.
340     ///
341     /// ### Example
342     /// ```rust
343     /// enum Wrapper {
344     ///     Data(i32),
345     /// }
346     ///
347     /// let wrapper = Wrapper::Data(42);
348     ///
349     /// let data = match wrapper {
350     ///     Wrapper::Data(i) => i,
351     /// };
352     /// ```
353     ///
354     /// The correct use would be:
355     /// ```rust
356     /// enum Wrapper {
357     ///     Data(i32),
358     /// }
359     ///
360     /// let wrapper = Wrapper::Data(42);
361     /// let Wrapper::Data(data) = wrapper;
362     /// ```
363     #[clippy::version = "pre 1.29.0"]
364     pub INFALLIBLE_DESTRUCTURING_MATCH,
365     style,
366     "a `match` statement with a single infallible arm instead of a `let`"
367 }
368
369 declare_clippy_lint! {
370     /// ### What it does
371     /// Checks for useless match that binds to only one value.
372     ///
373     /// ### Why is this bad?
374     /// Readability and needless complexity.
375     ///
376     /// ### Known problems
377     ///  Suggested replacements may be incorrect when `match`
378     /// is actually binding temporary value, bringing a 'dropped while borrowed' error.
379     ///
380     /// ### Example
381     /// ```rust
382     /// # let a = 1;
383     /// # let b = 2;
384     ///
385     /// // Bad
386     /// match (a, b) {
387     ///     (c, d) => {
388     ///         // useless match
389     ///     }
390     /// }
391     ///
392     /// // Good
393     /// let (c, d) = (a, b);
394     /// ```
395     #[clippy::version = "1.43.0"]
396     pub MATCH_SINGLE_BINDING,
397     complexity,
398     "a match with a single binding instead of using `let` statement"
399 }
400
401 declare_clippy_lint! {
402     /// ### What it does
403     /// Checks for unnecessary '..' pattern binding on struct when all fields are explicitly matched.
404     ///
405     /// ### Why is this bad?
406     /// Correctness and readability. It's like having a wildcard pattern after
407     /// matching all enum variants explicitly.
408     ///
409     /// ### Example
410     /// ```rust
411     /// # struct A { a: i32 }
412     /// let a = A { a: 5 };
413     ///
414     /// // Bad
415     /// match a {
416     ///     A { a: 5, .. } => {},
417     ///     _ => {},
418     /// }
419     ///
420     /// // Good
421     /// match a {
422     ///     A { a: 5 } => {},
423     ///     _ => {},
424     /// }
425     /// ```
426     #[clippy::version = "1.43.0"]
427     pub REST_PAT_IN_FULLY_BOUND_STRUCTS,
428     restriction,
429     "a match on a struct that binds all fields but still uses the wildcard pattern"
430 }
431
432 declare_clippy_lint! {
433     /// ### What it does
434     /// Lint for redundant pattern matching over `Result`, `Option`,
435     /// `std::task::Poll` or `std::net::IpAddr`
436     ///
437     /// ### Why is this bad?
438     /// It's more concise and clear to just use the proper
439     /// utility function
440     ///
441     /// ### Known problems
442     /// This will change the drop order for the matched type. Both `if let` and
443     /// `while let` will drop the value at the end of the block, both `if` and `while` will drop the
444     /// value before entering the block. For most types this change will not matter, but for a few
445     /// types this will not be an acceptable change (e.g. locks). See the
446     /// [reference](https://doc.rust-lang.org/reference/destructors.html#drop-scopes) for more about
447     /// drop order.
448     ///
449     /// ### Example
450     /// ```rust
451     /// # use std::task::Poll;
452     /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
453     /// if let Ok(_) = Ok::<i32, i32>(42) {}
454     /// if let Err(_) = Err::<i32, i32>(42) {}
455     /// if let None = None::<()> {}
456     /// if let Some(_) = Some(42) {}
457     /// if let Poll::Pending = Poll::Pending::<()> {}
458     /// if let Poll::Ready(_) = Poll::Ready(42) {}
459     /// if let IpAddr::V4(_) = IpAddr::V4(Ipv4Addr::LOCALHOST) {}
460     /// if let IpAddr::V6(_) = IpAddr::V6(Ipv6Addr::LOCALHOST) {}
461     /// match Ok::<i32, i32>(42) {
462     ///     Ok(_) => true,
463     ///     Err(_) => false,
464     /// };
465     /// ```
466     ///
467     /// The more idiomatic use would be:
468     ///
469     /// ```rust
470     /// # use std::task::Poll;
471     /// # use std::net::{IpAddr, Ipv4Addr, Ipv6Addr};
472     /// if Ok::<i32, i32>(42).is_ok() {}
473     /// if Err::<i32, i32>(42).is_err() {}
474     /// if None::<()>.is_none() {}
475     /// if Some(42).is_some() {}
476     /// if Poll::Pending::<()>.is_pending() {}
477     /// if Poll::Ready(42).is_ready() {}
478     /// if IpAddr::V4(Ipv4Addr::LOCALHOST).is_ipv4() {}
479     /// if IpAddr::V6(Ipv6Addr::LOCALHOST).is_ipv6() {}
480     /// Ok::<i32, i32>(42).is_ok();
481     /// ```
482     #[clippy::version = "1.31.0"]
483     pub REDUNDANT_PATTERN_MATCHING,
484     style,
485     "use the proper utility function avoiding an `if let`"
486 }
487
488 declare_clippy_lint! {
489     /// ### What it does
490     /// Checks for `match`  or `if let` expressions producing a
491     /// `bool` that could be written using `matches!`
492     ///
493     /// ### Why is this bad?
494     /// Readability and needless complexity.
495     ///
496     /// ### Known problems
497     /// This lint falsely triggers, if there are arms with
498     /// `cfg` attributes that remove an arm evaluating to `false`.
499     ///
500     /// ### Example
501     /// ```rust
502     /// let x = Some(5);
503     ///
504     /// // Bad
505     /// let a = match x {
506     ///     Some(0) => true,
507     ///     _ => false,
508     /// };
509     ///
510     /// let a = if let Some(0) = x {
511     ///     true
512     /// } else {
513     ///     false
514     /// };
515     ///
516     /// // Good
517     /// let a = matches!(x, Some(0));
518     /// ```
519     #[clippy::version = "1.47.0"]
520     pub MATCH_LIKE_MATCHES_MACRO,
521     style,
522     "a match that could be written with the matches! macro"
523 }
524
525 declare_clippy_lint! {
526     /// ### What it does
527     /// Checks for `match` with identical arm bodies.
528     ///
529     /// ### Why is this bad?
530     /// This is probably a copy & paste error. If arm bodies
531     /// are the same on purpose, you can factor them
532     /// [using `|`](https://doc.rust-lang.org/book/patterns.html#multiple-patterns).
533     ///
534     /// ### Known problems
535     /// False positive possible with order dependent `match`
536     /// (see issue
537     /// [#860](https://github.com/rust-lang/rust-clippy/issues/860)).
538     ///
539     /// ### Example
540     /// ```rust,ignore
541     /// match foo {
542     ///     Bar => bar(),
543     ///     Quz => quz(),
544     ///     Baz => bar(), // <= oops
545     /// }
546     /// ```
547     ///
548     /// This should probably be
549     /// ```rust,ignore
550     /// match foo {
551     ///     Bar => bar(),
552     ///     Quz => quz(),
553     ///     Baz => baz(), // <= fixed
554     /// }
555     /// ```
556     ///
557     /// or if the original code was not a typo:
558     /// ```rust,ignore
559     /// match foo {
560     ///     Bar | Baz => bar(), // <= shows the intent better
561     ///     Quz => quz(),
562     /// }
563     /// ```
564     #[clippy::version = "pre 1.29.0"]
565     pub MATCH_SAME_ARMS,
566     pedantic,
567     "`match` with identical arm bodies"
568 }
569
570 declare_clippy_lint! {
571     /// ### What it does
572     /// Checks for unnecessary `match` or match-like `if let` returns for `Option` and `Result`
573     /// when function signatures are the same.
574     ///
575     /// ### Why is this bad?
576     /// This `match` block does nothing and might not be what the coder intended.
577     ///
578     /// ### Example
579     /// ```rust,ignore
580     /// fn foo() -> Result<(), i32> {
581     ///     match result {
582     ///         Ok(val) => Ok(val),
583     ///         Err(err) => Err(err),
584     ///     }
585     /// }
586     ///
587     /// fn bar() -> Option<i32> {
588     ///     if let Some(val) = option {
589     ///         Some(val)
590     ///     } else {
591     ///         None
592     ///     }
593     /// }
594     /// ```
595     ///
596     /// Could be replaced as
597     ///
598     /// ```rust,ignore
599     /// fn foo() -> Result<(), i32> {
600     ///     result
601     /// }
602     ///
603     /// fn bar() -> Option<i32> {
604     ///     option
605     /// }
606     /// ```
607     #[clippy::version = "1.61.0"]
608     pub NEEDLESS_MATCH,
609     complexity,
610     "`match` or match-like `if let` that are unnecessary"
611 }
612
613 #[derive(Default)]
614 pub struct Matches {
615     msrv: Option<RustcVersion>,
616     infallible_destructuring_match_linted: bool,
617 }
618
619 impl Matches {
620     #[must_use]
621     pub fn new(msrv: Option<RustcVersion>) -> Self {
622         Self {
623             msrv,
624             ..Matches::default()
625         }
626     }
627 }
628
629 impl_lint_pass!(Matches => [
630     SINGLE_MATCH,
631     MATCH_REF_PATS,
632     MATCH_BOOL,
633     SINGLE_MATCH_ELSE,
634     MATCH_OVERLAPPING_ARM,
635     MATCH_WILD_ERR_ARM,
636     MATCH_AS_REF,
637     WILDCARD_ENUM_MATCH_ARM,
638     MATCH_WILDCARD_FOR_SINGLE_VARIANTS,
639     WILDCARD_IN_OR_PATTERNS,
640     MATCH_SINGLE_BINDING,
641     INFALLIBLE_DESTRUCTURING_MATCH,
642     REST_PAT_IN_FULLY_BOUND_STRUCTS,
643     REDUNDANT_PATTERN_MATCHING,
644     MATCH_LIKE_MATCHES_MACRO,
645     MATCH_SAME_ARMS,
646     NEEDLESS_MATCH,
647 ]);
648
649 impl<'tcx> LateLintPass<'tcx> for Matches {
650     fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &'tcx Expr<'_>) {
651         if expr.span.from_expansion() {
652             return;
653         }
654
655         if let ExprKind::Match(ex, arms, source) = expr.kind {
656             if !contains_cfg_arm(cx, expr, ex, arms) {
657                 if source == MatchSource::Normal {
658                     if !(meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO)
659                         && match_like_matches::check_match(cx, expr, ex, arms))
660                     {
661                         match_same_arms::check(cx, arms);
662                     }
663
664                     redundant_pattern_match::check_match(cx, expr, ex, arms);
665                     single_match::check(cx, ex, arms, expr);
666                     match_bool::check(cx, ex, arms, expr);
667                     overlapping_arms::check(cx, ex, arms);
668                     match_wild_enum::check(cx, ex, arms);
669                     match_as_ref::check(cx, ex, arms, expr);
670                     needless_match::check_match(cx, ex, arms);
671
672                     if self.infallible_destructuring_match_linted {
673                         self.infallible_destructuring_match_linted = false;
674                     } else {
675                         match_single_binding::check(cx, ex, arms, expr);
676                     }
677                 }
678                 match_ref_pats::check(cx, ex, arms.iter().map(|el| el.pat), expr);
679             }
680
681             // These don't depend on a relationship between multiple arms
682             match_wild_err_arm::check(cx, ex, arms);
683             wild_in_or_pats::check(cx, arms);
684         } else {
685             if meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO) {
686                 match_like_matches::check(cx, expr);
687             }
688             redundant_pattern_match::check(cx, expr);
689             needless_match::check(cx, expr);
690         }
691     }
692
693     fn check_local(&mut self, cx: &LateContext<'tcx>, local: &'tcx Local<'_>) {
694         self.infallible_destructuring_match_linted |= infalliable_detructuring_match::check(cx, local);
695     }
696
697     fn check_pat(&mut self, cx: &LateContext<'tcx>, pat: &'tcx Pat<'_>) {
698         rest_pat_in_fully_bound_struct::check(cx, pat);
699     }
700
701     extract_msrv_attr!(LateContext);
702 }
703
704 /// Checks if there are any arms with a `#[cfg(..)]` attribute.
705 fn contains_cfg_arm(cx: &LateContext<'_>, e: &Expr<'_>, scrutinee: &Expr<'_>, arms: &[Arm<'_>]) -> bool {
706     let Some(scrutinee_span) = walk_span_to_context(scrutinee.span, SyntaxContext::root()) else {
707         // Shouldn't happen, but treat this as though a `cfg` attribute were found
708         return true;
709     };
710
711     let start = scrutinee_span.hi();
712     let mut arm_spans = arms.iter().map(|arm| {
713         let data = arm.span.data();
714         (data.ctxt == SyntaxContext::root()).then(|| (data.lo, data.hi))
715     });
716     let end = e.span.hi();
717
718     // Walk through all the non-code space before each match arm. The space trailing the final arm is
719     // handled after the `try_fold` e.g.
720     //
721     // match foo {
722     // _________^-                      everything between the scrutinee and arm1
723     //|    arm1 => (),
724     //|---^___________^                 everything before arm2
725     //|    #[cfg(feature = "enabled")]
726     //|    arm2 => some_code(),
727     //|---^____________________^        everything before arm3
728     //|    // some comment about arm3
729     //|    arm3 => some_code(),
730     //|---^____________________^        everything after arm3
731     //|    #[cfg(feature = "disabled")]
732     //|    arm4 = some_code(),
733     //|};
734     //|^
735     let found = arm_spans.try_fold(start, |start, range| {
736         let Some((end, next_start)) = range else {
737             // Shouldn't happen as macros can't expand to match arms, but treat this as though a `cfg` attribute were
738             // found.
739             return Err(());
740         };
741         let span = SpanData {
742             lo: start,
743             hi: end,
744             ctxt: SyntaxContext::root(),
745             parent: None,
746         }
747         .span();
748         (!span_contains_cfg(cx, span)).then(|| next_start).ok_or(())
749     });
750     match found {
751         Ok(start) => {
752             let span = SpanData {
753                 lo: start,
754                 hi: end,
755                 ctxt: SyntaxContext::root(),
756                 parent: None,
757             }
758             .span();
759             span_contains_cfg(cx, span)
760         },
761         Err(()) => true,
762     }
763 }
764
765 /// Checks if the given span contains a `#[cfg(..)]` attribute
766 fn span_contains_cfg(cx: &LateContext<'_>, s: Span) -> bool {
767     let Some(snip) = snippet_opt(cx, s) else {
768         // Assume true. This would require either an invalid span, or one which crosses file boundaries.
769         return true;
770     };
771     let mut pos = 0usize;
772     let mut iter = tokenize(&snip).map(|t| {
773         let start = pos;
774         pos += t.len;
775         (t.kind, start..pos)
776     });
777
778     // Search for the token sequence [`#`, `[`, `cfg`]
779     while iter.any(|(t, _)| matches!(t, TokenKind::Pound)) {
780         let mut iter = iter.by_ref().skip_while(|(t, _)| {
781             matches!(
782                 t,
783                 TokenKind::Whitespace | TokenKind::LineComment { .. } | TokenKind::BlockComment { .. }
784             )
785         });
786         if matches!(iter.next(), Some((TokenKind::OpenBracket, _)))
787             && matches!(iter.next(), Some((TokenKind::Ident, range)) if &snip[range.clone()] == "cfg")
788         {
789             return true;
790         }
791     }
792     false
793 }