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) }) 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)] #[derive(Clone, Debug)]
pub(super) struct CoerceMany { pub(super) struct CoerceMany {
expected_ty: Ty, expected_ty: Ty,
@ -90,8 +97,12 @@ impl CoerceMany {
} }
} }
pub(super) fn coerce_forced_unit(&mut self, ctx: &mut InferenceContext<'_>) { pub(super) fn coerce_forced_unit(
self.coerce(ctx, None, &ctx.result.standard_types.unit.clone()) &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. /// Merge two types from different branches, with possible coercion.
@ -106,6 +117,7 @@ impl CoerceMany {
ctx: &mut InferenceContext<'_>, ctx: &mut InferenceContext<'_>,
expr: Option<ExprId>, expr: Option<ExprId>,
expr_ty: &Ty, expr_ty: &Ty,
cause: CoercionCause,
) { ) {
let expr_ty = ctx.resolve_ty_shallow(expr_ty); let expr_ty = ctx.resolve_ty_shallow(expr_ty);
self.expected_ty = ctx.resolve_ty_shallow(&self.expected_ty); self.expected_ty = ctx.resolve_ty_shallow(&self.expected_ty);
@ -153,12 +165,14 @@ impl CoerceMany {
} else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) { } else if let Ok(res) = ctx.coerce(expr, &self.merged_ty(), &expr_ty) {
self.final_ty = Some(res); self.final_ty = Some(res);
} else { } else {
if let Some(id) = expr { match cause {
CoercionCause::Expr(id) => {
ctx.result.type_mismatches.insert( ctx.result.type_mismatches.insert(
id.into(), id.into(),
TypeMismatch { expected: self.merged_ty(), actual: expr_ty.clone() }, TypeMismatch { expected: self.merged_ty(), actual: expr_ty.clone() },
); );
} }
}
cov_mark::hit!(coerce_merge_fail_fallback); cov_mark::hit!(coerce_merge_fail_fallback);
} }
if let Some(expr) = expr { if let Some(expr) = expr {

View file

@ -26,7 +26,10 @@ use crate::{
autoderef::{builtin_deref, deref_by_trait, Autoderef}, autoderef::{builtin_deref, deref_by_trait, Autoderef},
consteval, consteval,
infer::{ 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, lang_items::lang_items_for_bin_op,
lower::{ lower::{
@ -132,24 +135,28 @@ impl<'a> InferenceContext<'a> {
); );
let condition_diverges = mem::replace(&mut self.diverges, Diverges::Maybe); 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); 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)); 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 { match else_branch {
Some(else_branch) => { Some(else_branch) => {
let else_ty = self.infer_expr_inner(else_branch, expected); 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 => { 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) coerce.complete(self)
} }
@ -444,7 +451,7 @@ impl<'a> InferenceContext<'a> {
let arm_ty = self.infer_expr_inner(arm.expr, &expected); let arm_ty = self.infer_expr_inner(arm.expr, &expected);
all_arms_diverge &= self.diverges; 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; self.diverges = matchee_diverges | all_arms_diverge;
@ -492,7 +499,11 @@ impl<'a> InferenceContext<'a> {
match find_breakable(&mut self.breakables, label) { match find_breakable(&mut self.breakables, label) {
Some(ctxt) => match ctxt.coerce.take() { Some(ctxt) => match ctxt.coerce.take() {
Some(mut coerce) => { 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 // Avoiding borrowck
let ctxt = find_breakable(&mut self.breakables, label) let ctxt = find_breakable(&mut self.breakables, label)
@ -512,7 +523,7 @@ impl<'a> InferenceContext<'a> {
} }
self.result.standard_types.never.clone() 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 } => { Expr::Yield { expr } => {
if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() { if let Some((resume_ty, yield_ty)) = self.resume_yield_tys.clone() {
if let Some(expr) = expr { if let Some(expr) = expr {
@ -952,7 +963,7 @@ impl<'a> InferenceContext<'a> {
let mut coerce = CoerceMany::new(elem_ty); let mut coerce = CoerceMany::new(elem_ty);
for &expr in elements.iter() { for &expr in elements.iter() {
let cur_elem_ty = self.infer_expr_inner(expr, &expected); 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), coerce.complete(self),
@ -997,18 +1008,18 @@ impl<'a> InferenceContext<'a> {
.expected_ty(); .expected_ty();
let return_expr_ty = self.infer_expr_inner(expr, &Expectation::HasType(ret_ty)); let return_expr_ty = self.infer_expr_inner(expr, &Expectation::HasType(ret_ty));
let mut coerce_many = self.return_coercion.take().unwrap(); 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); 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 { match self.return_coercion {
Some(_) => { Some(_) => {
if let Some(expr) = expr { if let Some(expr) = expr {
self.infer_return(expr); self.infer_return(expr);
} else { } else {
let mut coerce = self.return_coercion.take().unwrap(); 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); self.return_coercion = Some(coerce);
} }
} }

View file

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