about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--clippy_lints/src/matches/mod.rs5
-rw-r--r--clippy_utils/src/source.rs19
-rw-r--r--tests/ui/auxiliary/proc_macro_with_span.rs32
-rw-r--r--tests/ui/single_match_else.rs18
-rw-r--r--tests/ui/single_match_else.stderr11
5 files changed, 77 insertions, 8 deletions
diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs
index e93b494653f..854cf06ed81 100644
--- a/clippy_lints/src/matches/mod.rs
+++ b/clippy_lints/src/matches/mod.rs
@@ -1,4 +1,4 @@
-use clippy_utils::source::{snippet_opt, walk_span_to_context};
+use clippy_utils::source::{snippet_opt, span_starts_with, walk_span_to_context};
 use clippy_utils::{meets_msrv, msrvs};
 use rustc_hir::{Arm, Expr, ExprKind, Local, MatchSource, Pat};
 use rustc_lexer::{tokenize, TokenKind};
@@ -653,6 +653,9 @@ impl<'tcx> LateLintPass<'tcx> for Matches {
         }
 
         if let ExprKind::Match(ex, arms, source) = expr.kind {
+            if !span_starts_with(cx, expr.span, "match") {
+                return;
+            }
             if !contains_cfg_arm(cx, expr, ex, arms) {
                 if source == MatchSource::Normal {
                     if !(meets_msrv(self.msrv.as_ref(), &msrvs::MATCHES_MACRO)
diff --git a/clippy_utils/src/source.rs b/clippy_utils/src/source.rs
index dbad607c58e..beed7326803 100644
--- a/clippy_utils/src/source.rs
+++ b/clippy_utils/src/source.rs
@@ -7,9 +7,28 @@ use rustc_errors::Applicability;
 use rustc_hir::{Expr, ExprKind};
 use rustc_lint::{LateContext, LintContext};
 use rustc_span::hygiene;
+use rustc_span::source_map::SourceMap;
 use rustc_span::{BytePos, Pos, Span, SyntaxContext};
 use std::borrow::Cow;
 
+/// Checks if the span starts with the given text. This will return false if the span crosses
+/// multiple files or if source is not available.
+///
+/// This is used to check for proc macros giving unhelpful spans to things.
+pub fn span_starts_with<T: LintContext>(cx: &T, span: Span, text: &str) -> bool {
+    fn helper(sm: &SourceMap, span: Span, text: &str) -> bool {
+        let pos = sm.lookup_byte_offset(span.lo());
+        let Some(ref src) = pos.sf.src else {
+            return false;
+        };
+        let end = span.hi() - pos.sf.start_pos;
+        src.get(pos.pos.0 as usize..end.0 as usize)
+            // Expression spans can include wrapping parenthesis. Remove them first.
+            .map_or(false, |s| s.trim_start_matches('(').starts_with(text))
+    }
+    helper(cx.sess().source_map(), span, text)
+}
+
 /// Like `snippet_block`, but add braces if the expr is not an `ExprKind::Block`.
 /// Also takes an `Option<String>` which can be put inside the braces.
 pub fn expr_block<'a, T: LintContext>(
diff --git a/tests/ui/auxiliary/proc_macro_with_span.rs b/tests/ui/auxiliary/proc_macro_with_span.rs
new file mode 100644
index 00000000000..8ea631f2bbd
--- /dev/null
+++ b/tests/ui/auxiliary/proc_macro_with_span.rs
@@ -0,0 +1,32 @@
+// compile-flags: --emit=link
+// no-prefer-dynamic
+
+#![crate_type = "proc-macro"]
+
+extern crate proc_macro;
+
+use proc_macro::{token_stream::IntoIter, Group, Span, TokenStream, TokenTree};
+
+#[proc_macro]
+pub fn with_span(input: TokenStream) -> TokenStream {
+    let mut iter = input.into_iter();
+    let span = iter.next().unwrap().span();
+    let mut res = TokenStream::new();
+    write_with_span(span, iter, &mut res);
+    res
+}
+
+fn write_with_span(s: Span, input: IntoIter, out: &mut TokenStream) {
+    for mut tt in input {
+        if let TokenTree::Group(g) = tt {
+            let mut stream = TokenStream::new();
+            write_with_span(s, g.stream().into_iter(), &mut stream);
+            let mut group = Group::new(g.delimiter(), stream);
+            group.set_span(s);
+            out.extend([TokenTree::Group(group)]);
+        } else {
+            tt.set_span(s);
+            out.extend([tt]);
+        }
+    }
+}
diff --git a/tests/ui/single_match_else.rs b/tests/ui/single_match_else.rs
index b624a41a29b..82387f3d80b 100644
--- a/tests/ui/single_match_else.rs
+++ b/tests/ui/single_match_else.rs
@@ -1,7 +1,12 @@
+// aux-build: proc_macro_with_span.rs
+
 #![warn(clippy::single_match_else)]
 #![allow(clippy::needless_return)]
 #![allow(clippy::no_effect)]
 
+extern crate proc_macro_with_span;
+use proc_macro_with_span::with_span;
+
 enum ExprNode {
     ExprAddrOf,
     Butterflies,
@@ -11,13 +16,22 @@ enum ExprNode {
 static NODE: ExprNode = ExprNode::Unicorns;
 
 fn unwrap_addr() -> Option<&'static ExprNode> {
-    match ExprNode::Butterflies {
+    let _ = match ExprNode::Butterflies {
         ExprNode::ExprAddrOf => Some(&NODE),
         _ => {
             let x = 5;
             None
         },
-    }
+    };
+
+    // Don't lint
+    with_span!(span match ExprNode::Butterflies {
+        ExprNode::ExprAddrOf => Some(&NODE),
+        _ => {
+            let x = 5;
+            None
+        },
+    })
 }
 
 macro_rules! unwrap_addr {
diff --git a/tests/ui/single_match_else.stderr b/tests/ui/single_match_else.stderr
index 21ea704b62a..7756c6f204e 100644
--- a/tests/ui/single_match_else.stderr
+++ b/tests/ui/single_match_else.stderr
@@ -1,22 +1,23 @@
 error: you seem to be trying to use `match` for destructuring a single pattern. Consider using `if let`
-  --> $DIR/single_match_else.rs:14:5
+  --> $DIR/single_match_else.rs:19:13
    |
-LL | /     match ExprNode::Butterflies {
+LL |       let _ = match ExprNode::Butterflies {
+   |  _____________^
 LL | |         ExprNode::ExprAddrOf => Some(&NODE),
 LL | |         _ => {
 LL | |             let x = 5;
 LL | |             None
 LL | |         },
-LL | |     }
+LL | |     };
    | |_____^
    |
    = note: `-D clippy::single-match-else` implied by `-D warnings`
 help: try this
    |
-LL ~     if let ExprNode::ExprAddrOf = ExprNode::Butterflies { Some(&NODE) } else {
+LL ~     let _ = if let ExprNode::ExprAddrOf = ExprNode::Butterflies { Some(&NODE) } else {
 LL +         let x = 5;
 LL +         None
-LL +     }
+LL ~     };
    |
 
 error: aborting due to previous error