Better handle never type and branch merging

Split out tests for never type to another file
This commit is contained in:
uHOOCCOOHu 2019-09-18 03:59:51 +08:00
parent 4bb66df6de
commit bf161fa3e5
No known key found for this signature in database
GPG key ID: CED392DE0C483D00
5 changed files with 392 additions and 181 deletions

View file

@ -13,4 +13,5 @@ test_utils::marks!(
infer_while_let
macro_rules_from_other_crates_are_visible_with_macro_use
prelude_is_macro_use
coerce_merge_fail_fallback
);

View file

@ -297,23 +297,35 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
fn unify_inner_trivial(&mut self, ty1: &Ty, ty2: &Ty) -> bool {
match (ty1, ty2) {
(Ty::Unknown, _) | (_, Ty::Unknown) => true,
(Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2)))
| (Ty::Infer(InferTy::IntVar(tv1)), Ty::Infer(InferTy::IntVar(tv2)))
| (Ty::Infer(InferTy::FloatVar(tv1)), Ty::Infer(InferTy::FloatVar(tv2))) => {
| (Ty::Infer(InferTy::FloatVar(tv1)), Ty::Infer(InferTy::FloatVar(tv2)))
| (
Ty::Infer(InferTy::MaybeNeverTypeVar(tv1)),
Ty::Infer(InferTy::MaybeNeverTypeVar(tv2)),
) => {
// both type vars are unknown since we tried to resolve them
self.var_unification_table.union(*tv1, *tv2);
true
}
// The order of MaybeNeverTypeVar matters here.
// Unifying MaybeNeverTypeVar and TypeVar will let the latter become MaybeNeverTypeVar.
// Unifying MaybeNeverTypeVar and other concrete type will let the former become it.
(Ty::Infer(InferTy::TypeVar(tv)), other)
| (other, Ty::Infer(InferTy::TypeVar(tv)))
| (Ty::Infer(InferTy::IntVar(tv)), other)
| (other, Ty::Infer(InferTy::IntVar(tv)))
| (Ty::Infer(InferTy::FloatVar(tv)), other)
| (other, Ty::Infer(InferTy::FloatVar(tv))) => {
| (Ty::Infer(InferTy::MaybeNeverTypeVar(tv)), other)
| (other, Ty::Infer(InferTy::MaybeNeverTypeVar(tv)))
| (Ty::Infer(InferTy::IntVar(tv)), other @ ty_app!(TypeCtor::Int(_)))
| (other @ ty_app!(TypeCtor::Int(_)), Ty::Infer(InferTy::IntVar(tv)))
| (Ty::Infer(InferTy::FloatVar(tv)), other @ ty_app!(TypeCtor::Float(_)))
| (other @ ty_app!(TypeCtor::Float(_)), Ty::Infer(InferTy::FloatVar(tv))) => {
// the type var is unknown since we tried to resolve it
self.var_unification_table.union_value(*tv, TypeVarValue::Known(other.clone()));
true
}
_ => false,
}
}
@ -330,6 +342,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
Ty::Infer(InferTy::FloatVar(self.var_unification_table.new_key(TypeVarValue::Unknown)))
}
fn new_maybe_never_type_var(&mut self) -> Ty {
Ty::Infer(InferTy::MaybeNeverTypeVar(
self.var_unification_table.new_key(TypeVarValue::Unknown),
))
}
/// Replaces Ty::Unknown by a new type var, so we can maybe still infer it.
fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty {
match ty {
@ -817,6 +835,24 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
ty
}
/// Merge two types from different branches, with possible implicit coerce.
///
/// Note that it is only possible that one type are coerced to another.
/// Coercing both types to another least upper bound type is not possible in rustc,
/// which will simply result in "incompatible types" error.
fn coerce_merge_branch<'t>(&mut self, ty1: &Ty, ty2: &Ty) -> Ty {
if self.coerce(ty1, ty2) {
ty2.clone()
} else if self.coerce(ty2, ty1) {
ty1.clone()
} else {
tested_by!(coerce_merge_fail_fallback);
// For incompatible types, we use the latter one as result
// to be better recovery for `if` without `else`.
ty2.clone()
}
}
/// Unify two types, but may coerce the first one to the second one
/// using "implicit coercion rules" if needed.
///
@ -828,12 +864,26 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
}
fn coerce_inner(&mut self, mut from_ty: Ty, to_ty: &Ty) -> bool {
match (&mut from_ty, &*to_ty) {
// Top and bottom type
match (&from_ty, to_ty) {
// Never type will make type variable to fallback to Never Type instead of Unknown.
(ty_app!(TypeCtor::Never), Ty::Infer(InferTy::TypeVar(tv))) => {
let var = self.new_maybe_never_type_var();
self.var_unification_table.union_value(*tv, TypeVarValue::Known(var));
return true;
}
(ty_app!(TypeCtor::Never), _) => return true,
// FIXME: Solve `FromTy: CoerceUnsized<ToTy>` instead of listing common impls here.
// Trivial cases, this should go after `never` check to
// avoid infer result type to be never
_ => {
if self.unify_inner_trivial(&from_ty, &to_ty) {
return true;
}
}
}
// Pointer weakening and function to pointer
match (&mut from_ty, to_ty) {
// `*mut T`, `&mut T, `&T`` -> `*const T`
// `&mut T` -> `&T`
// `&mut T` -> `*mut T`
@ -866,71 +916,67 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
}
}
// Trivial cases, this should go after `never` check to
// avoid infer result type to be never
_ => {
if self.unify_inner_trivial(&from_ty, &to_ty) {
return true;
}
}
_ => {}
}
// Try coerce or unify
// FIXME: Solve `FromTy: CoerceUnsized<ToTy>` instead of listing common impls here.
match (&from_ty, &to_ty) {
// FIXME: Solve `FromTy: CoerceUnsized<ToTy>` instead of listing common impls here.
// Mutilibity is checked above
(ty_app!(TypeCtor::Ref(_), st1), ty_app!(TypeCtor::Ref(_), st2))
| (ty_app!(TypeCtor::RawPtr(_), st1), ty_app!(TypeCtor::RawPtr(_), st2)) => {
match self.try_coerce_unsized(&st1[0], &st2[0], 0) {
Some(ret) => return ret,
None => {}
if self.try_coerce_unsized(&st1[0], &st2[0], 0) {
return true;
}
}
_ => {}
}
// Auto Deref if cannot coerce
match (&from_ty, &to_ty) {
match (&from_ty, to_ty) {
// FIXME: DerefMut
(ty_app!(TypeCtor::Ref(_), st1), ty_app!(TypeCtor::Ref(_), st2)) => {
self.unify_autoderef_behind_ref(&st1[0], &st2[0])
}
// Normal unify
_ => self.unify(&from_ty, &to_ty),
// Otherwise, normal unify
_ => self.unify(&from_ty, to_ty),
}
}
/// Coerce a type to a DST if `FromTy: Unsize<ToTy>`
///
/// See: `https://doc.rust-lang.org/nightly/std/marker/trait.Unsize.html`
fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty, depth: usize) -> Option<bool> {
fn try_coerce_unsized(&mut self, from_ty: &Ty, to_ty: &Ty, depth: usize) -> bool {
if depth > 1000 {
panic!("Infinite recursion in coercion");
}
// FIXME: Correctly handle
match (&from_ty, &to_ty) {
// `[T; N]` -> `[T]`
(ty_app!(TypeCtor::Array, st1), ty_app!(TypeCtor::Slice, st2)) => {
Some(self.unify(&st1[0], &st2[0]))
self.unify(&st1[0], &st2[0])
}
// `T` -> `dyn Trait` when `T: Trait`
(_, Ty::Dyn(_)) => {
// FIXME: Check predicates
Some(true)
true
}
(ty_app!(ctor1, st1), ty_app!(ctor2, st2)) if ctor1 == ctor2 => {
(
ty_app!(TypeCtor::Adt(Adt::Struct(struct1)), st1),
ty_app!(TypeCtor::Adt(Adt::Struct(struct2)), st2),
) if struct1 == struct2 => {
// FIXME: Check preconditions here
for (ty1, ty2) in st1.iter().zip(st2.iter()) {
match self.try_coerce_unsized(ty1, ty2, depth + 1) {
Some(true) => {}
ret => return ret,
if !self.try_coerce_unsized(ty1, ty2, depth + 1) {
return false;
}
}
Some(true)
true
}
_ => None,
_ => false,
}
}
@ -980,18 +1026,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
let then_ty = self.infer_expr_inner(*then_branch, &expected);
self.coerce(&then_ty, &expected.ty);
let else_ty = match else_branch {
Some(else_branch) => self.infer_expr_inner(*else_branch, &expected),
None => Ty::unit(),
};
if !self.coerce(&else_ty, &expected.ty) {
self.coerce(&expected.ty, &else_ty);
else_ty.clone()
} else {
expected.ty.clone()
}
self.coerce_merge_branch(&then_ty, &else_ty)
}
Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected),
Expr::TryBlock { body } => {
@ -1087,11 +1127,8 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
.infer_method_call(tgt_expr, *receiver, &args, &method_name, generic_args.as_ref()),
Expr::Match { expr, arms } => {
let input_ty = self.infer_expr(*expr, &Expectation::none());
let mut expected = match expected.ty {
Ty::Unknown => Expectation::has_type(Ty::simple(TypeCtor::Never)),
_ => expected.clone(),
};
let mut all_never = true;
let mut result_ty = self.new_maybe_never_type_var();
for arm in arms {
for &pat in &arm.pats {
@ -1103,22 +1140,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
&Expectation::has_type(Ty::simple(TypeCtor::Bool)),
);
}
let arm_ty = self.infer_expr_inner(arm.expr, &expected);
match &arm_ty {
ty_app!(TypeCtor::Never) => (),
_ => all_never = false,
}
if !self.coerce(&arm_ty, &expected.ty) {
self.coerce(&expected.ty, &arm_ty);
expected = Expectation::has_type(arm_ty);
}
result_ty = self.coerce_merge_branch(&result_ty, &arm_ty);
}
if all_never {
Ty::simple(TypeCtor::Never)
} else {
expected.ty
}
result_ty
}
Expr::Path(p) => {
// FIXME this could be more efficient...
@ -1558,12 +1585,16 @@ pub enum InferTy {
TypeVar(TypeVarId),
IntVar(TypeVarId),
FloatVar(TypeVarId),
MaybeNeverTypeVar(TypeVarId),
}
impl InferTy {
fn to_inner(self) -> TypeVarId {
match self {
InferTy::TypeVar(ty) | InferTy::IntVar(ty) | InferTy::FloatVar(ty) => ty,
InferTy::TypeVar(ty)
| InferTy::IntVar(ty)
| InferTy::FloatVar(ty)
| InferTy::MaybeNeverTypeVar(ty) => ty,
}
}
@ -1576,6 +1607,7 @@ impl InferTy {
InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(
primitive::UncertainFloatTy::Known(primitive::FloatTy::f64()),
)),
InferTy::MaybeNeverTypeVar(..) => Ty::simple(TypeCtor::Never),
}
}
}

View file

@ -63,6 +63,7 @@ where
InferTy::TypeVar(_) => InferTy::TypeVar(root),
InferTy::IntVar(_) => InferTy::IntVar(root),
InferTy::FloatVar(_) => InferTy::FloatVar(root),
InferTy::MaybeNeverTypeVar(_) => InferTy::MaybeNeverTypeVar(root),
};
let position = self.add(free_var);
Ty::Bound(position as u32)

View file

@ -20,6 +20,8 @@ use crate::{
// against snapshots of the expected results using insta. Use cargo-insta to
// update the snapshots.
mod never_type;
#[test]
fn infer_await() {
let (mut db, pos) = MockDatabase::with_position(
@ -1077,6 +1079,42 @@ fn test(i: i32) {
);
}
#[test]
fn coerce_merge_one_by_one1() {
covers!(coerce_merge_fail_fallback);
assert_snapshot!(
infer(r#"
fn test() {
let t = &mut 1;
let x = match 1 {
1 => t as *mut i32,
2 => t as &i32,
_ => t as *const i32,
};
}
"#),
@r###"
[11; 145) '{ ... }; }': ()
[21; 22) 't': &mut i32
[25; 31) '&mut 1': &mut i32
[30; 31) '1': i32
[41; 42) 'x': *const i32
[45; 142) 'match ... }': *const i32
[51; 52) '1': i32
[63; 64) '1': i32
[68; 69) 't': &mut i32
[68; 81) 't as *mut i32': *mut i32
[91; 92) '2': i32
[96; 97) 't': &mut i32
[96; 105) 't as &i32': &i32
[115; 116) '_': i32
[120; 121) 't': &mut i32
[120; 135) 't as *const i32': *const i32
"###
);
}
#[test]
fn bug_484() {
assert_snapshot!(
@ -2458,7 +2496,6 @@ fn extra_compiler_flags() {
}
"#),
@r###"
[27; 323) '{ ... } }': ()
[33; 321) 'for co... }': ()
[37; 44) 'content': &{unknown}
@ -2472,8 +2509,8 @@ fn extra_compiler_flags() {
[135; 167) '{ ... }': &&{unknown}
[149; 157) '&content': &&{unknown}
[150; 157) 'content': &{unknown}
[182; 189) 'content': &&{unknown}
[192; 314) 'if ICE... }': &&{unknown}
[182; 189) 'content': &{unknown}
[192; 314) 'if ICE... }': &{unknown}
[195; 232) 'ICE_RE..._VALUE': {unknown}
[195; 248) 'ICE_RE...&name)': bool
[242; 247) '&name': &&&{unknown}
@ -4683,121 +4720,3 @@ fn no_such_field_diagnostics() {
"###
);
}
mod branching_with_never_tests {
use super::type_at;
#[test]
fn if_never() {
let t = type_at(
r#"
//- /main.rs
fn test() {
let i = if true {
loop {}
} else {
3.0
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}
#[test]
fn if_else_never() {
let t = type_at(
r#"
//- /main.rs
fn test(input: bool) {
let i = if input {
2.0
} else {
return
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}
#[test]
fn match_first_arm_never() {
let t = type_at(
r#"
//- /main.rs
fn test(a: i32) {
let i = match a {
1 => return,
2 => 2.0,
3 => loop {},
_ => 3.0,
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}
#[test]
fn match_second_arm_never() {
let t = type_at(
r#"
//- /main.rs
fn test(a: i32) {
let i = match a {
1 => 3.0,
2 => loop {},
3 => 3.0,
_ => return,
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}
#[test]
fn match_all_arms_never() {
let t = type_at(
r#"
//- /main.rs
fn test(a: i32) {
let i = match a {
2 => return,
_ => loop {},
};
i<|>
()
}
"#,
);
assert_eq!(t, "!");
}
#[test]
fn match_no_never_arms() {
let t = type_at(
r#"
//- /main.rs
fn test(a: i32) {
let i = match a {
2 => 2.0,
_ => 3.0,
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}
}

View file

@ -0,0 +1,258 @@
use super::type_at;
#[test]
fn infer_never1() {
let t = type_at(
r#"
//- /main.rs
fn test() {
let t = return;
t<|>;
}
"#,
);
assert_eq!(t, "!");
}
#[test]
fn infer_never2() {
let t = type_at(
r#"
//- /main.rs
trait Foo { fn gen() -> Self; }
impl Foo for ! { fn gen() -> Self { loop {} } }
impl Foo for () { fn gen() -> Self { loop {} } }
fn test() {
let a = Foo::gen();
if false { a } else { loop {} };
a<|>;
}
"#,
);
assert_eq!(t, "!");
}
#[test]
fn infer_never3() {
let t = type_at(
r#"
//- /main.rs
trait Foo { fn gen() -> Self; }
impl Foo for ! { fn gen() -> Self { loop {} } }
impl Foo for () { fn gen() -> Self { loop {} } }
fn test() {
let a = Foo::gen();
if false { loop {} } else { a };
a<|>;
}
"#,
);
assert_eq!(t, "!");
}
#[test]
fn never_type_in_generic_args() {
let t = type_at(
r#"
//- /main.rs
enum Option<T> { None, Some(T) }
fn test() {
let a = if true { Option::None } else { Option::Some(return) };
a<|>;
}
"#,
);
assert_eq!(t, "Option<!>");
}
#[test]
fn never_type_can_be_reinferred1() {
let t = type_at(
r#"
//- /main.rs
trait Foo { fn gen() -> Self; }
impl Foo for ! { fn gen() -> Self { loop {} } }
impl Foo for () { fn gen() -> Self { loop {} } }
fn test() {
let a = Foo::gen();
if false { loop {} } else { a };
a<|>;
if false { a };
}
"#,
);
assert_eq!(t, "()");
}
#[test]
fn never_type_can_be_reinferred2() {
let t = type_at(
r#"
//- /main.rs
enum Option<T> { None, Some(T) }
fn test() {
let a = if true { Option::None } else { Option::Some(return) };
a<|>;
match 42 {
42 => a,
_ => Option::Some(42),
};
}
"#,
);
assert_eq!(t, "Option<i32>");
}
#[test]
fn never_type_can_be_reinferred3() {
let t = type_at(
r#"
//- /main.rs
enum Option<T> { None, Some(T) }
fn test() {
let a = if true { Option::None } else { Option::Some(return) };
a<|>;
match 42 {
42 => a,
_ => Option::Some("str"),
};
}
"#,
);
assert_eq!(t, "Option<&str>");
}
#[test]
fn match_no_arm() {
let t = type_at(
r#"
//- /main.rs
enum Void {}
fn test(a: Void) {
let t = match a {};
t<|>;
}
"#,
);
assert_eq!(t, "!");
}
#[test]
fn if_never() {
let t = type_at(
r#"
//- /main.rs
fn test() {
let i = if true {
loop {}
} else {
3.0
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}
#[test]
fn if_else_never() {
let t = type_at(
r#"
//- /main.rs
fn test(input: bool) {
let i = if input {
2.0
} else {
return
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}
#[test]
fn match_first_arm_never() {
let t = type_at(
r#"
//- /main.rs
fn test(a: i32) {
let i = match a {
1 => return,
2 => 2.0,
3 => loop {},
_ => 3.0,
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}
#[test]
fn match_second_arm_never() {
let t = type_at(
r#"
//- /main.rs
fn test(a: i32) {
let i = match a {
1 => 3.0,
2 => loop {},
3 => 3.0,
_ => return,
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}
#[test]
fn match_all_arms_never() {
let t = type_at(
r#"
//- /main.rs
fn test(a: i32) {
let i = match a {
2 => return,
_ => loop {},
};
i<|>
()
}
"#,
);
assert_eq!(t, "!");
}
#[test]
fn match_no_never_arms() {
let t = type_at(
r#"
//- /main.rs
fn test(a: i32) {
let i = match a {
2 => 2.0,
_ => 3.0,
};
i<|>
()
}
"#,
);
assert_eq!(t, "f64");
}