|
| 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, ¶m.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 | +} |
0 commit comments