Implement type inference for assignee expressions
This commit is contained in:
parent
62d6b5a594
commit
b7a4175cbb
@ -137,6 +137,14 @@ fn infer(
|
|||||||
) -> Ty;
|
) -> 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 {
|
impl PatLike for PatId {
|
||||||
type BindingMode = BindingMode;
|
type BindingMode = BindingMode;
|
||||||
|
|
||||||
|
@ -593,8 +593,8 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
|
|||||||
}
|
}
|
||||||
Expr::BinaryOp { lhs, rhs, op } => match op {
|
Expr::BinaryOp { lhs, rhs, op } => match op {
|
||||||
Some(BinaryOp::Assignment { op: None }) => {
|
Some(BinaryOp::Assignment { op: None }) => {
|
||||||
let lhs_ty = self.infer_expr(*lhs, &Expectation::none());
|
let rhs_ty = self.infer_expr(*rhs, &Expectation::none());
|
||||||
self.infer_expr_coerce(*rhs, &Expectation::has_type(lhs_ty));
|
self.infer_assignee_expr(*lhs, &rhs_ty);
|
||||||
self.result.standard_types.unit.clone()
|
self.result.standard_types.unit.clone()
|
||||||
}
|
}
|
||||||
Some(BinaryOp::LogicOp(_)) => {
|
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(
|
fn infer_overloadable_binop(
|
||||||
&mut self,
|
&mut self,
|
||||||
lhs: ExprId,
|
lhs: ExprId,
|
||||||
|
@ -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]
|
#[test]
|
||||||
fn coerce_fn_item_to_fn_ptr() {
|
fn coerce_fn_item_to_fn_ptr() {
|
||||||
check_no_mismatches(
|
check_no_mismatches(
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use expect_test::expect;
|
use expect_test::expect;
|
||||||
|
|
||||||
use super::{check_infer, check_no_mismatches, check_types};
|
use super::{check, check_infer, check_no_mismatches, check_types};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn infer_box() {
|
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
|
||||||
|
}
|
||||||
|
"#,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user