From 63f6a79bf87cd5960c069bb45c88646519d096f8 Mon Sep 17 00:00:00 2001 From: Jason Newcomb Date: Fri, 8 Apr 2022 16:51:40 -0400 Subject: [PATCH] Don't lint various match lints when expanded by a proc-macro --- clippy_lints/src/matches/mod.rs | 5 +++- clippy_utils/src/source.rs | 19 +++++++++++++ tests/ui/auxiliary/proc_macro_with_span.rs | 32 ++++++++++++++++++++++ tests/ui/single_match_else.rs | 18 ++++++++++-- tests/ui/single_match_else.stderr | 11 ++++---- 5 files changed, 77 insertions(+), 8 deletions(-) create mode 100644 tests/ui/auxiliary/proc_macro_with_span.rs diff --git a/clippy_lints/src/matches/mod.rs b/clippy_lints/src/matches/mod.rs index e93b49465..854cf06ed 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 dbad607c5..beed73268 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(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` 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 000000000..8ea631f2b --- /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 b624a41a2..82387f3d8 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 21ea704b6..7756c6f20 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