-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New lint: single_field_patterns
#8157
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Closed
Closed
Changes from all commits
Commits
Show all changes
48 commits
Select commit
Hold shift + click to select a range
8a83fe3
init + tests
peter-shen-dev e75127c
poc - need to add blank fields; suggestion
peter-shen-dev de0c847
handle blank fields
peter-shen-dev 46ad2ab
pass function directly
peter-shen-dev eff7948
propogate spans upwards
peter-shen-dev 37b6287
Refactor for DRY and to get rid of recursion
peter-shen-dev fcddebf
renaming
peter-shen-dev 2ff63f8
convert tuple struct to named struct
peter-shen-dev 212b617
rename to SingleField
peter-shen-dev bf57033
refactor: move code
peter-shen-dev 9d62c70
refactor: rename
peter-shen-dev 73ed511
get span mapping
peter-shen-dev 4107c44
moved file, added suggestions
peter-shen-dev 868a41b
support unused patterns and bless
peter-shen-dev c382200
documentation
peter-shen-dev 64f356a
docs
peter-shen-dev 7ce79fd
docs
peter-shen-dev ea671fe
docs
peter-shen-dev 30c76c4
stop handling empty patterns
peter-shen-dev dbe9a2f
account for conditional compilation
peter-shen-dev 4363610
docs
peter-shen-dev 3aa0d17
allow lint in other tests
peter-shen-dev 5b50e5e
fix self-lints in my code
peter-shen-dev d9670e3
correct uitest name
peter-shen-dev 707b2ea
bless rename
peter-shen-dev 7f52f24
remove comment
peter-shen-dev 1ccef14
internal lints
peter-shen-dev beb68ea
handle dereferencing; refactor
peter-shen-dev aa76b40
refactor
peter-shen-dev 2dc1acf
inline unnecessary type params
peter-shen-dev 4de5add
yay we can handle enums after all
peter-shen-dev 79a3e7a
don't handle unions
peter-shen-dev 75a7189
comment
peter-shen-dev 7c90d09
Merge branch 'master' of https://github.com/rust-lang/rust-clippy
peter-shen-dev bf40298
correct applicability
peter-shen-dev 571289c
fix lints
peter-shen-dev e9eea7e
fix doctest
peter-shen-dev e1d867a
ignore macros
peter-shen-dev 5b7e4e9
stop handling tuples and arrays
peter-shen-dev 42189af
Revert "internal lints"
peter-shen-dev a0c3cc9
Revert "allow lint in other tests"
peter-shen-dev 04bd2e3
Revert "fix lints"
peter-shen-dev 7b0e6f1
fix tests/self-lint
peter-shen-dev 735e7f3
code review
peter-shen-dev 66414f0
whoops I shouldn't have pushed to master
peter-shen-dev 04512e9
change diagnostic
peter-shen-dev 82b5901
bless
peter-shen-dev d9a2e72
cargo dev fmt
peter-shen-dev File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
#![allow(rustc::usage_of_ty_tykind)] | ||
|
||
use clippy_utils::{ | ||
diagnostics::{multispan_sugg_with_applicability, span_lint_and_then}, | ||
higher::IfLetOrMatch, | ||
higher::WhileLet, | ||
source::snippet_opt, | ||
}; | ||
use rustc_errors::Applicability; | ||
use rustc_hir::{Expr, ExprKind, Local, MatchSource, Pat, PatKind, Stmt, StmtKind, UnOp}; | ||
use rustc_lint::{LateContext, LateLintPass}; | ||
use rustc_middle::ty; | ||
use rustc_session::{declare_lint_pass, declare_tool_lint}; | ||
use rustc_span::{symbol::Ident, Span}; | ||
use std::iter; | ||
|
||
declare_clippy_lint! { | ||
/// ### What it does | ||
/// Checks for patterns that only match a single field of a struct when they could directly access the field. | ||
/// | ||
/// ### Why is this bad? | ||
/// It requires more text and more information than directly accessing the field. | ||
/// | ||
/// ### Example | ||
/// ```rust | ||
/// # struct Struct { | ||
/// # field1: Option<i32>, | ||
/// # field2: Option<i32>, | ||
/// # } | ||
/// # fn foo(struct1: Struct) { | ||
/// // bad: | ||
/// match struct1 { | ||
/// Struct { field1: Some(n), .. } if n >= 50 => {}, | ||
/// Struct { field1: None, .. } => {}, | ||
/// _ => {}, | ||
/// } | ||
/// // better: | ||
/// match struct1.field1 { | ||
/// Some(n) if n >= 50 => {}, | ||
/// None => {}, | ||
/// _ => {}, | ||
/// } | ||
/// # } | ||
/// ``` | ||
#[clippy::version = "1.59.0"] | ||
pub SINGLE_FIELD_PATTERNS, | ||
style, | ||
"single-field patterns" | ||
} | ||
declare_lint_pass!(SingleFieldPatterns => [SINGLE_FIELD_PATTERNS]); | ||
|
||
/// This represents 0 or 1 fields being used. Where more may be used, `Option<SingleField>` is used | ||
/// where `None` represents the absence of a lint | ||
#[derive(Debug, Clone, Copy)] | ||
enum SingleField { | ||
Id { id: Ident, pattern: Span }, | ||
Unused, // The name "SingleField" is a lie but idk what's better. "AtMostOneField"? | ||
} | ||
|
||
impl PartialEq for SingleField { | ||
fn eq(&self, other: &Self) -> bool { | ||
match (self, other) { | ||
(SingleField::Id { id: id1, .. }, SingleField::Id { id: id2, .. }) => id1 == id2, | ||
(SingleField::Unused, SingleField::Unused) => true, | ||
_ => false, | ||
} | ||
} | ||
} | ||
|
||
impl SingleField { | ||
fn new<'a>(mut iter: impl Iterator<Item = (Ident, &'a Pat<'a>)>) -> Option<SingleField> { | ||
let one = iter.by_ref().find(|(_, pat)| !matches!(pat.kind, PatKind::Wild)); | ||
match one { | ||
Some((id, pat)) => { | ||
if pat.span.from_expansion() { | ||
return None; | ||
} | ||
if iter.all(|(_, other)| matches!(other.kind, PatKind::Wild)) { | ||
Some(SingleField::Id { id, pattern: pat.span }) | ||
} else { | ||
None | ||
} | ||
}, | ||
None => Some(SingleField::Unused), | ||
} | ||
} | ||
} | ||
|
||
/// This handles recursive patterns and flattens them out lazily | ||
/// e.g. 1 | (2 | 9) | 3..5 | ||
struct FlatPatterns<'hir, I> | ||
where | ||
I: Iterator<Item = &'hir Pat<'hir>>, | ||
{ | ||
patterns: I, | ||
stack: Vec<&'hir Pat<'hir>>, | ||
} | ||
|
||
impl<I: Iterator<Item = &'hir Pat<'hir>>> FlatPatterns<'hir, I> { | ||
flip1995 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
fn new(patterns: I) -> Self { | ||
Self { | ||
patterns, | ||
stack: Vec::new(), | ||
} | ||
} | ||
} | ||
|
||
impl<I: Iterator<Item = &'hir Pat<'hir>>> Iterator for FlatPatterns<'hir, I> { | ||
type Item = &'hir Pat<'hir>; | ||
|
||
fn next(&mut self) -> Option<Self::Item> { | ||
if self.stack.is_empty() { | ||
if let Some(pat) = self.patterns.next() { | ||
self.stack.push(pat); | ||
} else { | ||
return None; | ||
} | ||
} | ||
while let Some(pat) = self.stack.pop() { | ||
match pat.kind { | ||
PatKind::Or(pats) => self.stack.extend(pats), | ||
_ => return Some(pat), | ||
} | ||
} | ||
unreachable!("Or always has 2 patterns, so one of the prior returns must return"); | ||
} | ||
} | ||
|
||
fn find_sf_lint<'hir>( | ||
cx: &LateContext<'_>, | ||
patterns: impl Iterator<Item = &'hir Pat<'hir>>, | ||
) -> Option<(SingleField, Vec<(Span, String)>)> { | ||
let fields = FlatPatterns::new(patterns).map(|p| { | ||
( | ||
p.span, | ||
match p.kind { | ||
PatKind::Wild => Some(SingleField::Unused), | ||
PatKind::Struct(_, pats, _) => SingleField::new(pats.iter().map(|field| (field.ident, field.pat))), | ||
_ => None, | ||
}, | ||
) | ||
}); | ||
let mut spans = Vec::<(Span, String)>::new(); | ||
let mut the_one: Option<SingleField> = None; | ||
for (target, sf) in fields { | ||
if target.from_expansion() { | ||
return None; | ||
} | ||
if let Some(sf) = sf { | ||
match sf { | ||
SingleField::Unused => { | ||
spans.push((target, String::from("_"))); | ||
}, | ||
SingleField::Id { pattern, .. } => { | ||
if let Some(str) = snippet_opt(cx, pattern) { | ||
spans.push((target, str)); | ||
} else { | ||
return None; | ||
} | ||
if let Some(one) = the_one { | ||
if sf != one { | ||
return None; | ||
} | ||
} else { | ||
the_one = Some(sf); | ||
} | ||
}, | ||
} | ||
} else { | ||
return None; | ||
} | ||
} | ||
the_one.map(|one| (one, spans)) | ||
} | ||
|
||
fn apply_lint_sf(cx: &LateContext<'_>, span: Span, sugg: impl IntoIterator<Item = (Span, String)>) { | ||
span_lint_and_then( | ||
cx, | ||
SINGLE_FIELD_PATTERNS, | ||
span, | ||
"this single-variant pattern only matches one field", | ||
|diag| { | ||
multispan_sugg_with_applicability( | ||
diag, | ||
"try accessing this field directly", | ||
Applicability::MaybeIncorrect, | ||
sugg, | ||
); | ||
}, | ||
); | ||
} | ||
|
||
fn remove_deref<'a>(mut scrutinee: &'a Expr<'a>) -> &'a Expr<'a> { | ||
flip1995 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// it would be wrong to convert something like `if let (x, _) = *a` into `if let x = *a.0` | ||
while let ExprKind::Unary(UnOp::Deref, expr) = scrutinee.kind { | ||
scrutinee = expr; | ||
} | ||
scrutinee | ||
} | ||
|
||
fn lint_sf<'hir>( | ||
cx: &LateContext<'_>, | ||
overall_span: Span, | ||
scrutinee: &Expr<'_>, | ||
patterns: impl Iterator<Item = &'hir Pat<'hir>>, | ||
) { | ||
if scrutinee.span.from_expansion() { | ||
return; | ||
} | ||
let scrutinee_name = if let Some(name) = snippet_opt(cx, remove_deref(scrutinee).span) { | ||
name | ||
} else { | ||
return; | ||
}; | ||
match cx.typeck_results().expr_ty(scrutinee).kind() { | ||
ty::Adt(def, ..) if def.is_struct() => { | ||
if let Some((field, mut spans)) = find_sf_lint(cx, patterns) { | ||
spans.push(( | ||
scrutinee.span, | ||
match field { | ||
SingleField::Id { id, .. } => format!("{}.{}", scrutinee_name, id.as_str()), | ||
SingleField::Unused => return, | ||
}, | ||
)); | ||
apply_lint_sf(cx, overall_span, spans); | ||
} | ||
}, | ||
_ => (), | ||
}; | ||
} | ||
|
||
impl LateLintPass<'_> for SingleFieldPatterns { | ||
fn check_expr(&mut self, cx: &LateContext<'tcx>, expr: &Expr<'tcx>) { | ||
if expr.span.from_expansion() { | ||
return; | ||
} | ||
match IfLetOrMatch::parse(cx, expr) { | ||
Some(IfLetOrMatch::Match(scrutinee, arms, MatchSource::Normal)) => { | ||
lint_sf(cx, expr.span, scrutinee, arms.iter().map(|arm| arm.pat)); | ||
}, | ||
Some(IfLetOrMatch::IfLet(scrutinee, pat, ..)) => lint_sf(cx, expr.span, scrutinee, iter::once(pat)), | ||
_ => { | ||
if let Some(WhileLet { let_pat, let_expr, .. }) = WhileLet::hir(expr) { | ||
lint_sf(cx, expr.span, let_expr, iter::once(let_pat)); | ||
} | ||
}, | ||
}; | ||
} | ||
|
||
fn check_stmt(&mut self, cx: &LateContext<'tcx>, stmt: &Stmt<'tcx>) { | ||
if stmt.span.from_expansion() { | ||
return; | ||
} | ||
if let StmtKind::Local(Local { | ||
pat, | ||
init: Some(scrutinee), | ||
.. | ||
}) = stmt.kind | ||
{ | ||
lint_sf(cx, stmt.span, scrutinee, iter::once(*pat)); | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.