From fe7bf993aa8d64668707e348f2ea69918cfda9a4 Mon Sep 17 00:00:00 2001 From: Florian Diebold Date: Fri, 8 May 2020 17:36:11 +0200 Subject: 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. --- crates/ra_hir_ty/src/infer/expr.rs | 53 ++++++++++++++++++++++++++++---------- 1 file changed, 39 insertions(+), 14 deletions(-) (limited to 'crates/ra_hir_ty/src/infer') diff --git a/crates/ra_hir_ty/src/infer/expr.rs b/crates/ra_hir_ty/src/infer/expr.rs index 614c352a0..f2f9883b2 100644 --- a/crates/ra_hir_ty/src/infer/expr.rs +++ b/crates/ra_hir_ty/src/infer/expr.rs @@ -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, 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( -- cgit v1.2.3