Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/hir-ty/src/diagnostics/expr.rs')
-rw-r--r--crates/hir-ty/src/diagnostics/expr.rs369
1 files changed, 369 insertions, 0 deletions
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
new file mode 100644
index 0000000000..335d95c0cf
--- /dev/null
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -0,0 +1,369 @@
+//! Various diagnostics for expressions that are collected together in one pass
+//! through the body using inference results: mismatched arg counts, missing
+//! fields, etc.
+
+use std::sync::Arc;
+
+use hir_def::{path::path, resolver::HasResolver, AssocItemId, DefWithBodyId, HasModule};
+use hir_expand::name;
+use itertools::Either;
+use rustc_hash::FxHashSet;
+use typed_arena::Arena;
+
+use crate::{
+ db::HirDatabase,
+ diagnostics::match_check::{
+ self,
+ deconstruct_pat::DeconstructedPat,
+ usefulness::{compute_match_usefulness, MatchCheckCtx},
+ },
+ InferenceResult, TyExt,
+};
+
+pub(crate) use hir_def::{
+ body::Body,
+ expr::{Expr, ExprId, MatchArm, Pat, PatId},
+ LocalFieldId, VariantId,
+};
+
+pub enum BodyValidationDiagnostic {
+ RecordMissingFields {
+ record: Either<ExprId, PatId>,
+ variant: VariantId,
+ missed_fields: Vec<LocalFieldId>,
+ },
+ ReplaceFilterMapNextWithFindMap {
+ method_call_expr: ExprId,
+ },
+ MissingMatchArms {
+ match_expr: ExprId,
+ },
+}
+
+impl BodyValidationDiagnostic {
+ pub fn collect(db: &dyn HirDatabase, owner: DefWithBodyId) -> Vec<BodyValidationDiagnostic> {
+ let _p = profile::span("BodyValidationDiagnostic::collect");
+ let infer = db.infer(owner);
+ let mut validator = ExprValidator::new(owner, infer);
+ validator.validate_body(db);
+ validator.diagnostics
+ }
+}
+
+struct ExprValidator {
+ owner: DefWithBodyId,
+ infer: Arc<InferenceResult>,
+ pub(super) diagnostics: Vec<BodyValidationDiagnostic>,
+}
+
+impl ExprValidator {
+ fn new(owner: DefWithBodyId, infer: Arc<InferenceResult>) -> ExprValidator {
+ ExprValidator { owner, infer, diagnostics: Vec::new() }
+ }
+
+ fn validate_body(&mut self, db: &dyn HirDatabase) {
+ let body = db.body(self.owner);
+ let mut filter_map_next_checker = None;
+
+ for (id, expr) in body.exprs.iter() {
+ if let Some((variant, missed_fields, true)) =
+ record_literal_missing_fields(db, &self.infer, id, expr)
+ {
+ self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
+ record: Either::Left(id),
+ variant,
+ missed_fields,
+ });
+ }
+
+ match expr {
+ Expr::Match { expr, arms } => {
+ self.validate_match(id, *expr, arms, db, self.infer.clone());
+ }
+ Expr::Call { .. } | Expr::MethodCall { .. } => {
+ self.validate_call(db, id, expr, &mut filter_map_next_checker);
+ }
+ _ => {}
+ }
+ }
+ for (id, pat) in body.pats.iter() {
+ if let Some((variant, missed_fields, true)) =
+ record_pattern_missing_fields(db, &self.infer, id, pat)
+ {
+ self.diagnostics.push(BodyValidationDiagnostic::RecordMissingFields {
+ record: Either::Right(id),
+ variant,
+ missed_fields,
+ });
+ }
+ }
+ }
+
+ fn validate_call(
+ &mut self,
+ db: &dyn HirDatabase,
+ call_id: ExprId,
+ expr: &Expr,
+ filter_map_next_checker: &mut Option<FilterMapNextChecker>,
+ ) {
+ // Check that the number of arguments matches the number of parameters.
+
+ // FIXME: Due to shortcomings in the current type system implementation, only emit this
+ // diagnostic if there are no type mismatches in the containing function.
+ if self.infer.expr_type_mismatches().next().is_some() {
+ return;
+ }
+
+ match expr {
+ Expr::MethodCall { receiver, .. } => {
+ let (callee, _) = match self.infer.method_resolution(call_id) {
+ 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,
+ },
+ );
+ }
+ }
+ _ => return,
+ };
+ }
+
+ fn validate_match(
+ &mut self,
+ id: ExprId,
+ match_expr: ExprId,
+ arms: &[MatchArm],
+ db: &dyn HirDatabase,
+ infer: Arc<InferenceResult>,
+ ) {
+ let body = db.body(self.owner);
+
+ let match_expr_ty = &infer[match_expr];
+ if match_expr_ty.is_unknown() {
+ return;
+ }
+
+ let pattern_arena = Arena::new();
+ let cx = MatchCheckCtx {
+ module: self.owner.module(db.upcast()),
+ body: self.owner,
+ db,
+ pattern_arena: &pattern_arena,
+ };
+
+ let mut m_arms = Vec::with_capacity(arms.len());
+ 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
+ // of the match expression. If we had an InvalidMatchArmPattern
+ // 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.
+ if (pat_ty == match_expr_ty
+ || match_expr_ty
+ .as_reference()
+ .map(|(match_expr_ty, ..)| match_expr_ty == pat_ty)
+ .unwrap_or(false))
+ && types_of_subpatterns_do_match(arm.pat, &body, &infer)
+ {
+ // If we had a NotUsefulMatchArm diagnostic, we could
+ // check the usefulness of each pattern as we added it
+ // to the matrix here.
+ let m_arm = match_check::MatchArm {
+ pat: self.lower_pattern(&cx, arm.pat, db, &body, &mut has_lowering_errors),
+ 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.
+ cov_mark::hit!(validate_match_bailed_out);
+ return;
+ }
+
+ let report = compute_match_usefulness(&cx, &m_arms, match_expr_ty);
+
+ // FIXME Report unreacheble arms
+ // https://github.com/rust-lang/rust/blob/f31622a50/compiler/rustc_mir_build/src/thir/pattern/check_match.rs#L200
+
+ let witnesses = report.non_exhaustiveness_witnesses;
+ // FIXME Report witnesses
+ // eprintln!("compute_match_usefulness(..) -> {:?}", &witnesses);
+ if !witnesses.is_empty() {
+ self.diagnostics.push(BodyValidationDiagnostic::MissingMatchArms { match_expr: id });
+ }
+ }
+
+ fn lower_pattern<'p>(
+ &self,
+ cx: &MatchCheckCtx<'_, 'p>,
+ pat: PatId,
+ db: &dyn HirDatabase,
+ body: &Body,
+ have_errors: &mut bool,
+ ) -> &'p DeconstructedPat<'p> {
+ let mut patcx = match_check::PatCtxt::new(db, &self.infer, body);
+ let pattern = patcx.lower_pattern(pat);
+ let pattern = cx.pattern_arena.alloc(DeconstructedPat::from_pat(cx, &pattern));
+ if !patcx.errors.is_empty() {
+ *have_errors = true;
+ }
+ pattern
+ }
+}
+
+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
+ }
+}
+
+pub fn record_literal_missing_fields(
+ db: &dyn HirDatabase,
+ infer: &InferenceResult,
+ id: ExprId,
+ expr: &Expr,
+) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
+ let (fields, exhaustive) = match expr {
+ 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;
+ }
+
+ let variant_data = variant_def.variant_data(db.upcast());
+
+ let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
+ let missed_fields: Vec<LocalFieldId> = variant_data
+ .fields()
+ .iter()
+ .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
+ .collect();
+ if missed_fields.is_empty() {
+ return None;
+ }
+ Some((variant_def, missed_fields, exhaustive))
+}
+
+pub fn record_pattern_missing_fields(
+ db: &dyn HirDatabase,
+ infer: &InferenceResult,
+ id: PatId,
+ pat: &Pat,
+) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
+ let (fields, exhaustive) = match pat {
+ Pat::Record { path: _, args, ellipsis } => (args, !ellipsis),
+ _ => return None,
+ };
+
+ let variant_def = infer.variant_resolution_for_pat(id)?;
+ if let VariantId::UnionId(_) = variant_def {
+ return None;
+ }
+
+ let variant_data = variant_def.variant_data(db.upcast());
+
+ let specified_fields: FxHashSet<_> = fields.iter().map(|f| &f.name).collect();
+ let missed_fields: Vec<LocalFieldId> = variant_data
+ .fields()
+ .iter()
+ .filter_map(|(f, d)| if specified_fields.contains(&d.name) { None } else { Some(f) })
+ .collect();
+ if missed_fields.is_empty() {
+ return None;
+ }
+ Some((variant_def, missed_fields, exhaustive))
+}
+
+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
+}