mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-03 07:04:49 +00:00
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:
parent
d3eb9d8eaf
commit
fe7bf993aa
7 changed files with 200 additions and 23 deletions
|
@ -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(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue