From b7a4175cbba679b704a122fcd95917e4d08f385d Mon Sep 17 00:00:00 2001 From: Ryo Yoshida Date: Tue, 31 May 2022 22:15:01 +0900 Subject: [PATCH] Implement type inference for assignee expressions --- crates/hir-ty/src/infer.rs | 8 + crates/hir-ty/src/infer/expr.rs | 93 ++++++++- crates/hir-ty/src/tests/coercion.rs | 18 ++ crates/hir-ty/src/tests/simple.rs | 300 +++++++++++++++++++++++++++- 4 files changed, 416 insertions(+), 3 deletions(-) diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index 4b80f06a3aed2..ecadfb6ce4eb3 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -137,6 +137,14 @@ trait PatLike: Into + Copy { ) -> 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; diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 4d4d187fe8a22..620d889bd5d1f 100644 --- a/crates/hir-ty/src/infer/expr.rs +++ b/crates/hir-ty/src/infer/expr.rs @@ -593,8 +593,8 @@ impl<'a> InferenceContext<'a> { } 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 @@ impl<'a> InferenceContext<'a> { } } + 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, diff --git a/crates/hir-ty/src/tests/coercion.rs b/crates/hir-ty/src/tests/coercion.rs index 268faf8cb3a06..0e512ef5ec9ec 100644 --- a/crates/hir-ty/src/tests/coercion.rs +++ b/crates/hir-ty/src/tests/coercion.rs @@ -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( diff --git a/crates/hir-ty/src/tests/simple.rs b/crates/hir-ty/src/tests/simple.rs index 46ab2377b5463..535b948371c75 100644 --- a/crates/hir-ty/src/tests/simple.rs +++ b/crates/hir-ty/src/tests/simple.rs @@ -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, ()); + + (::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 +} + "#, + ); +}