Skip to content

Commit 1378f58

Browse files
committed
Added restriction lint: pattern-type-mismatch
1 parent eff3bc5 commit 1378f58

15 files changed

+876
-2
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -1242,6 +1242,7 @@ Released 2018-09-13
12421242
[`panicking_unwrap`]: https://rust-lang.github.io/rust-clippy/master/index.html#panicking_unwrap
12431243
[`partialeq_ne_impl`]: https://rust-lang.github.io/rust-clippy/master/index.html#partialeq_ne_impl
12441244
[`path_buf_push_overwrite`]: https://rust-lang.github.io/rust-clippy/master/index.html#path_buf_push_overwrite
1245+
[`pattern_type_mismatch`]: https://rust-lang.github.io/rust-clippy/master/index.html#pattern_type_mismatch
12451246
[`possible_missing_comma`]: https://rust-lang.github.io/rust-clippy/master/index.html#possible_missing_comma
12461247
[`precedence`]: https://rust-lang.github.io/rust-clippy/master/index.html#precedence
12471248
[`print_literal`]: https://rust-lang.github.io/rust-clippy/master/index.html#print_literal

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66

77
A collection of lints to catch common mistakes and improve your [Rust](https://github.com/rust-lang/rust) code.
88

9-
[There are 348 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
9+
[There are 349 lints included in this crate!](https://rust-lang.github.io/rust-clippy/master/index.html)
1010

1111
We have a bunch of lint categories to allow you to choose how much Clippy is supposed to ~~annoy~~ help you:
1212

clippy_lints/src/lib.rs

+4
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,7 @@ pub mod overflow_check_conditional;
269269
pub mod panic_unimplemented;
270270
pub mod partialeq_ne_impl;
271271
pub mod path_buf_push_overwrite;
272+
pub mod pattern_type_mismatch;
272273
pub mod precedence;
273274
pub mod ptr;
274275
pub mod ptr_offset_with_cast;
@@ -714,6 +715,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
714715
&panic_unimplemented::UNREACHABLE,
715716
&partialeq_ne_impl::PARTIALEQ_NE_IMPL,
716717
&path_buf_push_overwrite::PATH_BUF_PUSH_OVERWRITE,
718+
&pattern_type_mismatch::PATTERN_TYPE_MISMATCH,
717719
&precedence::PRECEDENCE,
718720
&ptr::CMP_NULL,
719721
&ptr::MUT_FROM_REF,
@@ -991,6 +993,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
991993
store.register_early_pass(|| box utils::internal_lints::ProduceIce);
992994
store.register_late_pass(|| box let_underscore::LetUnderscore);
993995
store.register_late_pass(|| box atomic_ordering::AtomicOrdering);
996+
store.register_late_pass(|| box pattern_type_mismatch::PatternTypeMismatch);
994997

995998
store.register_group(true, "clippy::restriction", Some("clippy_restriction"), vec![
996999
LintId::of(&arithmetic::FLOAT_ARITHMETIC),
@@ -1023,6 +1026,7 @@ pub fn register_plugins(store: &mut rustc_lint::LintStore, sess: &Session, conf:
10231026
LintId::of(&panic_unimplemented::TODO),
10241027
LintId::of(&panic_unimplemented::UNIMPLEMENTED),
10251028
LintId::of(&panic_unimplemented::UNREACHABLE),
1029+
LintId::of(&pattern_type_mismatch::PATTERN_TYPE_MISMATCH),
10261030
LintId::of(&shadow::SHADOW_REUSE),
10271031
LintId::of(&shadow::SHADOW_SAME),
10281032
LintId::of(&strings::STRING_ADD),
+276
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
use crate::utils::{last_path_segment, span_help_and_lint};
2+
use rustc::lint::in_external_macro;
3+
use rustc::ty::subst::SubstsRef;
4+
use rustc::ty::{AdtDef, FieldDef, Ty, TyKind, VariantDef};
5+
use rustc_hir::{
6+
intravisit, Body, Expr, ExprKind, FieldPat, FnDecl, HirId, LocalSource, MatchSource, Mutability, Pat, PatKind,
7+
QPath, Stmt, StmtKind,
8+
};
9+
use rustc_lint::{LateContext, LateLintPass, LintContext};
10+
use rustc_session::{declare_lint_pass, declare_tool_lint};
11+
use rustc_span::source_map::Span;
12+
13+
declare_clippy_lint! {
14+
/// **What it does:** Checks for patterns that aren't exact representations of the types
15+
/// they are applied to.
16+
///
17+
/// **Why is this bad?** It isn't bad in general. But in some contexts it can be desirable
18+
/// because it increases ownership hints in the code, and will guard against some changes
19+
/// in ownership.
20+
///
21+
/// **Known problems:** None.
22+
///
23+
/// **Example:**
24+
///
25+
/// ```rust,ignore
26+
/// // Bad
27+
/// let value = &Some(Box::new(23));
28+
/// match value {
29+
/// Some(inner) => println!("{}", inner),
30+
/// None => println!("none"),
31+
/// }
32+
///
33+
/// // Good
34+
/// let value = &Some(Box::new(23));
35+
/// match *value {
36+
/// Some(ref inner) => println!("{}", inner),
37+
/// None => println!("none"),
38+
/// }
39+
/// ```
40+
pub PATTERN_TYPE_MISMATCH,
41+
restriction,
42+
"type of pattern does not match the expression type"
43+
}
44+
45+
declare_lint_pass!(PatternTypeMismatch => [PATTERN_TYPE_MISMATCH]);
46+
47+
impl<'a, 'tcx> LateLintPass<'a, 'tcx> for PatternTypeMismatch {
48+
fn check_stmt(&mut self, cx: &LateContext<'a, 'tcx>, stmt: &'tcx Stmt<'_>) {
49+
if let StmtKind::Local(ref local) = stmt.kind {
50+
if let Some(init) = &local.init {
51+
if let Some(init_ty) = cx.tables.node_type_opt(init.hir_id) {
52+
let pat = &local.pat;
53+
if in_external_macro(cx.sess(), pat.span) {
54+
return;
55+
}
56+
let deref_possible = match local.source {
57+
LocalSource::Normal => DerefPossible::Possible,
58+
_ => DerefPossible::Impossible,
59+
};
60+
apply_lint(cx, pat, init_ty, deref_possible);
61+
}
62+
}
63+
}
64+
}
65+
66+
fn check_expr(&mut self, cx: &LateContext<'a, 'tcx>, expr: &'tcx Expr<'_>) {
67+
if let ExprKind::Match(ref expr, arms, source) = expr.kind {
68+
match source {
69+
MatchSource::Normal | MatchSource::IfLetDesugar { .. } | MatchSource::WhileLetDesugar => {
70+
if let Some(expr_ty) = cx.tables.node_type_opt(expr.hir_id) {
71+
'pattern_checks: for arm in arms {
72+
let pat = &arm.pat;
73+
if in_external_macro(cx.sess(), pat.span) {
74+
continue 'pattern_checks;
75+
}
76+
if apply_lint(cx, pat, expr_ty, DerefPossible::Possible) {
77+
break 'pattern_checks;
78+
}
79+
}
80+
}
81+
},
82+
_ => (),
83+
}
84+
}
85+
}
86+
87+
fn check_fn(
88+
&mut self,
89+
cx: &LateContext<'a, 'tcx>,
90+
_: intravisit::FnKind<'tcx>,
91+
_: &'tcx FnDecl<'_>,
92+
body: &'tcx Body<'_>,
93+
_: Span,
94+
hir_id: HirId,
95+
) {
96+
if let Some(fn_sig) = cx.tables.liberated_fn_sigs().get(hir_id) {
97+
for (param, ty) in body.params.iter().zip(fn_sig.inputs().iter()) {
98+
apply_lint(cx, &param.pat, ty, DerefPossible::Impossible);
99+
}
100+
}
101+
}
102+
}
103+
104+
#[derive(Debug, Clone, Copy)]
105+
enum DerefPossible {
106+
Possible,
107+
Impossible,
108+
}
109+
110+
fn apply_lint<'a, 'tcx>(
111+
cx: &LateContext<'a, 'tcx>,
112+
pat: &Pat<'_>,
113+
expr_ty: Ty<'tcx>,
114+
deref_possible: DerefPossible,
115+
) -> bool {
116+
let maybe_mismatch = find_first_mismatch(cx, pat, expr_ty, Level::Top);
117+
if let Some((span, mutability, level)) = maybe_mismatch {
118+
span_help_and_lint(
119+
cx,
120+
PATTERN_TYPE_MISMATCH,
121+
span,
122+
"type of pattern does not match the expression type",
123+
&format!(
124+
"{}explicitly match against a `{}` pattern and adjust the enclosed variable bindings",
125+
match (deref_possible, level) {
126+
(DerefPossible::Possible, Level::Top) => "use `*` to dereference the match expression or ",
127+
_ => "",
128+
},
129+
match mutability {
130+
Mutability::Mut => "&mut _",
131+
Mutability::Not => "&_",
132+
},
133+
),
134+
);
135+
true
136+
} else {
137+
false
138+
}
139+
}
140+
141+
#[derive(Debug, Copy, Clone)]
142+
enum Level {
143+
Top,
144+
Lower,
145+
}
146+
147+
#[allow(rustc::usage_of_ty_tykind)]
148+
fn find_first_mismatch<'a, 'tcx>(
149+
cx: &LateContext<'a, 'tcx>,
150+
pat: &Pat<'_>,
151+
ty: Ty<'tcx>,
152+
level: Level,
153+
) -> Option<(Span, Mutability, Level)> {
154+
if let PatKind::Ref(ref sub_pat, _) = pat.kind {
155+
if let TyKind::Ref(_, sub_ty, _) = ty.kind {
156+
return find_first_mismatch(cx, sub_pat, sub_ty, Level::Lower);
157+
}
158+
}
159+
160+
if let TyKind::Ref(_, _, mutability) = ty.kind {
161+
if is_non_ref_pattern(&pat.kind) {
162+
return Some((pat.span, mutability, level));
163+
}
164+
}
165+
166+
if let PatKind::Struct(ref qpath, ref field_pats, _) = pat.kind {
167+
if let TyKind::Adt(ref adt_def, ref substs_ref) = ty.kind {
168+
if let Some(variant) = get_variant(adt_def, qpath) {
169+
let field_defs = &variant.fields;
170+
return find_first_mismatch_in_struct(cx, field_pats, field_defs, substs_ref);
171+
}
172+
}
173+
}
174+
175+
if let PatKind::TupleStruct(ref qpath, ref pats, _) = pat.kind {
176+
if let TyKind::Adt(ref adt_def, ref substs_ref) = ty.kind {
177+
if let Some(variant) = get_variant(adt_def, qpath) {
178+
let field_defs = &variant.fields;
179+
let ty_iter = field_defs.iter().map(|field_def| field_def.ty(cx.tcx, substs_ref));
180+
return find_first_mismatch_in_tuple(cx, pats, ty_iter);
181+
}
182+
}
183+
}
184+
185+
if let PatKind::Tuple(ref pats, _) = pat.kind {
186+
if let TyKind::Tuple(..) = ty.kind {
187+
return find_first_mismatch_in_tuple(cx, pats, ty.tuple_fields());
188+
}
189+
}
190+
191+
if let PatKind::Or(sub_pats) = pat.kind {
192+
for pat in sub_pats {
193+
let maybe_mismatch = find_first_mismatch(cx, pat, ty, level);
194+
if let Some(mismatch) = maybe_mismatch {
195+
return Some(mismatch);
196+
}
197+
}
198+
}
199+
200+
None
201+
}
202+
203+
fn get_variant<'a>(adt_def: &'a AdtDef, qpath: &QPath<'_>) -> Option<&'a VariantDef> {
204+
if adt_def.is_struct() {
205+
if let Some(variant) = adt_def.variants.iter().next() {
206+
return Some(variant);
207+
}
208+
}
209+
210+
if adt_def.is_enum() {
211+
let pat_ident = last_path_segment(qpath).ident;
212+
for variant in &adt_def.variants {
213+
if variant.ident == pat_ident {
214+
return Some(variant);
215+
}
216+
}
217+
}
218+
219+
None
220+
}
221+
222+
fn find_first_mismatch_in_tuple<'a, 'tcx, I>(
223+
cx: &LateContext<'a, 'tcx>,
224+
pats: &[&Pat<'_>],
225+
ty_iter_src: I,
226+
) -> Option<(Span, Mutability, Level)>
227+
where
228+
I: IntoIterator<Item = Ty<'tcx>>,
229+
{
230+
let mut field_tys = ty_iter_src.into_iter();
231+
'fields: for pat in pats {
232+
let field_ty = if let Some(ty) = field_tys.next() {
233+
ty
234+
} else {
235+
break 'fields;
236+
};
237+
238+
let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
239+
if let Some(mismatch) = maybe_mismatch {
240+
return Some(mismatch);
241+
}
242+
}
243+
244+
None
245+
}
246+
247+
fn find_first_mismatch_in_struct<'a, 'tcx>(
248+
cx: &LateContext<'a, 'tcx>,
249+
field_pats: &[FieldPat<'_>],
250+
field_defs: &[FieldDef],
251+
substs_ref: SubstsRef<'tcx>,
252+
) -> Option<(Span, Mutability, Level)> {
253+
for field_pat in field_pats {
254+
'definitions: for field_def in field_defs {
255+
if field_pat.ident == field_def.ident {
256+
let field_ty = field_def.ty(cx.tcx, substs_ref);
257+
let pat = &field_pat.pat;
258+
let maybe_mismatch = find_first_mismatch(cx, pat, field_ty, Level::Lower);
259+
if let Some(mismatch) = maybe_mismatch {
260+
return Some(mismatch);
261+
}
262+
break 'definitions;
263+
}
264+
}
265+
}
266+
267+
None
268+
}
269+
270+
fn is_non_ref_pattern(pat_kind: &PatKind<'_>) -> bool {
271+
match pat_kind {
272+
PatKind::Struct(..) | PatKind::Tuple(..) | PatKind::TupleStruct(..) | PatKind::Path(..) => true,
273+
PatKind::Or(sub_pats) => sub_pats.iter().any(|pat| is_non_ref_pattern(&pat.kind)),
274+
_ => false,
275+
}
276+
}

src/lintlist/mod.rs

+8-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use lint::Lint;
66
pub use lint::LINT_LEVELS;
77

88
// begin lint list, do not remove this comment, it’s used in `update_lints`
9-
pub const ALL_LINTS: [Lint; 348] = [
9+
pub const ALL_LINTS: [Lint; 349] = [
1010
Lint {
1111
name: "absurd_extreme_comparisons",
1212
group: "correctness",
@@ -1575,6 +1575,13 @@ pub const ALL_LINTS: [Lint; 348] = [
15751575
deprecation: None,
15761576
module: "path_buf_push_overwrite",
15771577
},
1578+
Lint {
1579+
name: "pattern_type_mismatch",
1580+
group: "restriction",
1581+
desc: "type of pattern does not match the expression type",
1582+
deprecation: None,
1583+
module: "pattern_type_mismatch",
1584+
},
15781585
Lint {
15791586
name: "possible_missing_comma",
15801587
group: "correctness",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#![allow(clippy::all)]
2+
#![warn(clippy::pattern_type_mismatch)]
3+
4+
fn main() {}
5+
6+
fn should_lint() {
7+
let value = &Some(23);
8+
match value {
9+
Some(_) => (),
10+
_ => (),
11+
}
12+
13+
let value = &mut Some(23);
14+
match value {
15+
Some(_) => (),
16+
_ => (),
17+
}
18+
}
19+
20+
fn should_not_lint() {
21+
let value = &Some(23);
22+
match value {
23+
&Some(_) => (),
24+
_ => (),
25+
}
26+
match *value {
27+
Some(_) => (),
28+
_ => (),
29+
}
30+
31+
let value = &mut Some(23);
32+
match value {
33+
&mut Some(_) => (),
34+
_ => (),
35+
}
36+
match *value {
37+
Some(_) => (),
38+
_ => (),
39+
}
40+
}

0 commit comments

Comments
 (0)