Skip to main content

hir_ty/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
5use std::fmt;
6
7use base_db::Crate;
8use either::Either;
9use hir_def::{
10    AdtId, AssocItemId, CallableDefId, DefWithBodyId, HasModule, ItemContainerId, Lookup,
11    attrs::AttrFlags,
12    lang_item::LangItems,
13    resolver::{HasResolver, ValueNs},
14};
15use intern::sym;
16use itertools::Itertools;
17use rustc_hash::FxHashSet;
18use rustc_pattern_analysis::constructor::Constructor;
19use rustc_type_ir::inherent::IntoKind;
20use syntax::{
21    AstNode,
22    ast::{self, UnaryOp},
23};
24use tracing::debug;
25
26use typed_arena::Arena;
27
28use crate::{
29    Adjust, InferenceResult,
30    db::HirDatabase,
31    diagnostics::match_check::{
32        self,
33        pat_analysis::{self, DeconstructedPat, MatchCheckCtx, WitnessPat},
34    },
35    display::{DisplayTarget, HirDisplay},
36    next_solver::{
37        CallableIdWrapper, DbInterner, ParamEnv, Ty, TyKind, TypingMode,
38        infer::{DbInternerInferExt, InferCtxt},
39    },
40};
41
42pub(crate) use hir_def::{
43    LocalFieldId, VariantId,
44    expr_store::Body,
45    hir::{Expr, ExprId, MatchArm, Pat, PatId, RecordSpread, Statement},
46};
47
48pub enum BodyValidationDiagnostic<'db> {
49    RecordMissingFields {
50        record: Either<ExprId, PatId>,
51        variant: VariantId,
52        missed_fields: Vec<LocalFieldId>,
53    },
54    ReplaceFilterMapNextWithFindMap {
55        method_call_expr: ExprId,
56    },
57    MissingMatchArms {
58        match_expr: ExprId,
59        uncovered_patterns: String,
60    },
61    NonExhaustiveLet {
62        pat: PatId,
63        uncovered_patterns: String,
64    },
65    RemoveTrailingReturn {
66        return_expr: ExprId,
67    },
68    RemoveUnnecessaryElse {
69        if_expr: ExprId,
70    },
71    UnusedMustUse {
72        expr: ExprId,
73        message: Option<&'db str>,
74    },
75}
76
77impl<'db> BodyValidationDiagnostic<'db> {
78    pub fn collect(
79        db: &'db dyn HirDatabase,
80        owner: DefWithBodyId,
81        validate_lints: bool,
82    ) -> Vec<BodyValidationDiagnostic<'db>> {
83        let _p = tracing::info_span!("BodyValidationDiagnostic::collect").entered();
84        let infer = InferenceResult::of(db, owner);
85        let body = Body::of(db, owner);
86        let env = db.trait_environment(owner.generic_def(db));
87        let interner = DbInterner::new_with(db, owner.krate(db));
88        let infcx =
89            interner.infer_ctxt().build(TypingMode::typeck_for_body(interner, owner.into()));
90        let mut validator = ExprValidator {
91            owner,
92            body,
93            infer,
94            diagnostics: Vec::new(),
95            validate_lints,
96            env,
97            infcx,
98        };
99        validator.validate_body();
100        validator.diagnostics
101    }
102}
103
104struct ExprValidator<'db> {
105    owner: DefWithBodyId,
106    body: &'db Body,
107    infer: &'db InferenceResult<'db>,
108    env: ParamEnv<'db>,
109    diagnostics: Vec<BodyValidationDiagnostic<'db>>,
110    validate_lints: bool,
111    infcx: InferCtxt<'db>,
112}
113
114impl<'db> ExprValidator<'db> {
115    #[inline]
116    fn db(&self) -> &'db dyn HirDatabase {
117        self.infcx.interner.db
118    }
119
120    fn validate_body(&mut self) {
121        let db = self.db();
122        let mut filter_map_next_checker = None;
123        // we'll pass &mut self while iterating over body.exprs, so they need to be disjoint
124        let body = self.body;
125
126        if matches!(self.owner, DefWithBodyId::FunctionId(_)) {
127            self.check_for_trailing_return(body.root_expr(), body);
128        }
129
130        for (id, expr) in body.exprs() {
131            if let Some((variant, missed_fields)) =
132                record_literal_missing_fields(db, self.infer, id, expr)
133            {
134                self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
135                    record: Either::Left(id),
136                    variant,
137                    missed_fields,
138                });
139            }
140
141            match expr {
142                Expr::Match { expr, arms } => {
143                    self.validate_match(id, *expr, arms);
144                }
145                Expr::Call { .. } | Expr::MethodCall { .. } => {
146                    self.validate_call(id, expr, &mut filter_map_next_checker);
147                }
148                Expr::Closure { body: body_expr, .. } => {
149                    self.check_for_trailing_return(*body_expr, body);
150                }
151                Expr::If { .. } => {
152                    self.check_for_unnecessary_else(id, expr);
153                }
154                Expr::Block { statements, .. } => {
155                    self.validate_block(statements);
156                }
157                _ => {}
158            }
159        }
160    }
161
162    fn validate_call(
163        &mut self,
164        call_id: ExprId,
165        expr: &Expr,
166        filter_map_next_checker: &mut Option<FilterMapNextChecker<'db>>,
167    ) {
168        if !self.validate_lints {
169            return;
170        }
171        // Check that the number of arguments matches the number of parameters.
172
173        if self.infer.exprs_have_type_mismatches() {
174            // FIXME: Due to shortcomings in the current type system implementation, only emit
175            // this diagnostic if there are no type mismatches in the containing function.
176        } else if let Expr::MethodCall { receiver, .. } = expr {
177            let (callee, _) = match self.infer.method_resolution(call_id) {
178                Some(it) => it,
179                None => return,
180            };
181
182            let checker = filter_map_next_checker.get_or_insert_with(|| {
183                FilterMapNextChecker::new(self.infcx.interner.lang_items(), self.db())
184            });
185
186            if checker.check(call_id, receiver, &callee).is_some() {
187                self.diagnostics.push(BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap {
188                    method_call_expr: call_id,
189                });
190            }
191
192            if let Some(receiver_ty) = self.infer.type_of_expr_with_adjust(*receiver) {
193                checker.prev_receiver_ty = Some(receiver_ty);
194            }
195        }
196    }
197
198    fn validate_match(&mut self, match_expr: ExprId, scrutinee_expr: ExprId, arms: &[MatchArm]) {
199        let Some(scrut_ty) = self.infer.type_of_expr_with_adjust(scrutinee_expr) else {
200            return;
201        };
202        if scrut_ty.references_non_lt_error() {
203            return;
204        }
205
206        let cx = MatchCheckCtx::new(self.owner.module(self.db()), &self.infcx, self.env);
207
208        let pattern_arena = Arena::new();
209        let mut m_arms = Vec::with_capacity(arms.len());
210        let mut has_lowering_errors = false;
211        // Note: Skipping the entire diagnostic rather than just not including a faulty match arm is
212        // preferred to avoid the chance of false positives.
213        for arm in arms {
214            let pat_ty = self.infer.type_of_pat_with_adjust(arm.pat);
215            if pat_ty.references_non_lt_error() {
216                return;
217            }
218
219            // We only include patterns whose type matches the type
220            // of the scrutinee expression. If we had an InvalidMatchArmPattern
221            // diagnostic or similar we could raise that in an else
222            // block here.
223            //
224            // When comparing the types, we also have to consider that rustc
225            // will automatically de-reference the scrutinee expression type if
226            // necessary.
227            //
228            // FIXME we should use the type checker for this.
229            if (pat_ty == scrut_ty
230                || scrut_ty
231                    .as_reference()
232                    .is_none_or(|(match_expr_ty, ..)| match_expr_ty == pat_ty))
233                && types_of_subpatterns_do_match(arm.pat, self.body, self.infer)
234            {
235                // If we had a NotUsefulMatchArm diagnostic, we could
236                // check the usefulness of each pattern as we added it
237                // to the matrix here.
238                let pat = self.lower_pattern(&cx, arm.pat, &mut has_lowering_errors);
239                let m_arm = pat_analysis::MatchArm {
240                    pat: pattern_arena.alloc(pat),
241                    has_guard: arm.guard.is_some(),
242                    arm_data: (),
243                };
244                m_arms.push(m_arm);
245                if !has_lowering_errors {
246                    continue;
247                }
248            }
249            // If the pattern type doesn't fit the match expression, we skip this diagnostic.
250            cov_mark::hit!(validate_match_bailed_out);
251            return;
252        }
253
254        let known_valid_scrutinee = Some(self.is_known_valid_scrutinee(scrutinee_expr));
255        let report =
256            match cx.compute_match_usefulness(m_arms.as_slice(), scrut_ty, known_valid_scrutinee) {
257                Ok(report) => report,
258                Err(()) => return,
259            };
260
261        // FIXME Report unreachable arms
262        // https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200
263
264        let witnesses = report.non_exhaustiveness_witnesses;
265        if !witnesses.is_empty() {
266            self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms {
267                match_expr,
268                uncovered_patterns: missing_match_arms(
269                    &cx,
270                    scrut_ty,
271                    witnesses,
272                    m_arms.is_empty(),
273                    self.owner.krate(self.db()),
274                ),
275            });
276        }
277    }
278
279    // [rustc's `is_known_valid_scrutinee`](https://github.com/rust-lang/rust/blob/c9bd03cb724e13cca96ad320733046cbdb16fbbe/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L288)
280    //
281    // While the above function in rustc uses thir exprs, r-a doesn't have them.
282    // So, the logic here is getting same result as "hir lowering + match with lowered thir"
283    // with "hir only"
284    fn is_known_valid_scrutinee(&self, scrutinee_expr: ExprId) -> bool {
285        let db = self.db();
286
287        if self
288            .infer
289            .expr_adjustments
290            .get(&scrutinee_expr)
291            .is_some_and(|adjusts| adjusts.iter().any(|a| matches!(a.kind, Adjust::Deref(..))))
292        {
293            return false;
294        }
295
296        match &self.body[scrutinee_expr] {
297            Expr::UnaryOp { op: UnaryOp::Deref, .. } => false,
298            Expr::Path(path) => {
299                let value_or_partial = self.owner.resolver(db).resolve_path_in_value_ns_fully(
300                    db,
301                    path,
302                    self.body.expr_path_hygiene(scrutinee_expr),
303                );
304                value_or_partial.is_none_or(|v| !matches!(v, ValueNs::StaticId(_)))
305            }
306            Expr::Field { expr, .. } => match self.infer.expr_ty(*expr).kind() {
307                TyKind::Adt(adt, ..) if matches!(adt.def_id(), AdtId::UnionId(_)) => false,
308                _ => self.is_known_valid_scrutinee(*expr),
309            },
310            Expr::Index { base, .. } => self.is_known_valid_scrutinee(*base),
311            Expr::Cast { expr, .. } => self.is_known_valid_scrutinee(*expr),
312            Expr::Missing => false,
313            _ => true,
314        }
315    }
316
317    fn validate_block(&mut self, statements: &[Statement]) {
318        let pattern_arena = Arena::new();
319        let cx = MatchCheckCtx::new(self.owner.module(self.db()), &self.infcx, self.env);
320        for stmt in statements {
321            match *stmt {
322                Statement::Expr { expr: stmt_expr, has_semi: true } if self.validate_lints => {
323                    let mut diags = Vec::new();
324                    self.check_unused_must_use(stmt_expr, &mut diags);
325                    self.diagnostics.extend(diags);
326                }
327                Statement::Let { pat, initializer, else_branch: None, .. } => {
328                    if let Some(diag) =
329                        self.check_non_exhaustive_let(&cx, &pattern_arena, pat, initializer)
330                    {
331                        self.diagnostics.push(diag);
332                    }
333                }
334                _ => {}
335            }
336        }
337    }
338
339    fn check_non_exhaustive_let<'a>(
340        &self,
341        cx: &MatchCheckCtx<'a, 'db>,
342        pattern_arena: &'a Arena<DeconstructedPat<'a, 'db>>,
343        pat: PatId,
344        initializer: Option<ExprId>,
345    ) -> Option<BodyValidationDiagnostic<'db>> {
346        if self.infer.pat_has_type_mismatch(pat) {
347            return None;
348        }
349        let initializer = initializer?;
350        let ty = self.infer.type_of_expr_with_adjust(initializer)?;
351        if ty.references_non_lt_error() {
352            return None;
353        }
354
355        let mut have_errors = false;
356        let deconstructed_pat = self.lower_pattern(cx, pat, &mut have_errors);
357
358        // optimization, wildcard trivially hold
359        if have_errors || matches!(deconstructed_pat.ctor(), Constructor::Wildcard) {
360            return None;
361        }
362
363        let match_arm = rustc_pattern_analysis::MatchArm {
364            pat: pattern_arena.alloc(deconstructed_pat),
365            has_guard: false,
366            arm_data: (),
367        };
368        let report = match cx.compute_match_usefulness(&[match_arm], ty, None) {
369            Ok(v) => v,
370            Err(e) => {
371                debug!(?e, "match usefulness error");
372                return None;
373            }
374        };
375        let witnesses = report.non_exhaustiveness_witnesses;
376        if witnesses.is_empty() {
377            return None;
378        }
379        Some(BodyValidationDiagnostic::NonExhaustiveLet {
380            pat,
381            uncovered_patterns: missing_match_arms(
382                cx,
383                ty,
384                witnesses,
385                false,
386                self.owner.krate(self.db()),
387            ),
388        })
389    }
390
391    fn lower_pattern<'a>(
392        &self,
393        cx: &MatchCheckCtx<'a, 'db>,
394        pat: PatId,
395        have_errors: &mut bool,
396    ) -> DeconstructedPat<'a, 'db> {
397        let mut patcx = match_check::PatCtxt::new(self.db(), self.infer, self.body);
398        let pattern = patcx.lower_pattern(pat);
399        let pattern = cx.lower_pat(&pattern);
400        if !patcx.errors.is_empty() {
401            *have_errors = true;
402        }
403        pattern
404    }
405
406    fn check_unused_must_use(
407        &self,
408        mut expr: ExprId,
409        acc: &mut Vec<BodyValidationDiagnostic<'db>>,
410    ) {
411        // Walk through container expressions so that the value-producing leaf is
412        // checked even when wrapped in a block, `unsafe { .. }`, `if`/`match`, or
413        // a `const { .. }` block.  Single-tail chains are followed by reassigning
414        // `expr`; branching containers (`if`/`match`) recurse on each arm.
415        loop {
416            match &self.body[expr] {
417                Expr::Block { tail: Some(tail), .. } | Expr::Const(tail) => expr = *tail,
418                Expr::If { then_branch, else_branch, .. } => {
419                    self.check_unused_must_use(*then_branch, acc);
420                    if let Some(else_branch) = else_branch {
421                        self.check_unused_must_use(*else_branch, acc);
422                    }
423                    return;
424                }
425                Expr::Match { arms, .. } => {
426                    for arm in arms.iter() {
427                        self.check_unused_must_use(arm.expr, acc);
428                    }
429                    return;
430                }
431                _ => break,
432            }
433        }
434
435        let fn_def = match &self.body[expr] {
436            Expr::Call { callee, .. } => {
437                let callee_ty = self.infer.expr_ty(*callee);
438                if let TyKind::FnDef(CallableIdWrapper(CallableDefId::FunctionId(func)), _) =
439                    callee_ty.kind()
440                {
441                    Some(func.into())
442                } else {
443                    None
444                }
445            }
446            Expr::MethodCall { .. } => {
447                self.infer.method_resolution(expr).map(|(func, _)| func.into())
448            }
449            _ => None,
450        };
451        let ty_def = self.infer.type_of_expr_with_adjust(expr).and_then(|ty| match ty.kind() {
452            TyKind::Adt(adt, _) => Some(adt.def_id().into()),
453            _ => None,
454        });
455        let must_use_diag = |owner| {
456            AttrFlags::must_use_message(self.db(), owner?)
457                .map(|message| BodyValidationDiagnostic::UnusedMustUse { expr, message })
458        };
459        if let Some(diag) = must_use_diag(fn_def).or_else(|| must_use_diag(ty_def)) {
460            acc.push(diag);
461        }
462    }
463
464    fn check_for_trailing_return(&mut self, body_expr: ExprId, body: &Body) {
465        if !self.validate_lints {
466            return;
467        }
468        match &body[body_expr] {
469            Expr::Block { statements, tail, .. } => {
470                let last_stmt = tail.or_else(|| match statements.last()? {
471                    Statement::Expr { expr, .. } => Some(*expr),
472                    _ => None,
473                });
474                if let Some(last_stmt) = last_stmt {
475                    self.check_for_trailing_return(last_stmt, body);
476                }
477            }
478            Expr::If { then_branch, else_branch, .. } => {
479                self.check_for_trailing_return(*then_branch, body);
480                if let Some(else_branch) = else_branch {
481                    self.check_for_trailing_return(*else_branch, body);
482                }
483            }
484            Expr::Match { arms, .. } => {
485                for arm in arms.iter() {
486                    let MatchArm { expr, .. } = arm;
487                    self.check_for_trailing_return(*expr, body);
488                }
489            }
490            Expr::Return { .. } => {
491                self.diagnostics.push(BodyValidationDiagnostic::RemoveTrailingReturn {
492                    return_expr: body_expr,
493                });
494            }
495            _ => (),
496        }
497    }
498
499    fn check_for_unnecessary_else(&mut self, id: ExprId, expr: &Expr) {
500        if !self.validate_lints {
501            return;
502        }
503        if let Expr::If { condition: _, then_branch, else_branch } = expr {
504            if else_branch.is_none() {
505                return;
506            }
507            if let Expr::Block { statements, tail, .. } = &self.body[*then_branch] {
508                let last_then_expr = tail.or_else(|| match statements.last()? {
509                    Statement::Expr { expr, .. } => Some(*expr),
510                    _ => None,
511                });
512                if let Some(last_then_expr) = last_then_expr
513                    && let Some(last_then_expr_ty) =
514                        self.infer.type_of_expr_with_adjust(last_then_expr)
515                    && last_then_expr_ty.is_never()
516                {
517                    // Only look at sources if the then branch diverges and we have an else branch.
518                    let source_map = &Body::with_source_map(self.db(), self.owner).1;
519                    let Ok(source_ptr) = source_map.expr_syntax(id) else {
520                        return;
521                    };
522                    let root = source_ptr.file_syntax(self.db());
523                    let either::Left(ast::Expr::IfExpr(if_expr)) = source_ptr.value.to_node(&root)
524                    else {
525                        return;
526                    };
527                    let mut top_if_expr = if_expr;
528                    loop {
529                        let parent = top_if_expr.syntax().parent();
530                        let has_parent_expr_stmt_or_stmt_list =
531                            parent.as_ref().is_some_and(|node| {
532                                ast::ExprStmt::can_cast(node.kind())
533                                    | ast::StmtList::can_cast(node.kind())
534                            });
535                        if has_parent_expr_stmt_or_stmt_list {
536                            // Only emit diagnostic if parent or direct ancestor is either
537                            // an expr stmt or a stmt list.
538                            break;
539                        }
540                        let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
541                            // Bail if parent is neither an if expr, an expr stmt nor a stmt list.
542                            return;
543                        };
544                        // Check parent if expr.
545                        top_if_expr = parent_if_expr;
546                    }
547
548                    self.diagnostics
549                        .push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id })
550                }
551            }
552        }
553    }
554}
555
556struct FilterMapNextChecker<'db> {
557    filter_map_function_id: Option<hir_def::FunctionId>,
558    next_function_id: Option<hir_def::FunctionId>,
559    prev_filter_map_expr_id: Option<ExprId>,
560    prev_receiver_ty: Option<Ty<'db>>,
561}
562
563impl<'db> FilterMapNextChecker<'db> {
564    fn new(lang_items: &'db LangItems, db: &'db dyn HirDatabase) -> Self {
565        // Find and store the FunctionIds for Iterator::filter_map and Iterator::next
566        let (next_function_id, filter_map_function_id) = match lang_items.IteratorNext {
567            Some(next_function_id) => (
568                Some(next_function_id),
569                match next_function_id.lookup(db).container {
570                    ItemContainerId::TraitId(iterator_trait_id) => {
571                        let iterator_trait_items = &iterator_trait_id.trait_items(db).items;
572                        iterator_trait_items.iter().find_map(|(name, it)| match it {
573                            &AssocItemId::FunctionId(id) if *name == sym::filter_map => Some(id),
574                            _ => None,
575                        })
576                    }
577                    _ => None,
578                },
579            ),
580            None => (None, None),
581        };
582        Self {
583            filter_map_function_id,
584            next_function_id,
585            prev_filter_map_expr_id: None,
586            prev_receiver_ty: None,
587        }
588    }
589
590    // check for instances of .filter_map(..).next()
591    fn check(
592        &mut self,
593        current_expr_id: ExprId,
594        receiver_expr_id: &ExprId,
595        function_id: &hir_def::FunctionId,
596    ) -> Option<()> {
597        if *function_id == self.filter_map_function_id? {
598            self.prev_filter_map_expr_id = Some(current_expr_id);
599            return None;
600        }
601
602        if *function_id == self.next_function_id?
603            && let Some(prev_filter_map_expr_id) = self.prev_filter_map_expr_id
604        {
605            let is_dyn_trait = self
606                .prev_receiver_ty
607                .as_ref()
608                .is_some_and(|it| it.strip_references().dyn_trait().is_some());
609            if *receiver_expr_id == prev_filter_map_expr_id && !is_dyn_trait {
610                return Some(());
611            }
612        }
613
614        self.prev_filter_map_expr_id = None;
615        None
616    }
617}
618
619pub fn record_literal_missing_fields<'db>(
620    db: &'db dyn HirDatabase,
621    infer: &InferenceResult<'db>,
622    id: ExprId,
623    expr: &Expr,
624) -> Option<(VariantId, Vec<LocalFieldId>)> {
625    let (fields, spread) = match expr {
626        Expr::RecordLit { fields, spread, .. } => (fields, spread),
627        _ => return None,
628    };
629
630    let variant_def = infer.variant_resolution_for_expr(id)?;
631    if let VariantId::UnionId(_) = variant_def {
632        return None;
633    }
634
635    let variant_data = variant_def.fields(db);
636
637    let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
638    // don't show missing fields if:
639    // - has ..expr
640    // - or has default value + ..
641    // - or already in code
642    let missed_fields: Vec<LocalFieldId> = variant_data
643        .fields()
644        .iter()
645        .filter_map(|(f, d)| {
646            if specified_fields.contains(&d.name)
647                || matches!(spread, RecordSpread::Expr(_))
648                || (d.default_value.is_some() && matches!(spread, RecordSpread::FieldDefaults))
649            {
650                None
651            } else {
652                Some(f)
653            }
654        })
655        .collect();
656    if missed_fields.is_empty() {
657        return None;
658    }
659    Some((variant_def, missed_fields))
660}
661
662pub fn record_pattern_missing_fields<'db>(
663    db: &'db dyn HirDatabase,
664    infer: &InferenceResult<'db>,
665    id: PatId,
666    pat: &Pat,
667) -> Option<(VariantId, Vec<LocalFieldId>)> {
668    let (fields, ellipsis) = match pat {
669        Pat::Record { path: _, args, ellipsis } => (args, *ellipsis),
670        _ => return None,
671    };
672
673    let variant_def = infer.variant_resolution_for_pat(id)?;
674    if let VariantId::UnionId(_) = variant_def {
675        return None;
676    }
677
678    let variant_data = variant_def.fields(db);
679
680    let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
681    // don't show missing fields if:
682    // - in code
683    // - or has ..
684    let missed_fields: Vec<LocalFieldId> = variant_data
685        .fields()
686        .iter()
687        .filter_map(
688            |(f, d)| {
689                if specified_fields.contains(&d.name) || ellipsis { None } else { Some(f) }
690            },
691        )
692        .collect();
693    if missed_fields.is_empty() {
694        return None;
695    }
696    Some((variant_def, missed_fields))
697}
698
699fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResult<'_>) -> bool {
700    fn walk(pat: PatId, body: &Body, infer: &InferenceResult<'_>, has_type_mismatches: &mut bool) {
701        match infer.pat_has_type_mismatch(pat) {
702            true => *has_type_mismatches = true,
703            false if *has_type_mismatches => (),
704            false => {
705                let pat = &body[pat];
706                if let Pat::Lit(expr) = *pat {
707                    *has_type_mismatches |= infer.expr_has_type_mismatch(expr);
708                    if *has_type_mismatches {
709                        return;
710                    }
711                }
712                pat.walk_child_pats(|subpat| walk(subpat, body, infer, has_type_mismatches))
713            }
714        }
715    }
716
717    let mut has_type_mismatches = false;
718    walk(pat, body, infer, &mut has_type_mismatches);
719    !has_type_mismatches
720}
721
722fn missing_match_arms<'a, 'db>(
723    cx: &MatchCheckCtx<'a, 'db>,
724    scrut_ty: Ty<'a>,
725    witnesses: Vec<WitnessPat<'a, 'db>>,
726    arms_is_empty: bool,
727    krate: Crate,
728) -> String {
729    struct DisplayWitness<'a, 'b, 'db>(
730        &'a WitnessPat<'b, 'db>,
731        &'a MatchCheckCtx<'b, 'db>,
732        DisplayTarget,
733    );
734    impl fmt::Display for DisplayWitness<'_, '_, '_> {
735        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
736            let DisplayWitness(witness, cx, display_target) = *self;
737            let pat = cx.hoist_witness_pat(witness);
738            write!(f, "{}", pat.display(cx.db, display_target))
739        }
740    }
741
742    let non_empty_enum = match scrut_ty.as_adt() {
743        Some((AdtId::EnumId(e), _)) => !e.enum_variants(cx.db).variants.is_empty(),
744        _ => false,
745    };
746    let display_target = DisplayTarget::from_crate(cx.db, krate);
747    if arms_is_empty && !non_empty_enum {
748        format!("type `{}` is non-empty", scrut_ty.display(cx.db, display_target))
749    } else {
750        let pat_display = |witness| DisplayWitness(witness, cx, display_target);
751        const LIMIT: usize = 3;
752        match &*witnesses {
753            [witness] => format!("`{}` not covered", pat_display(witness)),
754            [head @ .., tail] if head.len() < LIMIT => {
755                let head = head.iter().map(pat_display);
756                format!("`{}` and `{}` not covered", head.format("`, `"), pat_display(tail))
757            }
758            _ => {
759                let (head, tail) = witnesses.split_at(LIMIT);
760                let head = head.iter().map(pat_display);
761                format!("`{}` and {} more not covered", head.format("`, `"), tail.len())
762            }
763        }
764    }
765}