2021-05-22 14:39:19 +00:00
|
|
|
//! Various diagnostics for expressions that are collected together in one pass
|
|
|
|
//! through the body using inference results: mismatched arg counts, missing
|
|
|
|
//! fields, etc.
|
2019-09-30 08:58:53 +00:00
|
|
|
|
2021-10-07 18:33:41 +00:00
|
|
|
use std::sync::Arc;
|
2019-01-05 15:32:07 +00:00
|
|
|
|
2022-03-20 23:08:12 +00:00
|
|
|
use hir_def::{path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule};
|
internal: move diagnostics to hir
The idea here is to eventually get rid of `dyn Diagnostic` and
`DiagnosticSink` infrastructure altogether, and just have a `enum
hir::Diagnostic` instead.
The problem with `dyn Diagnostic` is that it is defined in the lowest
level of the stack (hir_expand), but is used by the highest level (ide).
As a first step, we free hir_expand and hir_def from `dyn Diagnostic`
and kick the can up to `hir_ty`, as an intermediate state. The plan is
then to move DiagnosticSink similarly to the hir crate, and, as final
third step, remove its usage from the ide.
One currently unsolved problem is testing. You can notice that the test
which checks precise diagnostic ranges, unresolved_import_in_use_tree,
was moved to the ide layer. Logically, only IDE should have the infra to
render a specific range.
At the same time, the range is determined with the data produced in
hir_def and hir crates, so this layering is rather unfortunate. Working
on hir_def shouldn't require compiling `ide` for testing.
2021-05-23 20:31:59 +00:00
|
|
|
use hir_expand::name;
|
2021-06-13 12:48:54 +00:00
|
|
|
use itertools::Either;
|
2019-11-15 11:53:09 +00:00
|
|
|
use rustc_hash::FxHashSet;
|
2021-10-07 18:33:41 +00:00
|
|
|
use typed_arena::Arena;
|
2019-01-05 15:32:07 +00:00
|
|
|
|
2019-11-15 11:53:09 +00:00
|
|
|
use crate::{
|
|
|
|
db::HirDatabase,
|
2021-06-12 16:28:19 +00:00
|
|
|
diagnostics::match_check::{
|
|
|
|
self,
|
2021-10-07 18:33:41 +00:00
|
|
|
deconstruct_pat::DeconstructedPat,
|
|
|
|
usefulness::{compute_match_usefulness, MatchCheckCtx},
|
2020-07-08 17:58:45 +00:00
|
|
|
},
|
2022-03-27 17:10:31 +00:00
|
|
|
InferenceResult, TyExt,
|
2019-11-15 11:53:09 +00:00
|
|
|
};
|
2019-01-05 15:32:07 +00:00
|
|
|
|
2020-11-02 12:13:32 +00:00
|
|
|
pub(crate) use hir_def::{
|
2021-09-12 11:03:12 +00:00
|
|
|
body::Body,
|
2020-11-02 12:13:32 +00:00
|
|
|
expr::{Expr, ExprId, MatchArm, Pat, PatId},
|
|
|
|
LocalFieldId, VariantId,
|
2019-11-12 12:09:25 +00:00
|
|
|
};
|
2019-09-03 05:56:36 +00:00
|
|
|
|
2021-06-12 16:28:19 +00:00
|
|
|
pub enum BodyValidationDiagnostic {
|
2021-06-13 12:48:54 +00:00
|
|
|
RecordMissingFields {
|
|
|
|
record: Either<ExprId, PatId>,
|
2021-06-12 16:28:19 +00:00
|
|
|
variant: VariantId,
|
|
|
|
missed_fields: Vec<LocalFieldId>,
|
|
|
|
},
|
|
|
|
ReplaceFilterMapNextWithFindMap {
|
|
|
|
method_call_expr: ExprId,
|
|
|
|
},
|
|
|
|
MissingMatchArms {
|
|
|
|
match_expr: ExprId,
|
|
|
|
},
|
|
|
|
}
|
2020-12-28 13:41:15 +00:00
|
|
|
|
2021-06-12 16:28:19 +00:00
|
|
|
impl BodyValidationDiagnostic {
|
2021-06-13 18:55:51 +00:00
|
|
|
pub fn collect(db: &dyn HirDatabase, owner: DefWithBodyId) -> Vec<BodyValidationDiagnostic> {
|
2021-06-12 16:28:19 +00:00
|
|
|
let _p = profile::span("BodyValidationDiagnostic::collect");
|
|
|
|
let infer = db.infer(owner);
|
2021-06-18 11:40:51 +00:00
|
|
|
let mut validator = ExprValidator::new(owner, infer);
|
2021-06-12 16:28:19 +00:00
|
|
|
validator.validate_body(db);
|
|
|
|
validator.diagnostics
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct ExprValidator {
|
2020-07-14 08:28:55 +00:00
|
|
|
owner: DefWithBodyId,
|
2019-11-15 11:53:09 +00:00
|
|
|
infer: Arc<InferenceResult>,
|
2021-06-12 16:28:19 +00:00
|
|
|
pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
|
2019-11-15 11:53:09 +00:00
|
|
|
}
|
|
|
|
|
2021-06-12 16:28:19 +00:00
|
|
|
impl ExprValidator {
|
|
|
|
fn new(owner: DefWithBodyId, infer: Arc<InferenceResult>) -> ExprValidator {
|
2021-06-13 18:55:51 +00:00
|
|
|
ExprValidator { owner, infer, diagnostics: Vec::new() }
|
2019-11-15 11:53:09 +00:00
|
|
|
}
|
|
|
|
|
2021-06-12 16:28:19 +00:00
|
|
|
fn validate_body(&mut self, db: &dyn HirDatabase) {
|
2021-03-17 00:27:56 +00:00
|
|
|
let body = db.body(self.owner);
|
2021-12-25 00:05:56 +00:00
|
|
|
let mut filter_map_next_checker = None;
|
2019-11-15 11:53:09 +00:00
|
|
|
|
2020-04-07 15:09:02 +00:00
|
|
|
for (id, expr) in body.exprs.iter() {
|
2021-06-12 16:28:19 +00:00
|
|
|
if let Some((variant, missed_fields, true)) =
|
2020-04-07 15:09:02 +00:00
|
|
|
record_literal_missing_fields(db, &self.infer, id, expr)
|
|
|
|
{
|
2021-06-13 12:48:54 +00:00
|
|
|
self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
|
|
|
|
record: Either::Left(id),
|
2021-06-12 16:28:19 +00:00
|
|
|
variant,
|
2020-04-09 03:23:51 +00:00
|
|
|
missed_fields,
|
2021-06-12 16:28:19 +00:00
|
|
|
});
|
2020-04-07 15:09:02 +00:00
|
|
|
}
|
2020-07-08 17:58:45 +00:00
|
|
|
|
|
|
|
match expr {
|
|
|
|
Expr::Match { expr, arms } => {
|
2021-05-11 12:18:16 +00:00
|
|
|
self.validate_match(id, *expr, arms, db, self.infer.clone());
|
2020-07-08 17:58:45 +00:00
|
|
|
}
|
|
|
|
Expr::Call { .. } | Expr::MethodCall { .. } => {
|
2021-12-25 00:05:56 +00:00
|
|
|
self.validate_call(db, id, expr, &mut filter_map_next_checker);
|
2020-07-08 17:58:45 +00:00
|
|
|
}
|
|
|
|
_ => {}
|
2019-11-15 11:53:09 +00:00
|
|
|
}
|
|
|
|
}
|
2020-04-09 03:23:51 +00:00
|
|
|
for (id, pat) in body.pats.iter() {
|
2021-06-12 16:28:19 +00:00
|
|
|
if let Some((variant, missed_fields, true)) =
|
2020-04-09 03:23:51 +00:00
|
|
|
record_pattern_missing_fields(db, &self.infer, id, pat)
|
|
|
|
{
|
2021-06-13 12:48:54 +00:00
|
|
|
self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
|
|
|
|
record: Either::Right(id),
|
2021-06-12 16:28:19 +00:00
|
|
|
variant,
|
2020-04-09 03:23:51 +00:00
|
|
|
missed_fields,
|
2021-06-12 16:28:19 +00:00
|
|
|
});
|
2020-04-09 03:23:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-12-25 00:05:56 +00:00
|
|
|
fn validate_call(
|
|
|
|
&mut self,
|
|
|
|
db: &dyn HirDatabase,
|
|
|
|
call_id: ExprId,
|
|
|
|
expr: &Expr,
|
|
|
|
filter_map_next_checker: &mut Option<FilterMapNextChecker>,
|
|
|
|
) {
|
2020-07-08 17:58:45 +00:00
|
|
|
// Check that the number of arguments matches the number of parameters.
|
2020-07-09 10:41:35 +00:00
|
|
|
|
2020-07-09 15:33:49 +00:00
|
|
|
// FIXME: Due to shortcomings in the current type system implementation, only emit this
|
|
|
|
// diagnostic if there are no type mismatches in the containing function.
|
2021-05-19 04:23:16 +00:00
|
|
|
if self.infer.expr_type_mismatches().next().is_some() {
|
2020-12-28 13:41:15 +00:00
|
|
|
return;
|
2020-07-09 10:41:35 +00:00
|
|
|
}
|
|
|
|
|
2022-03-27 17:10:31 +00:00
|
|
|
match expr {
|
|
|
|
Expr::MethodCall { receiver, .. } => {
|
|
|
|
let (callee, _) = match self.infer.method_resolution(call_id) {
|
2021-12-25 00:05:56 +00:00
|
|
|
Some(it) => it,
|
|
|
|
None => return,
|
|
|
|
};
|
|
|
|
|
|
|
|
if filter_map_next_checker
|
|
|
|
.get_or_insert_with(|| {
|
|
|
|
FilterMapNextChecker::new(&self.owner.resolver(db.upcast()), db)
|
|
|
|
})
|
|
|
|
.check(call_id, receiver, &callee)
|
|
|
|
.is_some()
|
|
|
|
{
|
|
|
|
self.diagnostics.push(
|
|
|
|
BodyValidationDiagnostic::ReplaceFilterMapNextWithFindMap {
|
|
|
|
method_call_expr: call_id,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
}
|
2020-07-08 17:58:45 +00:00
|
|
|
}
|
2020-12-28 13:41:15 +00:00
|
|
|
_ => return,
|
2020-07-08 17:58:45 +00:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2020-03-24 11:40:58 +00:00
|
|
|
fn validate_match(
|
|
|
|
&mut self,
|
|
|
|
id: ExprId,
|
2020-04-05 17:16:34 +00:00
|
|
|
match_expr: ExprId,
|
2020-03-24 11:40:58 +00:00
|
|
|
arms: &[MatchArm],
|
|
|
|
db: &dyn HirDatabase,
|
|
|
|
infer: Arc<InferenceResult>,
|
|
|
|
) {
|
2021-09-12 11:03:12 +00:00
|
|
|
let body = db.body(self.owner);
|
2020-03-24 11:40:58 +00:00
|
|
|
|
2021-10-07 18:33:41 +00:00
|
|
|
let match_expr_ty = &infer[match_expr];
|
|
|
|
if match_expr_ty.is_unknown() {
|
2021-03-13 13:44:51 +00:00
|
|
|
return;
|
2021-10-07 18:33:41 +00:00
|
|
|
}
|
2020-03-24 11:40:58 +00:00
|
|
|
|
2021-10-07 18:33:41 +00:00
|
|
|
let pattern_arena = Arena::new();
|
|
|
|
let cx = MatchCheckCtx {
|
|
|
|
module: self.owner.module(db.upcast()),
|
2021-11-27 13:13:47 +00:00
|
|
|
body: self.owner,
|
2021-10-07 18:33:41 +00:00
|
|
|
db,
|
|
|
|
pattern_arena: &pattern_arena,
|
|
|
|
};
|
2021-05-06 04:42:00 +00:00
|
|
|
|
2021-10-07 18:33:41 +00:00
|
|
|
let mut m_arms = Vec::with_capacity(arms.len());
|
2021-05-10 08:22:13 +00:00
|
|
|
let mut has_lowering_errors = false;
|
|
|
|
for arm in arms {
|
|
|
|
if let Some(pat_ty) = infer.type_of_pat.get(arm.pat) {
|
|
|
|
// We only include patterns whose type matches the type
|
2021-08-22 12:31:37 +00:00
|
|
|
// of the match expression. If we had an InvalidMatchArmPattern
|
2021-05-10 08:22:13 +00:00
|
|
|
// diagnostic or similar we could raise that in an else
|
|
|
|
// block here.
|
|
|
|
//
|
|
|
|
// When comparing the types, we also have to consider that rustc
|
|
|
|
// will automatically de-reference the match expression type if
|
|
|
|
// necessary.
|
|
|
|
//
|
|
|
|
// FIXME we should use the type checker for this.
|
2021-05-11 12:18:16 +00:00
|
|
|
if (pat_ty == match_expr_ty
|
2021-05-10 08:22:13 +00:00
|
|
|
|| match_expr_ty
|
|
|
|
.as_reference()
|
|
|
|
.map(|(match_expr_ty, ..)| match_expr_ty == pat_ty)
|
2021-05-11 12:18:16 +00:00
|
|
|
.unwrap_or(false))
|
|
|
|
&& types_of_subpatterns_do_match(arm.pat, &body, &infer)
|
2021-05-10 08:22:13 +00:00
|
|
|
{
|
|
|
|
// If we had a NotUsefulMatchArm diagnostic, we could
|
|
|
|
// check the usefulness of each pattern as we added it
|
|
|
|
// to the matrix here.
|
2021-05-11 12:18:16 +00:00
|
|
|
let m_arm = match_check::MatchArm {
|
2021-10-07 18:33:41 +00:00
|
|
|
pat: self.lower_pattern(&cx, arm.pat, db, &body, &mut has_lowering_errors),
|
2021-05-10 08:22:13 +00:00
|
|
|
has_guard: arm.guard.is_some(),
|
|
|
|
};
|
|
|
|
m_arms.push(m_arm);
|
|
|
|
if !has_lowering_errors {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we can't resolve the type of a pattern, or the pattern type doesn't
|
|
|
|
// fit the match expression, we skip this diagnostic. Skipping the entire
|
|
|
|
// diagnostic rather than just not including this match arm is preferred
|
|
|
|
// to avoid the chance of false positives.
|
2021-06-13 18:55:51 +00:00
|
|
|
cov_mark::hit!(validate_match_bailed_out);
|
2021-05-06 08:32:35 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2021-10-07 18:33:41 +00:00
|
|
|
let report = compute_match_usefulness(&cx, &m_arms, match_expr_ty);
|
2021-04-22 15:17:27 +00:00
|
|
|
|
2021-05-10 08:22:13 +00:00
|
|
|
// FIXME Report unreacheble arms
|
2021-11-21 13:15:21 +00:00
|
|
|
// https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200
|
2021-04-22 15:17:27 +00:00
|
|
|
|
|
|
|
let witnesses = report.non_exhaustiveness_witnesses;
|
2021-05-31 20:44:51 +00:00
|
|
|
// FIXME Report witnesses
|
2021-05-10 08:22:13 +00:00
|
|
|
// eprintln!("compute_match_usefulness(..) -> {:?}", &witnesses);
|
2021-04-22 15:17:27 +00:00
|
|
|
if !witnesses.is_empty() {
|
2021-06-12 16:28:19 +00:00
|
|
|
self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms { match_expr: id });
|
2021-04-22 15:17:27 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-07 18:33:41 +00:00
|
|
|
fn lower_pattern<'p>(
|
2021-05-06 04:42:00 +00:00
|
|
|
&self,
|
2021-10-07 18:33:41 +00:00
|
|
|
cx: &MatchCheckCtx<'_, 'p>,
|
2021-05-06 04:42:00 +00:00
|
|
|
pat: PatId,
|
|
|
|
db: &dyn HirDatabase,
|
|
|
|
body: &Body,
|
2021-05-06 08:32:35 +00:00
|
|
|
have_errors: &mut bool,
|
2021-10-07 18:33:41 +00:00
|
|
|
) -> &'p DeconstructedPat<'p> {
|
2021-05-11 12:18:16 +00:00
|
|
|
let mut patcx = match_check::PatCtxt::new(db, &self.infer, body);
|
2021-05-06 04:42:00 +00:00
|
|
|
let pattern = patcx.lower_pattern(pat);
|
2021-10-07 18:33:41 +00:00
|
|
|
let pattern = cx.pattern_arena.alloc(DeconstructedPat::from_pat(cx, &pattern));
|
2021-05-06 08:32:35 +00:00
|
|
|
if !patcx.errors.is_empty() {
|
|
|
|
*have_errors = true;
|
|
|
|
}
|
|
|
|
pattern
|
2021-05-06 04:42:00 +00:00
|
|
|
}
|
2019-11-15 11:53:09 +00:00
|
|
|
}
|
2020-04-07 15:09:02 +00:00
|
|
|
|
2021-12-25 00:05:56 +00:00
|
|
|
struct FilterMapNextChecker {
|
|
|
|
filter_map_function_id: Option<hir_def::FunctionId>,
|
|
|
|
next_function_id: Option<hir_def::FunctionId>,
|
|
|
|
prev_filter_map_expr_id: Option<ExprId>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl FilterMapNextChecker {
|
|
|
|
fn new(resolver: &hir_def::resolver::Resolver, db: &dyn HirDatabase) -> Self {
|
|
|
|
// Find and store the FunctionIds for Iterator::filter_map and Iterator::next
|
|
|
|
let iterator_path = path![core::iter::Iterator];
|
|
|
|
let mut filter_map_function_id = None;
|
|
|
|
let mut next_function_id = None;
|
|
|
|
|
|
|
|
if let Some(iterator_trait_id) = resolver.resolve_known_trait(db.upcast(), &iterator_path) {
|
|
|
|
let iterator_trait_items = &db.trait_data(iterator_trait_id).items;
|
|
|
|
for item in iterator_trait_items.iter() {
|
|
|
|
if let (name, AssocItemId::FunctionId(id)) = item {
|
|
|
|
if *name == name![filter_map] {
|
|
|
|
filter_map_function_id = Some(*id);
|
|
|
|
}
|
|
|
|
if *name == name![next] {
|
|
|
|
next_function_id = Some(*id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if filter_map_function_id.is_some() && next_function_id.is_some() {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Self { filter_map_function_id, next_function_id, prev_filter_map_expr_id: None }
|
|
|
|
}
|
|
|
|
|
|
|
|
// check for instances of .filter_map(..).next()
|
|
|
|
fn check(
|
|
|
|
&mut self,
|
|
|
|
current_expr_id: ExprId,
|
|
|
|
receiver_expr_id: &ExprId,
|
|
|
|
function_id: &hir_def::FunctionId,
|
|
|
|
) -> Option<()> {
|
|
|
|
if *function_id == self.filter_map_function_id? {
|
|
|
|
self.prev_filter_map_expr_id = Some(current_expr_id);
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
|
|
|
if *function_id == self.next_function_id? {
|
|
|
|
if let Some(prev_filter_map_expr_id) = self.prev_filter_map_expr_id {
|
|
|
|
if *receiver_expr_id == prev_filter_map_expr_id {
|
|
|
|
return Some(());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
self.prev_filter_map_expr_id = None;
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-07 15:09:02 +00:00
|
|
|
pub fn record_literal_missing_fields(
|
|
|
|
db: &dyn HirDatabase,
|
|
|
|
infer: &InferenceResult,
|
|
|
|
id: ExprId,
|
|
|
|
expr: &Expr,
|
2020-04-25 12:23:34 +00:00
|
|
|
) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
|
2021-01-08 14:41:32 +00:00
|
|
|
let (fields, exhaustive) = match expr {
|
2020-04-07 15:09:02 +00:00
|
|
|
Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
|
|
|
|
_ => return None,
|
|
|
|
};
|
|
|
|
|
|
|
|
let variant_def = infer.variant_resolution_for_expr(id)?;
|
|
|
|
if let VariantId::UnionId(_) = variant_def {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2021-04-06 15:59:18 +00:00
|
|
|
let variant_data = variant_def.variant_data(db.upcast());
|
2020-04-07 15:09:02 +00:00
|
|
|
|
|
|
|
let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
|
2020-04-25 12:23:34 +00:00
|
|
|
let missed_fields: Vec<LocalFieldId> = variant_data
|
2020-04-07 15:09:02 +00:00
|
|
|
.fields()
|
|
|
|
.iter()
|
|
|
|
.filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
|
|
|
|
.collect();
|
|
|
|
if missed_fields.is_empty() {
|
|
|
|
return None;
|
|
|
|
}
|
2021-01-08 14:41:32 +00:00
|
|
|
Some((variant_def, missed_fields, exhaustive))
|
2020-04-07 15:09:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn record_pattern_missing_fields(
|
|
|
|
db: &dyn HirDatabase,
|
|
|
|
infer: &InferenceResult,
|
|
|
|
id: PatId,
|
|
|
|
pat: &Pat,
|
2020-04-25 12:23:34 +00:00
|
|
|
) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
|
2020-04-09 03:23:51 +00:00
|
|
|
let (fields, exhaustive) = match pat {
|
|
|
|
Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
|
2020-04-07 15:09:02 +00:00
|
|
|
_ => return None,
|
|
|
|
};
|
|
|
|
|
|
|
|
let variant_def = infer.variant_resolution_for_pat(id)?;
|
|
|
|
if let VariantId::UnionId(_) = variant_def {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
|
2021-04-06 15:59:18 +00:00
|
|
|
let variant_data = variant_def.variant_data(db.upcast());
|
2020-04-07 15:09:02 +00:00
|
|
|
|
|
|
|
let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
|
2020-04-25 12:23:34 +00:00
|
|
|
let missed_fields: Vec<LocalFieldId> = variant_data
|
2020-04-07 15:09:02 +00:00
|
|
|
.fields()
|
|
|
|
.iter()
|
|
|
|
.filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
|
|
|
|
.collect();
|
|
|
|
if missed_fields.is_empty() {
|
|
|
|
return None;
|
|
|
|
}
|
2020-04-09 03:23:51 +00:00
|
|
|
Some((variant_def, missed_fields, exhaustive))
|
2020-04-07 15:09:02 +00:00
|
|
|
}
|
2020-07-09 13:52:10 +00:00
|
|
|
|
2021-05-19 05:57:10 +00:00
|
|
|
fn types_of_subpatterns_do_match(pat: PatId, body: &Body, infer: &InferenceResult) -> bool {
|
|
|
|
fn walk(pat: PatId, body: &Body, infer: &InferenceResult, has_type_mismatches: &mut bool) {
|
|
|
|
match infer.type_mismatch_for_pat(pat) {
|
|
|
|
Some(_) => *has_type_mismatches = true,
|
|
|
|
None => {
|
|
|
|
body[pat].walk_child_pats(|subpat| walk(subpat, body, infer, has_type_mismatches))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut has_type_mismatches = false;
|
|
|
|
walk(pat, body, infer, &mut has_type_mismatches);
|
|
|
|
!has_type_mismatches
|
|
|
|
}
|