]> git.lizzy.rs Git - rust.git/blob - crates/hir-ty/src/diagnostics/expr.rs
642e03edd230636ca3911a0e0eee44dc8989a41e
[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 {
163             module: self.owner.module(db.upcast()),
164             body: self.owner,
165             db,
166             pattern_arena: &pattern_arena,
167         };
168
169         let mut m_arms = Vec::with_capacity(arms.len());
170         let mut has_lowering_errors = false;
171         for arm in arms {
172             if let Some(pat_ty) = infer.type_of_pat.get(arm.pat) {
173                 // We only include patterns whose type matches the type
174                 // of the match expression. If we had an InvalidMatchArmPattern
175                 // diagnostic or similar we could raise that in an else
176                 // block here.
177                 //
178                 // When comparing the types, we also have to consider that rustc
179                 // will automatically de-reference the match expression type if
180                 // necessary.
181                 //
182                 // FIXME we should use the type checker for this.
183                 if (pat_ty == match_expr_ty
184                     || match_expr_ty
185                         .as_reference()
186                         .map(|(match_expr_ty, ..)| match_expr_ty == pat_ty)
187                         .unwrap_or(false))
188                     && types_of_subpatterns_do_match(arm.pat, &body, &infer)
189                 {
190                     // If we had a NotUsefulMatchArm diagnostic, we could
191                     // check the usefulness of each pattern as we added it
192                     // to the matrix here.
193                     let m_arm = match_check::MatchArm {
194                         pat: self.lower_pattern(&cx, arm.pat, db, &body, &mut has_lowering_errors),
195                         has_guard: arm.guard.is_some(),
196                     };
197                     m_arms.push(m_arm);
198                     if !has_lowering_errors {
199                         continue;
200                     }
201                 }
202             }
203
204             // If we can't resolve the type of a pattern, or the pattern type doesn't
205             // fit the match expression, we skip this diagnostic. Skipping the entire
206             // diagnostic rather than just not including this match arm is preferred
207             // to avoid the chance of false positives.
208             cov_mark::hit!(validate_match_bailed_out);
209             return;
210         }
211
212         let report = compute_match_usefulness(&cx, &m_arms, match_expr_ty);
213
214         // FIXME Report unreacheble arms
215         // https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200
216
217         let witnesses = report.non_exhaustiveness_witnesses;
218         if !witnesses.is_empty() {
219             self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
220                 match_expr: id,
221                 uncovered_patterns: missing_match_arms(&cx, match_expr_ty, witnesses, arms),
222             });
223         }
224     }
225
226     fn lower_pattern<'p>(
227         &self,
228         cx: &MatchCheckCtx<'_, 'p>,
229         pat: PatId,
230         db: &dyn HirDatabase,
231         body: &Body,
232         have_errors: &mut bool,
233     ) -> &'p DeconstructedPat<'p> {
234         let mut patcx = match_check::PatCtxt::new(db, &self.infer, body);
235         let pattern = patcx.lower_pattern(pat);
236         let pattern = cx.pattern_arena.alloc(DeconstructedPat::from_pat(cx, &pattern));
237         if !patcx.errors.is_empty() {
238             *have_errors = true;
239         }
240         pattern
241     }
242 }
243
244 struct FilterMapNextChecker {
245     filter_map_function_id: Option<hir_def::FunctionId>,
246     next_function_id: Option<hir_def::FunctionId>,
247     prev_filter_map_expr_id: Option<ExprId>,
248 }
249
250 impl FilterMapNextChecker {
251     fn new(resolver: &hir_def::resolver::Resolver, db: &dyn HirDatabase) -> Self {
252         // Find and store the FunctionIds for Iterator::filter_map and Iterator::next
253         let iterator_path = path![core::iter::Iterator];
254         let mut filter_map_function_id = None;
255         let mut next_function_id = None;
256
257         if let Some(iterator_trait_id) = resolver.resolve_known_trait(db.upcast(), &iterator_path) {
258             let iterator_trait_items = &db.trait_data(iterator_trait_id).items;
259             for item in iterator_trait_items.iter() {
260                 if let (name, AssocItemId::FunctionId(id)) = item {
261                     if *name == name![filter_map] {
262                         filter_map_function_id = Some(*id);
263                     }
264                     if *name == name![next] {
265                         next_function_id = Some(*id);
266                     }
267                 }
268                 if filter_map_function_id.is_some() && next_function_id.is_some() {
269                     break;
270                 }
271             }
272         }
273         Self { filter_map_function_id, next_function_id, prev_filter_map_expr_id: None }
274     }
275
276     // check for instances of .filter_map(..).next()
277     fn check(
278         &mut self,
279         current_expr_id: ExprId,
280         receiver_expr_id: &ExprId,
281         function_id: &hir_def::FunctionId,
282     ) -> Option<()> {
283         if *function_id == self.filter_map_function_id? {
284             self.prev_filter_map_expr_id = Some(current_expr_id);
285             return None;
286         }
287
288         if *function_id == self.next_function_id? {
289             if let Some(prev_filter_map_expr_id) = self.prev_filter_map_expr_id {
290                 if *receiver_expr_id == prev_filter_map_expr_id {
291                     return Some(());
292                 }
293             }
294         }
295
296         self.prev_filter_map_expr_id = None;
297         None
298     }
299 }
300
301 pub fn record_literal_missing_fields(
302     db: &dyn HirDatabase,
303     infer: &InferenceResult,
304     id: ExprId,
305     expr: &Expr,
306 ) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
307     let (fields, exhaustive) = match expr {
308         Expr::RecordLit { fields, spread, ellipsis, is_assignee_expr, .. } => {
309             let exhaustive = if *is_assignee_expr { !*ellipsis } else { spread.is_none() };
310             (fields, exhaustive)
311         }
312         _ => return None,
313     };
314
315     let variant_def = infer.variant_resolution_for_expr(id)?;
316     if let VariantId::UnionId(_) = variant_def {
317         return None;
318     }
319
320     let variant_data = variant_def.variant_data(db.upcast());
321
322     let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
323     let missed_fields: Vec<LocalFieldId> = variant_data
324         .fields()
325         .iter()
326         .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
327         .collect();
328     if missed_fields.is_empty() {
329         return None;
330     }
331     Some((variant_def, missed_fields, exhaustive))
332 }
333
334 pub fn record_pattern_missing_fields(
335     db: &dyn HirDatabase,
336     infer: &InferenceResult,
337     id: PatId,
338     pat: &Pat,
339 ) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
340     let (fields, exhaustive) = match pat {
341         Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
342         _ => return None,
343     };
344
345     let variant_def = infer.variant_resolution_for_pat(id)?;
346     if let VariantId::UnionId(_) = variant_def {
347         return None;
348     }
349
350     let variant_data = variant_def.variant_data(db.upcast());
351
352     let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
353     let missed_fields: Vec<LocalFieldId> = variant_data
354         .fields()
355         .iter()
356         .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
357         .collect();
358     if missed_fields.is_empty() {
359         return None;
360     }
361     Some((variant_def, missed_fields, exhaustive))
362 }
363
364 fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResult) -> bool {
365     fn walk(pat: PatId, body: &Body, infer: &InferenceResult, has_type_mismatches: &mut bool) {
366         match infer.type_mismatch_for_pat(pat) {
367             Some(_) => *has_type_mismatches = true,
368             None => {
369                 body[pat].walk_child_pats(|subpat| walk(subpat, body, infer, has_type_mismatches))
370             }
371         }
372     }
373
374     let mut has_type_mismatches = false;
375     walk(pat, body, infer, &mut has_type_mismatches);
376     !has_type_mismatches
377 }
378
379 fn missing_match_arms<'p>(
380     cx: &MatchCheckCtx<'_, 'p>,
381     scrut_ty: &Ty,
382     witnesses: Vec<DeconstructedPat<'p>>,
383     arms: &[MatchArm],
384 ) -> String {
385     struct DisplayWitness<'a, 'p>(&'a DeconstructedPat<'p>, &'a MatchCheckCtx<'a, 'p>);
386     impl<'a, 'p> fmt::Display for DisplayWitness<'a, 'p> {
387         fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
388             let DisplayWitness(witness, cx) = *self;
389             let pat = witness.to_pat(cx);
390             write!(f, "{}", pat.display(cx.db))
391         }
392     }
393
394     let non_empty_enum = match scrut_ty.as_adt() {
395         Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(),
396         _ => false,
397     };
398     if arms.is_empty() && !non_empty_enum {
399         format!("type `{}` is non-empty", scrut_ty.display(cx.db))
400     } else {
401         let pat_display = |witness| DisplayWitness(witness, cx);
402         const LIMIT: usize = 3;
403         match &*witnesses {
404             [witness] => format!("`{}` not covered", pat_display(witness)),
405             [head @ .., tail] if head.len() < LIMIT => {
406                 let head = head.iter().map(pat_display);
407                 format!("`{}` and `{}` not covered", head.format("`, `"), pat_display(tail))
408             }
409             _ => {
410                 let (head, tail) = witnesses.split_at(LIMIT);
411                 let head = head.iter().map(pat_display);
412                 format!("`{}` and {} more not covered", head.format("`, `"), tail.len())
413             }
414         }
415     }
416 }