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 infer_while_let
macro_rules_from_other_crates_are_visible_with_macro_use macro_rules_from_other_crates_are_visible_with_macro_use
prelude_is_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 { fn unify_inner_trivial(&mut self, ty1: &Ty, ty2: &Ty) -> bool {
match (ty1, ty2) { match (ty1, ty2) {
(Ty::Unknown, _) | (_, Ty::Unknown) => true, (Ty::Unknown, _) | (_, Ty::Unknown) => true,
(Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2))) (Ty::Infer(InferTy::TypeVar(tv1)), Ty::Infer(InferTy::TypeVar(tv2)))
| (Ty::Infer(InferTy::IntVar(tv1)), Ty::Infer(InferTy::IntVar(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 // both type vars are unknown since we tried to resolve them
self.var_unification_table.union(*tv1, *tv2); self.var_unification_table.union(*tv1, *tv2);
true 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) (Ty::Infer(InferTy::TypeVar(tv)), other)
| (other, Ty::Infer(InferTy::TypeVar(tv))) | (other, Ty::Infer(InferTy::TypeVar(tv)))
| (Ty::Infer(InferTy::IntVar(tv)), other) | (Ty::Infer(InferTy::MaybeNeverTypeVar(tv)), other)
| (other, Ty::Infer(InferTy::IntVar(tv))) | (other, Ty::Infer(InferTy::MaybeNeverTypeVar(tv)))
| (Ty::Infer(InferTy::FloatVar(tv)), other) | (Ty::Infer(InferTy::IntVar(tv)), other @ ty_app!(TypeCtor::Int(_)))
| (other, Ty::Infer(InferTy::FloatVar(tv))) => { | (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 // the type var is unknown since we tried to resolve it
self.var_unification_table.union_value(*tv, TypeVarValue::Known(other.clone())); self.var_unification_table.union_value(*tv, TypeVarValue::Known(other.clone()));
true true
} }
_ => false, _ => false,
} }
} }
@ -330,6 +342,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
Ty::Infer(InferTy::FloatVar(self.var_unification_table.new_key(TypeVarValue::Unknown))) 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. /// 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 { fn insert_type_vars_shallow(&mut self, ty: Ty) -> Ty {
match ty { match ty {
@ -817,6 +835,24 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
ty 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 /// Unify two types, but may coerce the first one to the second one
/// using "implicit coercion rules" if needed. /// 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 { fn coerce_inner(&mut self, mut from_ty: Ty, to_ty: &Ty) -> bool {
match (&mut from_ty, &*to_ty) { match (&from_ty, to_ty) {
// Top and bottom type // 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, (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`, `&mut T, `&T`` -> `*const T`
// `&mut T` -> `&T` // `&mut T` -> `&T`
// `&mut T` -> `*mut 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) { 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::Ref(_), st1), ty_app!(TypeCtor::Ref(_), st2))
| (ty_app!(TypeCtor::RawPtr(_), st1), ty_app!(TypeCtor::RawPtr(_), st2)) => { | (ty_app!(TypeCtor::RawPtr(_), st1), ty_app!(TypeCtor::RawPtr(_), st2)) => {
match self.try_coerce_unsized(&st1[0], &st2[0], 0) { if self.try_coerce_unsized(&st1[0], &st2[0], 0) {
Some(ret) => return ret, return true;
None => {}
} }
} }
_ => {} _ => {}
} }
// Auto Deref if cannot coerce // 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)) => { (ty_app!(TypeCtor::Ref(_), st1), ty_app!(TypeCtor::Ref(_), st2)) => {
self.unify_autoderef_behind_ref(&st1[0], &st2[0]) self.unify_autoderef_behind_ref(&st1[0], &st2[0])
} }
// Normal unify // Otherwise, normal unify
_ => self.unify(&from_ty, &to_ty), _ => self.unify(&from_ty, to_ty),
} }
} }
/// Coerce a type to a DST if `FromTy: Unsize<ToTy>` /// Coerce a type to a DST if `FromTy: Unsize<ToTy>`
/// ///
/// See: `https://doc.rust-lang.org/nightly/std/marker/trait.Unsize.html` /// 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 { if depth > 1000 {
panic!("Infinite recursion in coercion"); panic!("Infinite recursion in coercion");
} }
// FIXME: Correctly handle
match (&from_ty, &to_ty) { match (&from_ty, &to_ty) {
// `[T; N]` -> `[T]` // `[T; N]` -> `[T]`
(ty_app!(TypeCtor::Array, st1), ty_app!(TypeCtor::Slice, st2)) => { (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` // `T` -> `dyn Trait` when `T: Trait`
(_, Ty::Dyn(_)) => { (_, Ty::Dyn(_)) => {
// FIXME: Check predicates // 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()) { for (ty1, ty2) in st1.iter().zip(st2.iter()) {
match self.try_coerce_unsized(ty1, ty2, depth + 1) { if !self.try_coerce_unsized(ty1, ty2, depth + 1) {
Some(true) => {} return false;
ret => return ret,
} }
} }
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))); self.infer_expr(*condition, &Expectation::has_type(Ty::simple(TypeCtor::Bool)));
let then_ty = self.infer_expr_inner(*then_branch, &expected); let then_ty = self.infer_expr_inner(*then_branch, &expected);
self.coerce(&then_ty, &expected.ty);
let else_ty = match else_branch { let else_ty = match else_branch {
Some(else_branch) => self.infer_expr_inner(*else_branch, &expected), Some(else_branch) => self.infer_expr_inner(*else_branch, &expected),
None => Ty::unit(), None => Ty::unit(),
}; };
if !self.coerce(&else_ty, &expected.ty) {
self.coerce(&expected.ty, &else_ty); self.coerce_merge_branch(&then_ty, &else_ty)
else_ty.clone()
} else {
expected.ty.clone()
}
} }
Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected), Expr::Block { statements, tail } => self.infer_block(statements, *tail, expected),
Expr::TryBlock { body } => { 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()), .infer_method_call(tgt_expr, *receiver, &args, &method_name, generic_args.as_ref()),
Expr::Match { expr, arms } => { Expr::Match { expr, arms } => {
let input_ty = self.infer_expr(*expr, &Expectation::none()); let input_ty = self.infer_expr(*expr, &Expectation::none());
let mut expected = match expected.ty {
Ty::Unknown => Expectation::has_type(Ty::simple(TypeCtor::Never)), let mut result_ty = self.new_maybe_never_type_var();
_ => expected.clone(),
};
let mut all_never = true;
for arm in arms { for arm in arms {
for &pat in &arm.pats { for &pat in &arm.pats {
@ -1103,22 +1140,12 @@ impl<'a, D: HirDatabase> InferenceContext<'a, D> {
&Expectation::has_type(Ty::simple(TypeCtor::Bool)), &Expectation::has_type(Ty::simple(TypeCtor::Bool)),
); );
} }
let arm_ty = self.infer_expr_inner(arm.expr, &expected); let arm_ty = self.infer_expr_inner(arm.expr, &expected);
match &arm_ty { result_ty = self.coerce_merge_branch(&result_ty, &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);
}
} }
if all_never { result_ty
Ty::simple(TypeCtor::Never)
} else {
expected.ty
}
} }
Expr::Path(p) => { Expr::Path(p) => {
// FIXME this could be more efficient... // FIXME this could be more efficient...
@ -1558,12 +1585,16 @@ pub enum InferTy {
TypeVar(TypeVarId), TypeVar(TypeVarId),
IntVar(TypeVarId), IntVar(TypeVarId),
FloatVar(TypeVarId), FloatVar(TypeVarId),
MaybeNeverTypeVar(TypeVarId),
} }
impl InferTy { impl InferTy {
fn to_inner(self) -> TypeVarId { fn to_inner(self) -> TypeVarId {
match self { 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( InferTy::FloatVar(..) => Ty::simple(TypeCtor::Float(
primitive::UncertainFloatTy::Known(primitive::FloatTy::f64()), 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::TypeVar(_) => InferTy::TypeVar(root),
InferTy::IntVar(_) => InferTy::IntVar(root), InferTy::IntVar(_) => InferTy::IntVar(root),
InferTy::FloatVar(_) => InferTy::FloatVar(root), InferTy::FloatVar(_) => InferTy::FloatVar(root),
InferTy::MaybeNeverTypeVar(_) => InferTy::MaybeNeverTypeVar(root),
}; };
let position = self.add(free_var); let position = self.add(free_var);
Ty::Bound(position as u32) 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 // against snapshots of the expected results using insta. Use cargo-insta to
// update the snapshots. // update the snapshots.
mod never_type;
#[test] #[test]
fn infer_await() { fn infer_await() {
let (mut db, pos) = MockDatabase::with_position( 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] #[test]
fn bug_484() { fn bug_484() {
assert_snapshot!( assert_snapshot!(
@ -2458,7 +2496,6 @@ fn extra_compiler_flags() {
} }
"#), "#),
@r###" @r###"
[27; 323) '{ ... } }': () [27; 323) '{ ... } }': ()
[33; 321) 'for co... }': () [33; 321) 'for co... }': ()
[37; 44) 'content': &{unknown} [37; 44) 'content': &{unknown}
@ -2472,8 +2509,8 @@ fn extra_compiler_flags() {
[135; 167) '{ ... }': &&{unknown} [135; 167) '{ ... }': &&{unknown}
[149; 157) '&content': &&{unknown} [149; 157) '&content': &&{unknown}
[150; 157) 'content': &{unknown} [150; 157) 'content': &{unknown}
[182; 189) 'content': &&{unknown} [182; 189) 'content': &{unknown}
[192; 314) 'if ICE... }': &&{unknown} [192; 314) 'if ICE... }': &{unknown}
[195; 232) 'ICE_RE..._VALUE': {unknown} [195; 232) 'ICE_RE..._VALUE': {unknown}
[195; 248) 'ICE_RE...&name)': bool [195; 248) 'ICE_RE...&name)': bool
[242; 247) '&name': &&&{unknown} [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");
}