Implement better handling of divergence

Divergence here means that for some reason, the end of a block will not be
reached. We tried to model this just using the never type, but that doesn't work
fully (e.g. in `let x = { loop {}; "foo" };` x should still have type `&str`);
so this introduces a `diverges` flag that the type checker keeps track of, like
rustc does.
This commit is contained in:
Florian Diebold 2020-05-08 17:36:11 +02:00
parent d3eb9d8eaf
commit fe7bf993aa
7 changed files with 200 additions and 23 deletions

View file

@ -1,7 +1,7 @@
//! Type inference for expressions.
use std::iter::{repeat, repeat_with};
use std::sync::Arc;
use std::{mem, sync::Arc};
use hir_def::{
builtin_type::Signedness,
@ -21,11 +21,15 @@ use crate::{
Ty, TypeCtor, Uncertain,
};
use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch};
use super::{BindingMode, Expectation, InferenceContext, InferenceDiagnostic, TypeMismatch, Diverges};
impl<'a> InferenceContext<'a> {
pub(super) fn infer_expr(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
let ty = self.infer_expr_inner(tgt_expr, expected);
if ty.is_never() {
// Any expression that produces a value of type `!` must have diverged
self.diverges = Diverges::Always;
}
let could_unify = self.unify(&ty, &expected.ty);
if !could_unify {
self.result.type_mismatches.insert(
@ -64,11 +68,18 @@ impl<'a> InferenceContext<'a> {
// if let is desugared to match, so this is always simple if
self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
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 else_ty = match else_branch {
Some(else_branch) => self.infer_expr_inner(*else_branch, &expected),
None => Ty::unit(),
};
both_arms_diverge &= self.diverges;
self.diverges = condition_diverges | both_arms_diverge;
self.coerce_merge_branch(&then_ty, &else_ty)
}
@ -132,10 +143,12 @@ impl<'a> InferenceContext<'a> {
// infer the body.
self.coerce(&closure_ty, &expected.ty);
let prev_ret_ty = std::mem::replace(&mut self.return_ty, ret_ty.clone());
let prev_diverges = mem::replace(&mut self.diverges, Diverges::Maybe);
let prev_ret_ty = mem::replace(&mut self.return_ty, ret_ty.clone());
self.infer_expr_coerce(*body, &Expectation::has_type(ret_ty));
self.diverges = prev_diverges;
self.return_ty = prev_ret_ty;
closure_ty
@ -165,7 +178,11 @@ impl<'a> InferenceContext<'a> {
self.table.new_type_var()
};
let matchee_diverges = self.diverges;
let mut all_arms_diverge = Diverges::Always;
for arm in arms {
self.diverges = Diverges::Maybe;
let _pat_ty = self.infer_pat(arm.pat, &input_ty, BindingMode::default());
if let Some(guard_expr) = arm.guard {
self.infer_expr(
@ -175,9 +192,12 @@ impl<'a> InferenceContext<'a> {
}
let arm_ty = self.infer_expr_inner(arm.expr, &expected);
all_arms_diverge &= self.diverges;
result_ty = self.coerce_merge_branch(&result_ty, &arm_ty);
}
self.diverges = matchee_diverges | all_arms_diverge;
result_ty
}
Expr::Path(p) => {
@ -522,7 +542,6 @@ impl<'a> InferenceContext<'a> {
tail: Option<ExprId>,
expected: &Expectation,
) -> Ty {
let mut diverges = false;
for stmt in statements {
match stmt {
Statement::Let { pat, type_ref, initializer } => {
@ -544,9 +563,7 @@ impl<'a> InferenceContext<'a> {
self.infer_pat(*pat, &ty, BindingMode::default());
}
Statement::Expr(expr) => {
if let ty_app!(TypeCtor::Never) = self.infer_expr(*expr, &Expectation::none()) {
diverges = true;
}
self.infer_expr(*expr, &Expectation::none());
}
}
}
@ -554,14 +571,22 @@ impl<'a> InferenceContext<'a> {
let ty = if let Some(expr) = tail {
self.infer_expr_coerce(expr, expected)
} else {
self.coerce(&Ty::unit(), expected.coercion_target());
Ty::unit()
// Citing rustc: if there is no explicit tail expression,
// that is typically equivalent to a tail expression
// of `()` -- except if the block diverges. In that
// case, there is no value supplied from the tail
// expression (assuming there are no other breaks,
// this implies that the type of the block will be
// `!`).
if self.diverges.is_always() {
// we don't even make an attempt at coercion
self.table.new_maybe_never_type_var()
} else {
self.coerce(&Ty::unit(), expected.coercion_target());
Ty::unit()
}
};
if diverges {
Ty::simple(TypeCtor::Never)
} else {
ty
}
ty
}
fn infer_method_call(