]> git.lizzy.rs Git - rust.git/blob - crates/hir-ty/src/diagnostics/expr.rs
Implement unstable RFC 1872 `exhaustive_patterns`
[rust.git] / crates / hir-ty / src / diagnostics / expr.rs
1 //! Various diagnostics for expressions that are collected together in one pass
2 //! through the body using inference results: mismatched arg counts, missing
3 //! fields, etc.
4
5 use std::fmt;
6 use std::sync::Arc;
7
8 use hir_def::{path::path, resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule};
9 use hir_expand::name;
10 use itertools::Either;
11 use itertools::Itertools;
12 use rustc_hash::FxHashSet;
13 use typed_arena::Arena;
14
15 use crate::{
16     db::HirDatabase,
17     diagnostics::match_check::{
18         self,
19         deconstruct_pat::DeconstructedPat,
20         usefulness::{compute_match_usefulness, MatchCheckCtx},
21     },
22     display::HirDisplay,
23     InferenceResult, Ty, TyExt,
24 };
25
26 pub(crate) use hir_def::{
27     body::Body,
28     expr::{Expr, ExprId, MatchArm, Pat, PatId},
29     LocalFieldId, VariantId,
30 };
31
32 pub enum BodyValidationDiagnostic {
33     RecordMissingFields {
34         record: Either<ExprId, PatId>,
35         variant: VariantId,
36         missed_fields: Vec<LocalFieldId>,
37     },
38     ReplaceFilterMapNextWithFindMap {
39         method_call_expr: ExprId,
40     },
41     MissingMatchArms {
42         match_expr: ExprId,
43         uncovered_patterns: String,
44     },
45 }
46
47 impl BodyValidationDiagnostic {
48     pub fn collect(db: &dyn HirDatabase, owner: DefWithBodyId) -> Vec<BodyValidationDiagnostic> {
49         let _p = profile::span("BodyValidationDiagnostic::collect");
50         let infer = db.infer(owner);
51         let mut validator = ExprValidator::new(owner, infer);
52         validator.validate_body(db);
53         validator.diagnostics
54     }
55 }
56
57 struct ExprValidator {
58     owner: DefWithBodyId,
59     infer: Arc<InferenceResult>,
60     pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
61 }
62
63 impl ExprValidator {
64     fn new(owner: DefWithBodyId, infer: Arc<InferenceResult>) -> ExprValidator {
65         ExprValidator { owner, infer, diagnostics: Vec::new() }
66     }
67
68     fn validate_body(&mut self, db: &dyn HirDatabase) {
69         let body = db.body(self.owner);
70         let mut filter_map_next_checker = None;
71
72         for (id, expr) in body.exprs.iter() {
73             if let Some((variant, missed_fields, true)) =
74                 record_literal_missing_fields(db, &self.infer, id, expr)
75             {
76                 self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
77                     record: Either::Left(id),
78                     variant,
79                     missed_fields,
80                 });
81             }
82
83             match expr {
84                 Expr::Match { expr, arms } => {
85                     self.validate_match(id, *expr, arms, db, self.infer.clone());
86                 }
87                 Expr::Call { .. } | Expr::MethodCall { .. } => {
88                     self.validate_call(db, id, expr, &mut filter_map_next_checker);
89                 }
90                 _ => {}
91             }
92         }
93         for (id, pat) in body.pats.iter() {
94             if let Some((variant, missed_fields, true)) =
95                 record_pattern_missing_fields(db, &self.infer, id, pat)
96             {
97                 self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
98                     record: Either::Right(id),
99                     variant,
100                     missed_fields,
101                 });
102             }
103         }
104     }
105
106     fn validate_call(
107         &mut self,
108         db: &dyn HirDatabase,
109         call_id: ExprId,
110         expr: &Expr,
111         filter_map_next_checker: &mut Option<FilterMapNextChecker>,
112     ) {
113         // Check that the number of arguments matches the number of parameters.
114
115         // FIXME: Due to shortcomings in the current type system implementation, only emit this
116         // diagnostic if there are no type mismatches in the containing function.
117         if self.infer.expr_type_mismatches().next().is_some() {
118             return;
119         }
120
121         match expr {
122             Expr::MethodCall { receiver, .. } => {
123                 let (callee, _) = match self.infer.method_resolution(call_id) {
124                     Some(it) => it,
125                     None => return,
126                 };
127
128                 if filter_map_next_checker
129                     .get_or_insert_with(|| {
130                         FilterMapNextChecker::new(&self.owner.resolver(db.upcast()), db)
131                     })
132                     .check(call_id, receiver, &callee)
133                     .is_some()
134                 {
135                     self.diagnostics.push(
136                         BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap {
137                             method_call_expr: call_id,
138                         },
139                     );
140                 }
141             }
142             _ => return,
143         };
144     }
145
146     fn validate_match(
147         &mut self,
148         id: ExprId,
149         match_expr: ExprId,
150         arms: &[MatchArm],
151         db: &dyn HirDatabase,
152         infer: Arc<InferenceResult>,
153     ) {
154         let body = db.body(self.owner);
155
156         let match_expr_ty = &infer[match_expr];
157         if match_expr_ty.is_unknown() {
158             return;
159         }
160
161         let pattern_arena = Arena::new();
162         let cx = MatchCheckCtx::new(self.owner.module(db.upcast()), self.owner, db, &pattern_arena);
163
164         let mut m_arms = Vec::with_capacity(arms.len());
165         let mut has_lowering_errors = false;
166         for arm in arms {
167             if let Some(pat_ty) = infer.type_of_pat.get(arm.pat) {
168                 // We only include patterns whose type matches the type
169                 // of the match expression. If we had an InvalidMatchArmPattern
170                 // diagnostic or similar we could raise that in an else
171                 // block here.
172                 //
173                 // When comparing the types, we also have to consider that rustc
174                 // will automatically de-reference the match expression type if
175                 // necessary.
176                 //
177                 // FIXME we should use the type checker for this.
178                 if (pat_ty == match_expr_ty
179                     || match_expr_ty
180                         .as_reference()
181                         .map(|(match_expr_ty, ..)| match_expr_ty == pat_ty)
182                         .unwrap_or(false))
183                     && types_of_subpatterns_do_match(arm.pat, &body, &infer)
184                 {
185                     // If we had a NotUsefulMatchArm diagnostic, we could
186                     // check the usefulness of each pattern as we added it
187                     // to the matrix here.
188                     let m_arm = match_check::MatchArm {
189                         pat: self.lower_pattern(&cx, arm.pat, db, &body, &mut has_lowering_errors),
190                         has_guard: arm.guard.is_some(),
191                     };
192                     m_arms.push(m_arm);
193                     if !has_lowering_errors {
194                         continue;
195                     }
196                 }
197             }
198
199             // If we can't resolve the type of a pattern, or the pattern type doesn't
200             // fit the match expression, we skip this diagnostic. Skipping the entire
201             // diagnostic rather than just not including this match arm is preferred
202             // to avoid the chance of false positives.
203             cov_mark::hit!(validate_match_bailed_out);
204             return;
205         }
206
207         let report = compute_match_usefulness(&cx, &m_arms, match_expr_ty);
208
209         // FIXME Report unreacheble arms
210         // https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200
211
212         let witnesses = report.non_exhaustiveness_witnesses;
213         if !witnesses.is_empty() {
214             self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
215                 match_expr: id,
216                 uncovered_patterns: missing_match_arms(&cx, match_expr_ty, witnesses, arms),
217             });
218         }
219     }
220
221     fn lower_pattern<'p>(
222         &self,
223         cx: &MatchCheckCtx<'_, 'p>,
224         pat: PatId,
225         db: &dyn HirDatabase,
226         body: &Body,
227         have_errors: &mut bool,
228     ) -> &'p DeconstructedPat<'p> {
229         let mut patcx = match_check::PatCtxt::new(db, &self.infer, body);
230         let pattern = patcx.lower_pattern(pat);
231         let pattern = cx.pattern_arena.alloc(DeconstructedPat::from_pat(cx, &pattern));
232         if !patcx.errors.is_empty() {
233             *have_errors = true;
234         }
235         pattern
236     }
237 }
238
239 struct FilterMapNextChecker {
240     filter_map_function_id: Option<hir_def::FunctionId>,
241     next_function_id: Option<hir_def::FunctionId>,
242     prev_filter_map_expr_id: Option<ExprId>,
243 }
244
245 impl FilterMapNextChecker {
246     fn new(resolver: &hir_def::resolver::Resolver, db: &dyn HirDatabase) -> Self {
247         // Find and store the FunctionIds for Iterator::filter_map and Iterator::next
248         let iterator_path = path![core::iter::Iterator];
249         let mut filter_map_function_id = None;
250         let mut next_function_id = None;
251
252         if let Some(iterator_trait_id) = resolver.resolve_known_trait(db.upcast(), &iterator_path) {
253             let iterator_trait_items = &db.trait_data(iterator_trait_id).items;
254             for item in iterator_trait_items.iter() {
255                 if let (name, AssocItemId::FunctionId(id)) = item {
256                     if *name == name![filter_map] {
257                         filter_map_function_id = Some(*id);
258                     }
259                     if *name == name![next] {
260                         next_function_id = Some(*id);
261                     }
262                 }
263                 if filter_map_function_id.is_some() && next_function_id.is_some() {
264                     break;
265                 }
266             }
267         }
268         Self { filter_map_function_id, next_function_id, prev_filter_map_expr_id: None }
269     }
270
271     // check for instances of .filter_map(..).next()
272     fn check(
273         &mut self,
274         current_expr_id: ExprId,
275         receiver_expr_id: &ExprId,
276         function_id: &hir_def::FunctionId,
277     ) -> Option<()> {
278         if *function_id == self.filter_map_function_id? {
279             self.prev_filter_map_expr_id = Some(current_expr_id);
280             return None;
281         }
282
283         if *function_id == self.next_function_id? {
284             if let Some(prev_filter_map_expr_id) = self.prev_filter_map_expr_id {
285                 if *receiver_expr_id == prev_filter_map_expr_id {
286                     return Some(());
287                 }
288             }
289         }
290
291         self.prev_filter_map_expr_id = None;
292         None
293     }
294 }
295
296 pub fn record_literal_missing_fields(
297     db: &dyn HirDatabase,
298     infer: &InferenceResult,
299     id: ExprId,
300     expr: &Expr,
301 ) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
302     let (fields, exhaustive) = match expr {
303         Expr::RecordLit { fields, spread, ellipsis, is_assignee_expr, .. } => {
304             let exhaustive = if *is_assignee_expr { !*ellipsis } else { spread.is_none() };
305             (fields, exhaustive)
306         }
307         _ => return None,
308     };
309
310     let variant_def = infer.variant_resolution_for_expr(id)?;
311     if let VariantId::UnionId(_) = variant_def {
312         return None;
313     }
314
315     let variant_data = variant_def.variant_data(db.upcast());
316
317     let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
318     let missed_fields: Vec<LocalFieldId> = variant_data
319         .fields()
320         .iter()
321         .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
322         .collect();
323     if missed_fields.is_empty() {
324         return None;
325     }
326     Some((variant_def, missed_fields, exhaustive))
327 }
328
329 pub fn record_pattern_missing_fields(
330     db: &dyn HirDatabase,
331     infer: &InferenceResult,
332     id: PatId,
333     pat: &Pat,
334 ) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
335     let (fields, exhaustive) = match pat {
336         Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
337         _ => return None,
338     };
339
340     let variant_def = infer.variant_resolution_for_pat(id)?;
341     if let VariantId::UnionId(_) = variant_def {
342         return None;
343     }
344
345     let variant_data = variant_def.variant_data(db.upcast());
346
347     let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
348     let missed_fields: Vec<LocalFieldId> = variant_data
349         .fields()
350         .iter()
351         .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
352         .collect();
353     if missed_fields.is_empty() {
354         return None;
355     }
356     Some((variant_def, missed_fields, exhaustive))
357 }
358
359 fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResult) -> bool {
360     fn walk(pat: PatId, body: &Body, infer: &InferenceResult, has_type_mismatches: &mut bool) {
361         match infer.type_mismatch_for_pat(pat) {
362             Some(_) => *has_type_mismatches = true,
363             None => {
364                 body[pat].walk_child_pats(|subpat| walk(subpat, body, infer, has_type_mismatches))
365             }
366         }
367     }
368
369     let mut has_type_mismatches = false;
370     walk(pat, body, infer, &mut has_type_mismatches);
371     !has_type_mismatches
372 }
373
374 fn missing_match_arms<'p>(
375     cx: &MatchCheckCtx<'_, 'p>,
376     scrut_ty: &Ty,
377     witnesses: Vec<DeconstructedPat<'p>>,
378     arms: &[MatchArm],
379 ) -> String {
380     struct DisplayWitness<'a, 'p>(&'a DeconstructedPat<'p>, &'a MatchCheckCtx<'a, 'p>);
381     impl<'a, 'p> fmt::Display for DisplayWitness<'a, 'p> {
382         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
383             let DisplayWitness(witness, cx) = *self;
384             let pat = witness.to_pat(cx);
385             write!(f, "{}", pat.display(cx.db))
386         }
387     }
388
389     let non_empty_enum = match scrut_ty.as_adt() {
390         Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(),
391         _ => false,
392     };
393     if arms.is_empty() && !non_empty_enum {
394         format!("type `{}` is non-empty", scrut_ty.display(cx.db))
395     } else {
396         let pat_display = |witness| DisplayWitness(witness, cx);
397         const LIMIT: usize = 3;
398         match &*witnesses {
399             [witness] => format!("`{}` not covered", pat_display(witness)),
400             [head @ .., tail] if head.len() < LIMIT => {
401                 let head = head.iter().map(pat_display);
402                 format!("`{}` and `{}` not covered", head.format("`, `"), pat_display(tail))
403             }
404             _ => {
405                 let (head, tail) = witnesses.split_at(LIMIT);
406                 let head = head.iter().map(pat_display);
407                 format!("`{}` and {} more not covered", head.format("`, `"), tail.len())
408             }
409         }
410     }
411 }