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.rs164
1 files changed, 107 insertions, 57 deletions
diff --git a/crates/hir-ty/src/diagnostics/expr.rs b/crates/hir-ty/src/diagnostics/expr.rs
index 068118c705..760ebd27e0 100644
--- a/crates/hir-ty/src/diagnostics/expr.rs
+++ b/crates/hir-ty/src/diagnostics/expr.rs
@@ -7,7 +7,8 @@ use std::fmt;
use base_db::Crate;
use either::Either;
use hir_def::{
- AdtId, AssocItemId, DefWithBodyId, HasModule, ItemContainerId, Lookup,
+ AdtId, AssocItemId, CallableDefId, DefWithBodyId, HasModule, ItemContainerId, Lookup,
+ attrs::AttrFlags,
lang_item::LangItems,
resolver::{HasResolver, ValueNs},
};
@@ -15,7 +16,7 @@ use intern::sym;
use itertools::Itertools;
use rustc_hash::FxHashSet;
use rustc_pattern_analysis::constructor::Constructor;
-use rustc_type_ir::inherent::{AdtDef, IntoKind};
+use rustc_type_ir::inherent::IntoKind;
use syntax::{
AstNode,
ast::{self, UnaryOp},
@@ -33,7 +34,7 @@ use crate::{
},
display::{DisplayTarget, HirDisplay},
next_solver::{
- DbInterner, ParamEnv, Ty, TyKind, TypingMode,
+ CallableIdWrapper, DbInterner, ParamEnv, Ty, TyKind, TypingMode,
infer::{DbInternerInferExt, InferCtxt},
},
};
@@ -44,7 +45,7 @@ pub(crate) use hir_def::{
hir::{Expr, ExprId, MatchArm, Pat, PatId, RecordSpread, Statement},
};
-pub enum BodyValidationDiagnostic {
+pub enum BodyValidationDiagnostic<'db> {
RecordMissingFields {
record: Either<ExprId, PatId>,
variant: VariantId,
@@ -67,14 +68,18 @@ pub enum BodyValidationDiagnostic {
RemoveUnnecessaryElse {
if_expr: ExprId,
},
+ UnusedMustUse {
+ expr: ExprId,
+ message: Option<&'db str>,
+ },
}
-impl BodyValidationDiagnostic {
+impl<'db> BodyValidationDiagnostic<'db> {
pub fn collect(
- db: &dyn HirDatabase,
+ db: &'db dyn HirDatabase,
owner: DefWithBodyId,
validate_lints: bool,
- ) -> Vec<BodyValidationDiagnostic> {
+ ) -> Vec<BodyValidationDiagnostic<'db>> {
let _p = tracing::info_span!("BodyValidationDiagnostic::collect").entered();
let infer = InferenceResult::of(db, owner);
let body = Body::of(db, owner);
@@ -101,7 +106,7 @@ struct ExprValidator<'db> {
body: &'db Body,
infer: &'db InferenceResult,
env: ParamEnv<'db>,
- diagnostics: Vec<BodyValidationDiagnostic>,
+ diagnostics: Vec<BodyValidationDiagnostic<'db>>,
validate_lints: bool,
infcx: InferCtxt<'db>,
}
@@ -177,7 +182,7 @@ impl<'db> ExprValidator<'db> {
}
// Check that the number of arguments matches the number of parameters.
- if self.infer.expr_type_mismatches().next().is_some() {
+ if self.infer.exprs_have_type_mismatches() {
// FIXME: Due to shortcomings in the current type system implementation, only emit
// this diagnostic if there are no type mismatches in the containing function.
} else if let Expr::MethodCall { receiver, .. } = expr {
@@ -218,9 +223,7 @@ impl<'db> ExprValidator<'db> {
// Note: Skipping the entire diagnostic rather than just not including a faulty match arm is
// preferred to avoid the chance of false positives.
for arm in arms {
- let Some(pat_ty) = self.infer.type_of_pat_with_adjust(arm.pat) else {
- return;
- };
+ let pat_ty = self.infer.type_of_pat_with_adjust(arm.pat);
if pat_ty.references_non_lt_error() {
return;
}
@@ -313,7 +316,7 @@ impl<'db> ExprValidator<'db> {
value_or_partial.is_none_or(|v| !matches!(v, ValueNs::StaticId(_)))
}
Expr::Field { expr, .. } => match self.infer.expr_ty(*expr).kind() {
- TyKind::Adt(adt, ..) if matches!(adt.def_id().0, AdtId::UnionId(_)) => false,
+ TyKind::Adt(adt, ..) if matches!(adt.def_id(), AdtId::UnionId(_)) => false,
_ => self.is_known_valid_scrutinee(*expr),
},
Expr::Index { base, .. } => self.is_known_valid_scrutinee(*base),
@@ -330,52 +333,71 @@ impl<'db> ExprValidator<'db> {
let pattern_arena = Arena::new();
let cx = MatchCheckCtx::new(self.owner.module(self.db()), &self.infcx, self.env);
for stmt in &**statements {
- let &Statement::Let { pat, initializer, else_branch: None, .. } = stmt else {
- continue;
+ let diag = match *stmt {
+ Statement::Expr { expr: stmt_expr, has_semi: true } if self.validate_lints => {
+ self.check_unused_must_use(stmt_expr)
+ }
+ Statement::Let { pat, initializer, else_branch: None, .. } => {
+ self.check_non_exhaustive_let(&cx, &pattern_arena, pat, initializer)
+ }
+ _ => None,
};
- if self.infer.type_mismatch_for_pat(pat).is_some() {
- continue;
- }
- let Some(initializer) = initializer else { continue };
- let Some(ty) = self.infer.type_of_expr_with_adjust(initializer) else { continue };
- if ty.references_non_lt_error() {
- continue;
+ if let Some(diag) = diag {
+ self.diagnostics.push(diag);
}
+ }
+ }
- let mut have_errors = false;
- let deconstructed_pat = self.lower_pattern(&cx, pat, &mut have_errors);
+ fn check_non_exhaustive_let<'a>(
+ &self,
+ cx: &MatchCheckCtx<'a, 'db>,
+ pattern_arena: &'a Arena<DeconstructedPat<'a, 'db>>,
+ pat: PatId,
+ initializer: Option<ExprId>,
+ ) -> Option<BodyValidationDiagnostic<'db>> {
+ if self.infer.pat_has_type_mismatch(pat) {
+ return None;
+ }
+ let initializer = initializer?;
+ let ty = self.infer.type_of_expr_with_adjust(initializer)?;
+ if ty.references_non_lt_error() {
+ return None;
+ }
- // optimization, wildcard trivially hold
- if have_errors || matches!(deconstructed_pat.ctor(), Constructor::Wildcard) {
- continue;
- }
+ let mut have_errors = false;
+ let deconstructed_pat = self.lower_pattern(cx, pat, &mut have_errors);
- let match_arm = rustc_pattern_analysis::MatchArm {
- pat: pattern_arena.alloc(deconstructed_pat),
- has_guard: false,
- arm_data: (),
- };
- let report = match cx.compute_match_usefulness(&[match_arm], ty, None) {
- Ok(v) => v,
- Err(e) => {
- debug!(?e, "match usefulness error");
- continue;
- }
- };
- let witnesses = report.non_exhaustiveness_witnesses;
- if !witnesses.is_empty() {
- self.diagnostics.push(BodyValidationDiagnostic::NonExhaustiveLet {
- pat,
- uncovered_patterns: missing_match_arms(
- &cx,
- ty,
- witnesses,
- false,
- self.owner.krate(self.db()),
- ),
- });
+ // optimization, wildcard trivially hold
+ if have_errors || matches!(deconstructed_pat.ctor(), Constructor::Wildcard) {
+ return None;
+ }
+
+ let match_arm = rustc_pattern_analysis::MatchArm {
+ pat: pattern_arena.alloc(deconstructed_pat),
+ has_guard: false,
+ arm_data: (),
+ };
+ let report = match cx.compute_match_usefulness(&[match_arm], ty, None) {
+ Ok(v) => v,
+ Err(e) => {
+ debug!(?e, "match usefulness error");
+ return None;
}
+ };
+ let witnesses = report.non_exhaustiveness_witnesses;
+ if witnesses.is_empty() {
+ return None;
}
+ Some(BodyValidationDiagnostic::NonExhaustiveLet {
+ pat,
+ uncovered_patterns: missing_match_arms(
+ cx,
+ ty,
+ witnesses,
+ false,
+ self.owner.krate(self.db()),
+ ),
+ })
}
fn lower_pattern<'a>(
@@ -393,6 +415,34 @@ impl<'db> ExprValidator<'db> {
pattern
}
+ fn check_unused_must_use(&self, expr: ExprId) -> Option<BodyValidationDiagnostic<'db>> {
+ let fn_def = match &self.body[expr] {
+ Expr::Call { callee, .. } => {
+ let callee_ty = self.infer.expr_ty(*callee);
+ if let TyKind::FnDef(CallableIdWrapper(CallableDefId::FunctionId(func)), _) =
+ callee_ty.kind()
+ {
+ Some(func.into())
+ } else {
+ None
+ }
+ }
+ Expr::MethodCall { .. } => {
+ self.infer.method_resolution(expr).map(|(func, _)| func.into())
+ }
+ _ => return None,
+ };
+ let ty_def = self.infer.type_of_expr_with_adjust(expr).and_then(|ty| match ty.kind() {
+ TyKind::Adt(adt, _) => Some(adt.def_id().into()),
+ _ => None,
+ });
+ let must_use_diag = |owner| {
+ AttrFlags::must_use_message(self.db(), owner?)
+ .map(|message| BodyValidationDiagnostic::UnusedMustUse { expr, message })
+ };
+ must_use_diag(fn_def).or_else(|| must_use_diag(ty_def))
+ }
+
fn check_for_trailing_return(&mut self, body_expr: ExprId, body: &Body) {
if !self.validate_lints {
return;
@@ -630,13 +680,13 @@ pub fn record_pattern_missing_fields(
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 if *has_type_mismatches => (),
- None => {
+ match infer.pat_has_type_mismatch(pat) {
+ true => *has_type_mismatches = true,
+ false if *has_type_mismatches => (),
+ false => {
let pat = &body[pat];
if let Pat::ConstBlock(expr) | Pat::Lit(expr) = *pat {
- *has_type_mismatches |= infer.type_mismatch_for_expr(expr).is_some();
+ *has_type_mismatches |= infer.expr_has_type_mismatch(expr);
if *has_type_mismatches {
return;
}