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 { .. } | Expr::Unsafe { .. } => {
155                    self.validate_block(expr);
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, expr: &Expr) {
318        let (Expr::Block { statements, .. } | Expr::Unsafe { statements, .. }) = expr else {
319            return;
320        };
321        let pattern_arena = Arena::new();
322        let cx = MatchCheckCtx::new(self.owner.module(self.db()), &self.infcx, self.env);
323        for stmt in &**statements {
324            match *stmt {
325                Statement::Expr { expr: stmt_expr, has_semi: true } if self.validate_lints => {
326                    let mut diags = Vec::new();
327                    self.check_unused_must_use(stmt_expr, &mut diags);
328                    self.diagnostics.extend(diags);
329                }
330                Statement::Let { pat, initializer, else_branch: None, .. } => {
331                    if let Some(diag) =
332                        self.check_non_exhaustive_let(&cx, &pattern_arena, pat, initializer)
333                    {
334                        self.diagnostics.push(diag);
335                    }
336                }
337                _ => {}
338            }
339        }
340    }
341
342    fn check_non_exhaustive_let<'a>(
343        &self,
344        cx: &MatchCheckCtx<'a, 'db>,
345        pattern_arena: &'a Arena<DeconstructedPat<'a, 'db>>,
346        pat: PatId,
347        initializer: Option<ExprId>,
348    ) -> Option<BodyValidationDiagnostic<'db>> {
349        if self.infer.pat_has_type_mismatch(pat) {
350            return None;
351        }
352        let initializer = initializer?;
353        let ty = self.infer.type_of_expr_with_adjust(initializer)?;
354        if ty.references_non_lt_error() {
355            return None;
356        }
357
358        let mut have_errors = false;
359        let deconstructed_pat = self.lower_pattern(cx, pat, &mut have_errors);
360
361        // optimization, wildcard trivially hold
362        if have_errors || matches!(deconstructed_pat.ctor(), Constructor::Wildcard) {
363            return None;
364        }
365
366        let match_arm = rustc_pattern_analysis::MatchArm {
367            pat: pattern_arena.alloc(deconstructed_pat),
368            has_guard: false,
369            arm_data: (),
370        };
371        let report = match cx.compute_match_usefulness(&[match_arm], ty, None) {
372            Ok(v) => v,
373            Err(e) => {
374                debug!(?e, "match usefulness error");
375                return None;
376            }
377        };
378        let witnesses = report.non_exhaustiveness_witnesses;
379        if witnesses.is_empty() {
380            return None;
381        }
382        Some(BodyValidationDiagnostic::NonExhaustiveLet {
383            pat,
384            uncovered_patterns: missing_match_arms(
385                cx,
386                ty,
387                witnesses,
388                false,
389                self.owner.krate(self.db()),
390            ),
391        })
392    }
393
394    fn lower_pattern<'a>(
395        &self,
396        cx: &MatchCheckCtx<'a, 'db>,
397        pat: PatId,
398        have_errors: &mut bool,
399    ) -> DeconstructedPat<'a, 'db> {
400        let mut patcx = match_check::PatCtxt::new(self.db(), self.infer, self.body);
401        let pattern = patcx.lower_pattern(pat);
402        let pattern = cx.lower_pat(&pattern);
403        if !patcx.errors.is_empty() {
404            *have_errors = true;
405        }
406        pattern
407    }
408
409    fn check_unused_must_use(
410        &self,
411        mut expr: ExprId,
412        acc: &mut Vec<BodyValidationDiagnostic<'db>>,
413    ) {
414        // Walk through container expressions so that the value-producing leaf is
415        // checked even when wrapped in a block, `unsafe { .. }`, `if`/`match`, or
416        // a `const { .. }` block.  Single-tail chains are followed by reassigning
417        // `expr`; branching containers (`if`/`match`) recurse on each arm.
418        loop {
419            match &self.body[expr] {
420                Expr::Block { tail: Some(tail), .. }
421                | Expr::Unsafe { tail: Some(tail), .. }
422                | Expr::Const(tail) => expr = *tail,
423                Expr::If { then_branch, else_branch, .. } => {
424                    self.check_unused_must_use(*then_branch, acc);
425                    if let Some(else_branch) = else_branch {
426                        self.check_unused_must_use(*else_branch, acc);
427                    }
428                    return;
429                }
430                Expr::Match { arms, .. } => {
431                    for arm in arms.iter() {
432                        self.check_unused_must_use(arm.expr, acc);
433                    }
434                    return;
435                }
436                _ => break,
437            }
438        }
439
440        let fn_def = match &self.body[expr] {
441            Expr::Call { callee, .. } => {
442                let callee_ty = self.infer.expr_ty(*callee);
443                if let TyKind::FnDef(CallableIdWrapper(CallableDefId::FunctionId(func)), _) =
444                    callee_ty.kind()
445                {
446                    Some(func.into())
447                } else {
448                    None
449                }
450            }
451            Expr::MethodCall { .. } => {
452                self.infer.method_resolution(expr).map(|(func, _)| func.into())
453            }
454            _ => None,
455        };
456        let ty_def = self.infer.type_of_expr_with_adjust(expr).and_then(|ty| match ty.kind() {
457            TyKind::Adt(adt, _) => Some(adt.def_id().into()),
458            _ => None,
459        });
460        let must_use_diag = |owner| {
461            AttrFlags::must_use_message(self.db(), owner?)
462                .map(|message| BodyValidationDiagnostic::UnusedMustUse { expr, message })
463        };
464        if let Some(diag) = must_use_diag(fn_def).or_else(|| must_use_diag(ty_def)) {
465            acc.push(diag);
466        }
467    }
468
469    fn check_for_trailing_return(&mut self, body_expr: ExprId, body: &Body) {
470        if !self.validate_lints {
471            return;
472        }
473        match &body[body_expr] {
474            Expr::Block { statements, tail, .. } => {
475                let last_stmt = tail.or_else(|| match statements.last()? {
476                    Statement::Expr { expr, .. } => Some(*expr),
477                    _ => None,
478                });
479                if let Some(last_stmt) = last_stmt {
480                    self.check_for_trailing_return(last_stmt, body);
481                }
482            }
483            Expr::If { then_branch, else_branch, .. } => {
484                self.check_for_trailing_return(*then_branch, body);
485                if let Some(else_branch) = else_branch {
486                    self.check_for_trailing_return(*else_branch, body);
487                }
488            }
489            Expr::Match { arms, .. } => {
490                for arm in arms.iter() {
491                    let MatchArm { expr, .. } = arm;
492                    self.check_for_trailing_return(*expr, body);
493                }
494            }
495            Expr::Return { .. } => {
496                self.diagnostics.push(BodyValidationDiagnostic::RemoveTrailingReturn {
497                    return_expr: body_expr,
498                });
499            }
500            _ => (),
501        }
502    }
503
504    fn check_for_unnecessary_else(&mut self, id: ExprId, expr: &Expr) {
505        if !self.validate_lints {
506            return;
507        }
508        if let Expr::If { condition: _, then_branch, else_branch } = expr {
509            if else_branch.is_none() {
510                return;
511            }
512            if let Expr::Block { statements, tail, .. } = &self.body[*then_branch] {
513                let last_then_expr = tail.or_else(|| match statements.last()? {
514                    Statement::Expr { expr, .. } => Some(*expr),
515                    _ => None,
516                });
517                if let Some(last_then_expr) = last_then_expr
518                    && let Some(last_then_expr_ty) =
519                        self.infer.type_of_expr_with_adjust(last_then_expr)
520                    && last_then_expr_ty.is_never()
521                {
522                    // Only look at sources if the then branch diverges and we have an else branch.
523                    let source_map = &Body::with_source_map(self.db(), self.owner).1;
524                    let Ok(source_ptr) = source_map.expr_syntax(id) else {
525                        return;
526                    };
527                    let root = source_ptr.file_syntax(self.db());
528                    let either::Left(ast::Expr::IfExpr(if_expr)) = source_ptr.value.to_node(&root)
529                    else {
530                        return;
531                    };
532                    let mut top_if_expr = if_expr;
533                    loop {
534                        let parent = top_if_expr.syntax().parent();
535                        let has_parent_expr_stmt_or_stmt_list =
536                            parent.as_ref().is_some_and(|node| {
537                                ast::ExprStmt::can_cast(node.kind())
538                                    | ast::StmtList::can_cast(node.kind())
539                            });
540                        if has_parent_expr_stmt_or_stmt_list {
541                            // Only emit diagnostic if parent or direct ancestor is either
542                            // an expr stmt or a stmt list.
543                            break;
544                        }
545                        let Some(parent_if_expr) = parent.and_then(ast::IfExpr::cast) else {
546                            // Bail if parent is neither an if expr, an expr stmt nor a stmt list.
547                            return;
548                        };
549                        // Check parent if expr.
550                        top_if_expr = parent_if_expr;
551                    }
552
553                    self.diagnostics
554                        .push(BodyValidationDiagnostic::RemoveUnnecessaryElse { if_expr: id })
555                }
556            }
557        }
558    }
559}
560
561struct FilterMapNextChecker<'db> {
562    filter_map_function_id: Option<hir_def::FunctionId>,
563    next_function_id: Option<hir_def::FunctionId>,
564    prev_filter_map_expr_id: Option<ExprId>,
565    prev_receiver_ty: Option<Ty<'db>>,
566}
567
568impl<'db> FilterMapNextChecker<'db> {
569    fn new(lang_items: &'db LangItems, db: &'db dyn HirDatabase) -> Self {
570        // Find and store the FunctionIds for Iterator::filter_map and Iterator::next
571        let (next_function_id, filter_map_function_id) = match lang_items.IteratorNext {
572            Some(next_function_id) => (
573                Some(next_function_id),
574                match next_function_id.lookup(db).container {
575                    ItemContainerId::TraitId(iterator_trait_id) => {
576                        let iterator_trait_items = &iterator_trait_id.trait_items(db).items;
577                        iterator_trait_items.iter().find_map(|(name, it)| match it {
578                            &AssocItemId::FunctionId(id) if *name == sym::filter_map => Some(id),
579                            _ => None,
580                        })
581                    }
582                    _ => None,
583                },
584            ),
585            None => (None, None),
586        };
587        Self {
588            filter_map_function_id,
589            next_function_id,
590            prev_filter_map_expr_id: None,
591            prev_receiver_ty: None,
592        }
593    }
594
595    // check for instances of .filter_map(..).next()
596    fn check(
597        &mut self,
598        current_expr_id: ExprId,
599        receiver_expr_id: &ExprId,
600        function_id: &hir_def::FunctionId,
601    ) -> Option<()> {
602        if *function_id == self.filter_map_function_id? {
603            self.prev_filter_map_expr_id = Some(current_expr_id);
604            return None;
605        }
606
607        if *function_id == self.next_function_id?
608            && let Some(prev_filter_map_expr_id) = self.prev_filter_map_expr_id
609        {
610            let is_dyn_trait = self
611                .prev_receiver_ty
612                .as_ref()
613                .is_some_and(|it| it.strip_references().dyn_trait().is_some());
614            if *receiver_expr_id == prev_filter_map_expr_id && !is_dyn_trait {
615                return Some(());
616            }
617        }
618
619        self.prev_filter_map_expr_id = None;
620        None
621    }
622}
623
624pub fn record_literal_missing_fields<'db>(
625    db: &'db dyn HirDatabase,
626    infer: &InferenceResult<'db>,
627    id: ExprId,
628    expr: &Expr,
629) -> Option<(VariantId, Vec<LocalFieldId>)> {
630    let (fields, spread) = match expr {
631        Expr::RecordLit { fields, spread, .. } => (fields, spread),
632        _ => return None,
633    };
634
635    let variant_def = infer.variant_resolution_for_expr(id)?;
636    if let VariantId::UnionId(_) = variant_def {
637        return None;
638    }
639
640    let variant_data = variant_def.fields(db);
641
642    let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
643    // don't show missing fields if:
644    // - has ..expr
645    // - or has default value + ..
646    // - or already in code
647    let missed_fields: Vec<LocalFieldId> = variant_data
648        .fields()
649        .iter()
650        .filter_map(|(f, d)| {
651            if specified_fields.contains(&d.name)
652                || matches!(spread, RecordSpread::Expr(_))
653                || (d.default_value.is_some() && matches!(spread, RecordSpread::FieldDefaults))
654            {
655                None
656            } else {
657                Some(f)
658            }
659        })
660        .collect();
661    if missed_fields.is_empty() {
662        return None;
663    }
664    Some((variant_def, missed_fields))
665}
666
667pub fn record_pattern_missing_fields<'db>(
668    db: &'db dyn HirDatabase,
669    infer: &InferenceResult<'db>,
670    id: PatId,
671    pat: &Pat,
672) -> Option<(VariantId, Vec<LocalFieldId>)> {
673    let (fields, ellipsis) = match pat {
674        Pat::Record { path: _, args, ellipsis } => (args, *ellipsis),
675        _ => return None,
676    };
677
678    let variant_def = infer.variant_resolution_for_pat(id)?;
679    if let VariantId::UnionId(_) = variant_def {
680        return None;
681    }
682
683    let variant_data = variant_def.fields(db);
684
685    let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
686    // don't show missing fields if:
687    // - in code
688    // - or has ..
689    let missed_fields: Vec<LocalFieldId> = variant_data
690        .fields()
691        .iter()
692        .filter_map(
693            |(f, d)| {
694                if specified_fields.contains(&d.name) || ellipsis { None } else { Some(f) }
695            },
696        )
697        .collect();
698    if missed_fields.is_empty() {
699        return None;
700    }
701    Some((variant_def, missed_fields))
702}
703
704fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResult<'_>) -> bool {
705    fn walk(pat: PatId, body: &Body, infer: &InferenceResult<'_>, has_type_mismatches: &mut bool) {
706        match infer.pat_has_type_mismatch(pat) {
707            true => *has_type_mismatches = true,
708            false if *has_type_mismatches => (),
709            false => {
710                let pat = &body[pat];
711                if let Pat::ConstBlock(expr) | Pat::Lit(expr) = *pat {
712                    *has_type_mismatches |= infer.expr_has_type_mismatch(expr);
713                    if *has_type_mismatches {
714                        return;
715                    }
716                }
717                pat.walk_child_pats(|subpat| walk(subpat, body, infer, has_type_mismatches))
718            }
719        }
720    }
721
722    let mut has_type_mismatches = false;
723    walk(pat, body, infer, &mut has_type_mismatches);
724    !has_type_mismatches
725}
726
727fn missing_match_arms<'a, 'db>(
728    cx: &MatchCheckCtx<'a, 'db>,
729    scrut_ty: Ty<'a>,
730    witnesses: Vec<WitnessPat<'a, 'db>>,
731    arms_is_empty: bool,
732    krate: Crate,
733) -> String {
734    struct DisplayWitness<'a, 'b, 'db>(
735        &'a WitnessPat<'b, 'db>,
736        &'a MatchCheckCtx<'b, 'db>,
737        DisplayTarget,
738    );
739    impl fmt::Display for DisplayWitness<'_, '_, '_> {
740        fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
741            let DisplayWitness(witness, cx, display_target) = *self;
742            let pat = cx.hoist_witness_pat(witness);
743            write!(f, "{}", pat.display(cx.db, display_target))
744        }
745    }
746
747    let non_empty_enum = match scrut_ty.as_adt() {
748        Some((AdtId::EnumId(e), _)) => !e.enum_variants(cx.db).variants.is_empty(),
749        _ => false,
750    };
751    let display_target = DisplayTarget::from_crate(cx.db, krate);
752    if arms_is_empty && !non_empty_enum {
753        format!("type `{}` is non-empty", scrut_ty.display(cx.db, display_target))
754    } else {
755        let pat_display = |witness| DisplayWitness(witness, cx, display_target);
756        const LIMIT: usize = 3;
757        match &*witnesses {
758            [witness] => format!("`{}` not covered", pat_display(witness)),
759            [head @ .., tail] if head.len() < LIMIT => {
760                let head = head.iter().map(pat_display);
761                format!("`{}` and `{}` not covered", head.format("`, `"), pat_display(tail))
762            }
763            _ => {
764                let (head, tail) = witnesses.split_at(LIMIT);
765                let head = head.iter().map(pat_display);
766                format!("`{}` and {} more not covered", head.format("`, `"), tail.len())
767            }
768        }
769    }
770}