Implement type inference for assignee expressions

This commit is contained in:
Ryo Yoshida 2022-05-31 22:15:01 +09:00
parent 62d6b5a594
commit b7a4175cbb
No known key found for this signature in database
GPG Key ID: E25698A930586171
4 changed files with 416 additions and 3 deletions

View File

@ -137,6 +137,14 @@ fn infer(
) -> Ty;
}
impl PatLike for ExprId {
type BindingMode = ();
fn infer(this: &mut InferenceContext, id: Self, expected_ty: &Ty, _: Self::BindingMode) -> Ty {
this.infer_assignee_expr(id, expected_ty)
}
}
impl PatLike for PatId {
type BindingMode = BindingMode;

View File

@ -593,8 +593,8 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
}
Expr::BinaryOp { lhs, rhs, op } => match op {
Some(BinaryOp::Assignment { op: None }) => {
let lhs_ty = self.infer_expr(*lhs, &Expectation::none());
self.infer_expr_coerce(*rhs, &Expectation::has_type(lhs_ty));
let rhs_ty = self.infer_expr(*rhs, &Expectation::none());
self.infer_assignee_expr(*lhs, &rhs_ty);
self.result.standard_types.unit.clone()
}
Some(BinaryOp::LogicOp(_)) => {
@ -817,6 +817,95 @@ fn infer_expr_box(&mut self, inner_expr: ExprId, expected: &Expectation) -> Ty {
}
}
pub(super) fn infer_assignee_expr(&mut self, lhs: ExprId, rhs_ty: &Ty) -> Ty {
let is_rest_expr = |expr| {
matches!(
&self.body[expr],
Expr::Range { lhs: None, rhs: None, range_type: RangeOp::Exclusive },
)
};
let rhs_ty = self.resolve_ty_shallow(rhs_ty);
let ty = match &self.body[lhs] {
Expr::Tuple { exprs } => {
// We don't consider multiple ellipses. This is analogous to
// `hir_def::body::lower::ExprCollector::collect_tuple_pat()`.
let ellipsis = exprs.iter().position(|e| is_rest_expr(*e));
let exprs: Vec<_> = exprs.iter().filter(|e| !is_rest_expr(**e)).copied().collect();
self.infer_tuple_pat_like(&rhs_ty, (), ellipsis, &exprs)
}
Expr::Call { callee, args } => {
// Tuple structs
let path = match &self.body[*callee] {
Expr::Path(path) => Some(path),
_ => None,
};
// We don't consider multiple ellipses. This is analogous to
// `hir_def::body::lower::ExprCollector::collect_tuple_pat()`.
let ellipsis = args.iter().position(|e| is_rest_expr(*e));
let args: Vec<_> = args.iter().filter(|e| !is_rest_expr(**e)).copied().collect();
self.infer_tuple_struct_pat_like(path, &rhs_ty, (), lhs, ellipsis, &args)
}
Expr::Array(Array::ElementList(elements)) => {
let elem_ty = match rhs_ty.kind(Interner) {
TyKind::Array(st, _) => st.clone(),
_ => self.err_ty(),
};
// There's no need to handle `..` as it cannot be bound.
let sub_exprs = elements.iter().filter(|e| !is_rest_expr(**e));
for e in sub_exprs {
self.infer_assignee_expr(*e, &elem_ty);
}
match rhs_ty.kind(Interner) {
TyKind::Array(_, _) => rhs_ty.clone(),
// Even when `rhs_ty` is not an array type, this assignee
// expression is infered to be an array (of unknown element
// type and length). This should not be just an error type,
// because we are to compute the unifiability of this type and
// `rhs_ty` in the end of this function to issue type mismatches.
_ => TyKind::Array(self.err_ty(), crate::consteval::usize_const(None))
.intern(Interner),
}
}
Expr::RecordLit { path, fields, .. } => {
let subs = fields.iter().map(|f| (f.name.clone(), f.expr));
self.infer_record_pat_like(path.as_deref(), &rhs_ty, (), lhs.into(), subs)
}
Expr::Underscore => rhs_ty.clone(),
_ => {
// `lhs` is a place expression, a unit struct, or an enum variant.
let lhs_ty = self.infer_expr(lhs, &Expectation::none());
// This is the only branch where this function may coerce any type.
// We are returning early to avoid the unifiability check below.
let lhs_ty = self.insert_type_vars_shallow(lhs_ty);
let ty = match self.coerce(None, &rhs_ty, &lhs_ty) {
Ok(ty) => ty,
Err(_) => self.err_ty(),
};
self.write_expr_ty(lhs, ty.clone());
return ty;
}
};
let ty = self.insert_type_vars_shallow(ty);
if !self.unify(&ty, &rhs_ty) {
self.result
.type_mismatches
.insert(lhs.into(), TypeMismatch { expected: rhs_ty.clone(), actual: ty.clone() });
}
self.write_expr_ty(lhs, ty.clone());
ty
}
fn infer_overloadable_binop(
&mut self,
lhs: ExprId,

View File

@ -312,6 +312,24 @@ fn f(text: &str) {
);
}
#[test]
fn destructuring_assign_coerce() {
check_no_mismatches(
r"
//- minicore: deref
struct String;
impl core::ops::Deref for String { type Target = str; }
fn g(_text: &str) {}
fn f(text: &str) {
let mut text = text;
let tmp = String;
[text, _] = [&tmp, &tmp];
g(text);
}
",
);
}
#[test]
fn coerce_fn_item_to_fn_ptr() {
check_no_mismatches(

View File

@ -1,6 +1,6 @@
use expect_test::expect;
use super::{check_infer, check_no_mismatches, check_types};
use super::{check, check_infer, check_no_mismatches, check_types};
#[test]
fn infer_box() {
@ -2745,3 +2745,301 @@ fn f() {
"#,
);
}
#[test]
fn destructuring_assignment_slice() {
check_types(
r#"
fn main() {
let a;
//^usize
[a,] = [0usize];
let a;
//^usize
[a, ..] = [0usize; 5];
let a;
//^usize
[.., a] = [0usize; 5];
let a;
//^usize
[.., a, _] = [0usize; 5];
let a;
//^usize
[_, a, ..] = [0usize; 5];
let a: &mut i64 = &mut 0;
[*a, ..] = [1, 2, 3];
let a: usize;
let b;
//^usize
[a, _, b] = [3, 4, 5];
//^usize
let a;
//^i64
let b;
//^i64
[[a, ..], .., [.., b]] = [[1, 2], [3i64, 4], [5, 6], [7, 8]];
}
"#,
);
}
#[test]
fn destructuring_assignment_tuple() {
check_types(
r#"
fn main() {
let a;
//^char
let b;
//^i64
(a, b) = ('c', 0i64);
let a;
//^char
(a, ..) = ('c', 0i64);
let a;
//^i64
(.., a) = ('c', 0i64);
let a;
//^char
let b;
//^i64
(a, .., b) = ('c', 0i64);
let a;
//^char
let b;
//^bool
(a, .., b) = ('c', 0i64, true);
let a;
//^i64
let b;
//^bool
(_, a, .., b) = ('c', 0i64, true);
let a;
//^i64
let b;
//^usize
(_, a, .., b) = ('c', 0i64, true, 0usize);
let mut a = 1;
//^^^^^i64
let mut b: i64 = 0;
(a, b) = (b, a);
}
"#,
);
}
#[test]
fn destructuring_assignment_tuple_struct() {
check_types(
r#"
struct S2(char, i64);
struct S3(char, i64, bool);
struct S4(char, i64, bool usize);
fn main() {
let a;
//^char
let b;
//^i64
S2(a, b) = S2('c', 0i64);
let a;
//^char
let b;
//^i64
S2(a, .., b) = S2('c', 0i64);
let a;
//^char
let b;
//^bool
S3(a, .., b) = S3('c', 0i64, true);
let a;
//^i64
let b;
//^bool
S3(_, a, .., b) = S3('c', 0i64, true);
let a;
//^i64
let b;
//^usize
S4(_, a, .., b) = S4('c', 0i64, true, 0usize);
struct Swap(i64, i64);
let mut a = 1;
//^^^^^i64
let mut b = 0;
//^^^^^i64
Swap(a, b) = Swap(b, a);
}
"#,
);
}
#[test]
fn destructuring_assignment_struct() {
check_types(
r#"
struct S {
a: usize,
b: char,
}
struct T {
s: S,
t: i64,
}
fn main() {
let a;
//^usize
let c;
//^char
S { a, b: c } = S { a: 3, b: 'b' };
let a;
//^char
S { b: a, .. } = S { a: 3, b: 'b' };
let a;
//^char
S { b: a, _ } = S { a: 3, b: 'b' };
let a;
//^usize
let c;
//^char
let t;
//^i64
T { s: S { a, b: c }, t } = T { s: S { a: 3, b: 'b' }, t: 0 };
}
"#,
);
}
#[test]
fn destructuring_assignment_nested() {
check_types(
r#"
struct S {
a: TS,
b: [char; 3],
}
struct TS(usize, i64);
fn main() {
let a;
//^i32
let b;
//^bool
([.., a], .., b, _) = ([0, 1, 2], true, 'c');
let a;
//^i32
let b;
//^i32
[(.., a, _), .., (b, ..)] = [(1, 2); 5];
let a;
//^usize
let b;
//^char
S { a: TS(a, ..), b: [_, b, ..] } = S { a: TS(0, 0), b: ['a'; 3] };
}
"#,
);
}
#[test]
fn destructuring_assignment_unit_struct() {
// taken from rustc; see https://github.com/rust-lang/rust/pull/95380
check_no_mismatches(
r#"
struct S;
enum E { V, }
type A = E;
fn main() {
let mut a;
(S, a) = (S, ());
(E::V, a) = (E::V, ());
(<E>::V, a) = (E::V, ());
(A::V, a) = (E::V, ());
}
impl S {
fn check() {
let a;
(Self, a) = (S, ());
}
}
impl E {
fn check() {
let a;
(Self::V, a) = (E::V, ());
}
}
"#,
);
}
#[test]
fn destructuring_assignment_no_default_binding_mode() {
check(
r#"
struct S { a: usize }
struct TS(usize);
fn main() {
let x;
[x,] = &[1,];
//^^^^expected &[i32; 1], got [{unknown}; _]
// FIXME we only want the outermost error, but this matches the current
// behavior of slice patterns
let x;
[(x,),] = &[(1,),];
// ^^^^expected {unknown}, got ({unknown},)
//^^^^^^^expected &[(i32,); 1], got [{unknown}; _]
let x;
((x,),) = &((1,),);
//^^^^^^^expected &((i32,),), got (({unknown},),)
let x;
(x,) = &(1,);
//^^^^expected &(i32,), got ({unknown},)
let x;
(S { a: x },) = &(S { a: 42 },);
//^^^^^^^^^^^^^expected &(S,), got (S,)
let x;
S { a: x } = &S { a: 42 };
//^^^^^^^^^^expected &S, got S
let x;
TS(x) = &TS(42);
//^^^^^expected &TS, got TS
}
"#,
);
}