fix: Diagnose non-value return and break type mismatches

This commit is contained in:
Lukas Wirth 2023-05-16 22:47:27 +02:00
parent 2f8cd66fb4
commit 478705baf5
3 changed files with 62 additions and 23 deletions

View file

@ -50,6 +50,13 @@ fn success(
Ok(InferOk { goals, value: (adj, target) })
}
pub(super) enum CoercionCause {
// FIXME: Make better use of this. Right now things like return and break without a value
// use it to point to themselves, causing us to report a mismatch on those expressions even
// though technically they themselves are `!`
Expr(ExprId),
}
#[derive(Clone, Debug)]
pub(super) struct CoerceMany {
expected_ty: Ty,
@ -90,8 +97,12 @@ impl CoerceMany {
}
}
pub(super) fn coerce_forced_unit(&mut self, ctx: &mut InferenceContext<'_>) {
self.coerce(ctx, None, &ctx.result.standard_types.unit.clone())
pub(super) fn coerce_forced_unit(
&mut self,
ctx: &mut InferenceContext<'_>,
cause: CoercionCause,
) {
self.coerce(ctx, None, &ctx.result.standard_types.unit.clone(), cause)
}
/// Merge two types from different branches, with possible coercion.
@ -106,6 +117,7 @@ impl CoerceMany {
ctx: &mut InferenceContext<'_>,
expr: Option<ExprId>,
expr_ty: &Ty,
cause: CoercionCause,
) {
let expr_ty = ctx.resolve_ty_shallow(expr_ty);
self.expected_ty = ctx.resolve_ty_shallow(&self.expected_ty);
@ -153,11 +165,13 @@ impl CoerceMany {
} else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) {
self.final_ty = Some(res);
} else {
if let Some(id) = expr {
ctx.result.type_mismatches.insert(
id.into(),
TypeMismatch { expected: self.merged_ty(), actual: expr_ty.clone() },
);
match cause {
CoercionCause::Expr(id) => {
ctx.result.type_mismatches.insert(
id.into(),
TypeMismatch { expected: self.merged_ty(), actual: expr_ty.clone() },
);
}
}
cov_mark::hit!(coerce_merge_fail_fallback);
}

View file

@ -26,7 +26,10 @@ use crate::{
autoderef::{builtin_deref, deref_by_trait, Autoderef},
consteval,
infer::{
coerce::CoerceMany, find_continuable, pat::contains_explicit_ref_binding, BreakableKind,
coerce::{CoerceMany, CoercionCause},
find_continuable,
pat::contains_explicit_ref_binding,
BreakableKind,
},
lang_items::lang_items_for_bin_op,
lower::{
@ -132,24 +135,28 @@ impl<'a> InferenceContext<'a> {
);
let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
let mut both_arms_diverge = Diverges::Always;
let then_ty = self.infer_expr_inner(then_branch, expected);
both_arms_diverge &= mem::replace(&mut self.diverges, Diverges::Maybe);
let then_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
let mut coerce = CoerceMany::new(expected.coercion_target_type(&mut self.table));
coerce.coerce(self, Some(then_branch), &then_ty);
coerce.coerce(self, Some(then_branch), &then_ty, CoercionCause::Expr(then_branch));
match else_branch {
Some(else_branch) => {
let else_ty = self.infer_expr_inner(else_branch, expected);
coerce.coerce(self, Some(else_branch), &else_ty);
let else_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
coerce.coerce(
self,
Some(else_branch),
&else_ty,
CoercionCause::Expr(else_branch),
);
self.diverges = condition_diverges | then_diverges & else_diverges;
}
None => {
coerce.coerce_forced_unit(self);
coerce.coerce_forced_unit(self, CoercionCause::Expr(tgt_expr));
self.diverges = condition_diverges;
}
}
both_arms_diverge &= self.diverges;
self.diverges = condition_diverges | both_arms_diverge;
coerce.complete(self)
}
@ -444,7 +451,7 @@ impl<'a> InferenceContext<'a> {
let arm_ty = self.infer_expr_inner(arm.expr, &expected);
all_arms_diverge &= self.diverges;
coerce.coerce(self, Some(arm.expr), &arm_ty);
coerce.coerce(self, Some(arm.expr), &arm_ty, CoercionCause::Expr(arm.expr));
}
self.diverges = matchee_diverges | all_arms_diverge;
@ -492,7 +499,11 @@ impl<'a> InferenceContext<'a> {
match find_breakable(&mut self.breakables, label) {
Some(ctxt) => match ctxt.coerce.take() {
Some(mut coerce) => {
coerce.coerce(self, expr, &val_ty);
let cause = match expr {
Some(expr) => CoercionCause::Expr(expr),
None => CoercionCause::Expr(tgt_expr),
};
coerce.coerce(self, expr, &val_ty, cause);
// Avoiding borrowck
let ctxt = find_breakable(&mut self.breakables, label)
@ -512,7 +523,7 @@ impl<'a> InferenceContext<'a> {
}
self.result.standard_types.never.clone()
}
&Expr::Return { expr } => self.infer_expr_return(expr),
&Expr::Return { expr } => self.infer_expr_return(tgt_expr, expr),
Expr::Yield { expr } => {
if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() {
if let Some(expr) = expr {
@ -952,7 +963,7 @@ impl<'a> InferenceContext<'a> {
let mut coerce = CoerceMany::new(elem_ty);
for &expr in elements.iter() {
let cur_elem_ty = self.infer_expr_inner(expr, &expected);
coerce.coerce(self, Some(expr), &cur_elem_ty);
coerce.coerce(self, Some(expr), &cur_elem_ty, CoercionCause::Expr(expr));
}
(
coerce.complete(self),
@ -997,18 +1008,18 @@ impl<'a> InferenceContext<'a> {
.expected_ty();
let return_expr_ty = self.infer_expr_inner(expr, &Expectation::HasType(ret_ty));
let mut coerce_many = self.return_coercion.take().unwrap();
coerce_many.coerce(self, Some(expr), &return_expr_ty);
coerce_many.coerce(self, Some(expr), &return_expr_ty, CoercionCause::Expr(expr));
self.return_coercion = Some(coerce_many);
}
fn infer_expr_return(&mut self, expr: Option<ExprId>) -> Ty {
fn infer_expr_return(&mut self, ret: ExprId, expr: Option<ExprId>) -> Ty {
match self.return_coercion {
Some(_) => {
if let Some(expr) = expr {
self.infer_return(expr);
} else {
let mut coerce = self.return_coercion.take().unwrap();
coerce.coerce_forced_unit(self);
coerce.coerce_forced_unit(self, CoercionCause::Expr(ret));
self.return_coercion = Some(coerce);
}
}

View file

@ -678,6 +678,20 @@ struct Bar {
f2: &[u16],
f3: dyn Debug,
}
"#,
);
}
#[test]
fn return_no_value() {
check_diagnostics(
r#"
fn f() -> i32 {
return;
// ^^^^^^ error: expected i32, found ()
0
}
fn g() { return; }
"#,
);
}