1 //! Various diagnostics for expressions that are collected together in one pass
2 //! through the body using inference results: mismatched arg counts, missing
8 use hir_def::{path::path, resolver::HasResolver, AdtId, AssocItemId, DefWithBodyId, HasModule};
10 use itertools::Either;
11 use itertools::Itertools;
12 use rustc_hash::FxHashSet;
13 use typed_arena::Arena;
17 diagnostics::match_check::{
19 deconstruct_pat::DeconstructedPat,
20 usefulness::{compute_match_usefulness, MatchCheckCtx},
23 InferenceResult, Ty, TyExt,
26 pub(crate) use hir_def::{
28 expr::{Expr, ExprId, MatchArm, Pat, PatId},
29 LocalFieldId, VariantId,
32 pub enum BodyValidationDiagnostic {
34 record: Either<ExprId, PatId>,
36 missed_fields: Vec<LocalFieldId>,
38 ReplaceFilterMapNextWithFindMap {
39 method_call_expr: ExprId,
43 uncovered_patterns: String,
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);
57 struct ExprValidator {
59 infer: Arc<InferenceResult>,
60 pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
64 fn new(owner: DefWithBodyId, infer: Arc<InferenceResult>) -> ExprValidator {
65 ExprValidator { owner, infer, diagnostics: Vec::new() }
68 fn validate_body(&mut self, db: &dyn HirDatabase) {
69 let body = db.body(self.owner);
70 let mut filter_map_next_checker = None;
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)
76 self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
77 record: Either::Left(id),
84 Expr::Match { expr, arms } => {
85 self.validate_match(id, *expr, arms, db, self.infer.clone());
87 Expr::Call { .. } | Expr::MethodCall { .. } => {
88 self.validate_call(db, id, expr, &mut filter_map_next_checker);
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)
97 self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
98 record: Either::Right(id),
108 db: &dyn HirDatabase,
111 filter_map_next_checker: &mut Option<FilterMapNextChecker>,
113 // Check that the number of arguments matches the number of parameters.
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() {
122 Expr::MethodCall { receiver, .. } => {
123 let (callee, _) = match self.infer.method_resolution(call_id) {
128 if filter_map_next_checker
129 .get_or_insert_with(|| {
130 FilterMapNextChecker::new(&self.owner.resolver(db.upcast()), db)
132 .check(call_id, receiver, &callee)
135 self.diagnostics.push(
136 BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap {
137 method_call_expr: call_id,
151 db: &dyn HirDatabase,
152 infer: Arc<InferenceResult>,
154 let body = db.body(self.owner);
156 let match_expr_ty = &infer[match_expr];
157 if match_expr_ty.is_unknown() {
161 let pattern_arena = Arena::new();
162 let cx = MatchCheckCtx {
163 module: self.owner.module(db.upcast()),
166 pattern_arena: &pattern_arena,
169 let mut m_arms = Vec::with_capacity(arms.len());
170 let mut has_lowering_errors = false;
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
178 // When comparing the types, we also have to consider that rustc
179 // will automatically de-reference the match expression type if
182 // FIXME we should use the type checker for this.
183 if (pat_ty == match_expr_ty
186 .map(|(match_expr_ty, ..)| match_expr_ty == pat_ty)
188 && types_of_subpatterns_do_match(arm.pat, &body, &infer)
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(),
198 if !has_lowering_errors {
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);
212 let report = compute_match_usefulness(&cx, &m_arms, match_expr_ty);
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
217 let witnesses = report.non_exhaustiveness_witnesses;
218 if !witnesses.is_empty() {
219 self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
221 uncovered_patterns: missing_match_arms(&cx, match_expr_ty, witnesses, arms),
226 fn lower_pattern<'p>(
228 cx: &MatchCheckCtx<'_, 'p>,
230 db: &dyn HirDatabase,
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() {
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>,
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;
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);
264 if *name == name![next] {
265 next_function_id = Some(*id);
268 if filter_map_function_id.is_some() && next_function_id.is_some() {
273 Self { filter_map_function_id, next_function_id, prev_filter_map_expr_id: None }
276 // check for instances of .filter_map(..).next()
279 current_expr_id: ExprId,
280 receiver_expr_id: &ExprId,
281 function_id: &hir_def::FunctionId,
283 if *function_id == self.filter_map_function_id? {
284 self.prev_filter_map_expr_id = Some(current_expr_id);
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 {
296 self.prev_filter_map_expr_id = None;
301 pub fn record_literal_missing_fields(
302 db: &dyn HirDatabase,
303 infer: &InferenceResult,
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() };
315 let variant_def = infer.variant_resolution_for_expr(id)?;
316 if let VariantId::UnionId(_) = variant_def {
320 let variant_data = variant_def.variant_data(db.upcast());
322 let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
323 let missed_fields: Vec<LocalFieldId> = variant_data
326 .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
328 if missed_fields.is_empty() {
331 Some((variant_def, missed_fields, exhaustive))
334 pub fn record_pattern_missing_fields(
335 db: &dyn HirDatabase,
336 infer: &InferenceResult,
339 ) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
340 let (fields, exhaustive) = match pat {
341 Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
345 let variant_def = infer.variant_resolution_for_pat(id)?;
346 if let VariantId::UnionId(_) = variant_def {
350 let variant_data = variant_def.variant_data(db.upcast());
352 let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
353 let missed_fields: Vec<LocalFieldId> = variant_data
356 .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
358 if missed_fields.is_empty() {
361 Some((variant_def, missed_fields, exhaustive))
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,
369 body[pat].walk_child_pats(|subpat| walk(subpat, body, infer, has_type_mismatches))
374 let mut has_type_mismatches = false;
375 walk(pat, body, infer, &mut has_type_mismatches);
379 fn missing_match_arms<'p>(
380 cx: &MatchCheckCtx<'_, 'p>,
382 witnesses: Vec<DeconstructedPat<'p>>,
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))
394 let non_empty_enum = match scrut_ty.as_adt() {
395 Some((AdtId::EnumId(e), _)) => !cx.db.enum_data(e).variants.is_empty(),
398 if arms.is_empty() && !non_empty_enum {
399 format!("type `{}` is non-empty", scrut_ty.display(cx.db))
401 let pat_display = |witness| DisplayWitness(witness, cx);
402 const LIMIT: usize = 3;
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))
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())