diff --git a/crates/hir-def/src/body/lower.rs b/crates/hir-def/src/body/lower.rs index 31e3877ea932f..dff3449098fa7 100644 --- a/crates/hir-def/src/body/lower.rs +++ b/crates/hir-def/src/body/lower.rs @@ -536,7 +536,7 @@ impl ExprCollector<'_> { self.alloc_expr(Expr::MacroStmts { tail }, syntax_ptr) } - ast::Expr::UnderscoreExpr(_) => return None, + ast::Expr::UnderscoreExpr(_) => self.alloc_expr(Expr::Underscore, syntax_ptr), }) } diff --git a/crates/hir-def/src/expr.rs b/crates/hir-def/src/expr.rs index bc0608589467b..49b7ef451eda2 100644 --- a/crates/hir-def/src/expr.rs +++ b/crates/hir-def/src/expr.rs @@ -203,6 +203,7 @@ pub enum Expr { }, Array(Array), Literal(Literal), + Underscore, } #[derive(Debug, Clone, Eq, PartialEq)] @@ -345,6 +346,7 @@ impl Expr { }, Expr::MacroStmts { tail } => f(*tail), Expr::Literal(_) => {} + Expr::Underscore => {} } } } diff --git a/crates/hir-ty/src/infer.rs b/crates/hir-ty/src/infer.rs index a19ff8bf60168..ecadfb6ce4eb3 100644 --- a/crates/hir-ty/src/infer.rs +++ b/crates/hir-ty/src/infer.rs @@ -125,6 +125,39 @@ impl Default for BindingMode { } } +/// Used to generalize patterns and assignee expressions. +trait PatLike: Into + Copy { + type BindingMode: Copy; + + fn infer( + this: &mut InferenceContext, + id: Self, + expected_ty: &Ty, + default_bm: Self::BindingMode, + ) -> 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; + + fn infer( + this: &mut InferenceContext, + id: Self, + expected_ty: &Ty, + default_bm: Self::BindingMode, + ) -> Ty { + this.infer_pat(id, expected_ty, default_bm) + } +} + #[derive(Debug)] pub(crate) struct InferOk { value: T, diff --git a/crates/hir-ty/src/infer/expr.rs b/crates/hir-ty/src/infer/expr.rs index 8be236421fb7e..a1a7b17f37d7d 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(_)) => { @@ -775,6 +775,12 @@ impl<'a> InferenceContext<'a> { }, }, Expr::MacroStmts { tail } => self.infer_expr_inner(*tail, expected), + Expr::Underscore => { + // Underscore expressions may only appear in assignee expressions, + // which are handled by `infer_assignee_expr()`, so any underscore + // expression reaching this branch is an error. + self.err_ty() + } }; // use a new type variable if we got unknown here let ty = self.insert_type_vars_shallow(ty); @@ -811,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/infer/pat.rs b/crates/hir-ty/src/infer/pat.rs index c06d262f5e3c2..dc86f696d4fca 100644 --- a/crates/hir-ty/src/infer/pat.rs +++ b/crates/hir-ty/src/infer/pat.rs @@ -4,7 +4,7 @@ use std::iter::repeat_with; use chalk_ir::Mutability; use hir_def::{ - expr::{BindingAnnotation, Expr, Literal, Pat, PatId, RecordFieldPat}, + expr::{BindingAnnotation, Expr, Literal, Pat, PatId}, path::Path, type_ref::ConstScalar, }; @@ -17,15 +17,20 @@ use crate::{ TyKind, }; +use super::PatLike; + impl<'a> InferenceContext<'a> { - fn infer_tuple_struct_pat( + /// Infers type for tuple struct pattern or its corresponding assignee expression. + /// + /// Ellipses found in the original pattern or expression must be filtered out. + pub(super) fn infer_tuple_struct_pat_like( &mut self, path: Option<&Path>, - subpats: &[PatId], expected: &Ty, - default_bm: BindingMode, - id: PatId, + default_bm: T::BindingMode, + id: T, ellipsis: Option, + subs: &[T], ) -> Ty { let (ty, def) = self.resolve_variant(path, true); let var_data = def.map(|it| it.variant_data(self.db.upcast())); @@ -39,8 +44,8 @@ impl<'a> InferenceContext<'a> { let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default(); let (pre, post) = match ellipsis { - Some(idx) => subpats.split_at(idx), - None => (subpats, &[][..]), + Some(idx) => subs.split_at(idx), + None => (subs, &[][..]), }; let post_idx_offset = field_tys.iter().count().saturating_sub(post.len()); @@ -54,22 +59,22 @@ impl<'a> InferenceContext<'a> { field_tys[field].clone().substitute(Interner, &substs) }); let expected_ty = self.normalize_associated_types_in(expected_ty); - self.infer_pat(subpat, &expected_ty, default_bm); + T::infer(self, subpat, &expected_ty, default_bm); } ty } - fn infer_record_pat( + /// Infers type for record pattern or its corresponding assignee expression. + pub(super) fn infer_record_pat_like( &mut self, path: Option<&Path>, - subpats: &[RecordFieldPat], expected: &Ty, - default_bm: BindingMode, - id: PatId, + default_bm: T::BindingMode, + id: T, + subs: impl Iterator, ) -> Ty { let (ty, def) = self.resolve_variant(path, false); - let var_data = def.map(|it| it.variant_data(self.db.upcast())); if let Some(variant) = def { self.write_variant_resolution(id.into(), variant); } @@ -80,18 +85,64 @@ impl<'a> InferenceContext<'a> { ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner)); let field_tys = def.map(|it| self.db.field_types(it)).unwrap_or_default(); - for subpat in subpats { - let matching_field = var_data.as_ref().and_then(|it| it.field(&subpat.name)); - let expected_ty = matching_field.map_or(self.err_ty(), |field| { - field_tys[field].clone().substitute(Interner, &substs) - }); + let var_data = def.map(|it| it.variant_data(self.db.upcast())); + + for (name, inner) in subs { + let expected_ty = var_data + .as_ref() + .and_then(|it| it.field(&name)) + .map_or(self.err_ty(), |f| field_tys[f].clone().substitute(Interner, &substs)); let expected_ty = self.normalize_associated_types_in(expected_ty); - self.infer_pat(subpat.pat, &expected_ty, default_bm); + + T::infer(self, inner, &expected_ty, default_bm); } ty } + /// Infers type for tuple pattern or its corresponding assignee expression. + /// + /// Ellipses found in the original pattern or expression must be filtered out. + pub(super) fn infer_tuple_pat_like( + &mut self, + expected: &Ty, + default_bm: T::BindingMode, + ellipsis: Option, + subs: &[T], + ) -> Ty { + let expectations = match expected.as_tuple() { + Some(parameters) => &*parameters.as_slice(Interner), + _ => &[], + }; + + let ((pre, post), n_uncovered_patterns) = match ellipsis { + Some(idx) => (subs.split_at(idx), expectations.len().saturating_sub(subs.len())), + None => ((&subs[..], &[][..]), 0), + }; + let mut expectations_iter = expectations + .iter() + .cloned() + .map(|a| a.assert_ty_ref(Interner).clone()) + .chain(repeat_with(|| self.table.new_type_var())); + + let mut inner_tys = Vec::with_capacity(n_uncovered_patterns + subs.len()); + + inner_tys.extend(expectations_iter.by_ref().take(n_uncovered_patterns + subs.len())); + + // Process pre + for (ty, pat) in inner_tys.iter_mut().zip(pre) { + *ty = T::infer(self, *pat, ty, default_bm); + } + + // Process post + for (ty, pat) in inner_tys.iter_mut().skip(pre.len() + n_uncovered_patterns).zip(post) { + *ty = T::infer(self, *pat, ty, default_bm); + } + + TyKind::Tuple(inner_tys.len(), Substitution::from_iter(Interner, inner_tys)) + .intern(Interner) + } + pub(super) fn infer_pat( &mut self, pat: PatId, @@ -129,42 +180,7 @@ impl<'a> InferenceContext<'a> { let ty = match &self.body[pat] { Pat::Tuple { args, ellipsis } => { - let expectations = match expected.as_tuple() { - Some(parameters) => &*parameters.as_slice(Interner), - _ => &[], - }; - - let ((pre, post), n_uncovered_patterns) = match ellipsis { - Some(idx) => { - (args.split_at(*idx), expectations.len().saturating_sub(args.len())) - } - None => ((&args[..], &[][..]), 0), - }; - let mut expectations_iter = expectations - .iter() - .cloned() - .map(|a| a.assert_ty_ref(Interner).clone()) - .chain(repeat_with(|| self.table.new_type_var())); - - let mut inner_tys = Vec::with_capacity(n_uncovered_patterns + args.len()); - - inner_tys - .extend(expectations_iter.by_ref().take(n_uncovered_patterns + args.len())); - - // Process pre - for (ty, pat) in inner_tys.iter_mut().zip(pre) { - *ty = self.infer_pat(*pat, ty, default_bm); - } - - // Process post - for (ty, pat) in - inner_tys.iter_mut().skip(pre.len() + n_uncovered_patterns).zip(post) - { - *ty = self.infer_pat(*pat, ty, default_bm); - } - - TyKind::Tuple(inner_tys.len(), Substitution::from_iter(Interner, inner_tys)) - .intern(Interner) + self.infer_tuple_pat_like(&expected, default_bm, *ellipsis, args) } Pat::Or(pats) => { if let Some((first_pat, rest)) = pats.split_first() { @@ -191,16 +207,18 @@ impl<'a> InferenceContext<'a> { let subty = self.infer_pat(*pat, &expectation, default_bm); TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) } - Pat::TupleStruct { path: p, args: subpats, ellipsis } => self.infer_tuple_struct_pat( - p.as_deref(), - subpats, - &expected, - default_bm, - pat, - *ellipsis, - ), + Pat::TupleStruct { path: p, args: subpats, ellipsis } => self + .infer_tuple_struct_pat_like( + p.as_deref(), + &expected, + default_bm, + pat, + *ellipsis, + subpats, + ), Pat::Record { path: p, args: fields, ellipsis: _ } => { - self.infer_record_pat(p.as_deref(), fields, &expected, default_bm, pat) + let subs = fields.iter().map(|f| (f.name.clone(), f.pat)); + self.infer_record_pat_like(p.as_deref(), &expected, default_bm, pat.into(), subs) } Pat::Path(path) => { // FIXME use correct resolver for the surrounding expression 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 +} + "#, + ); +}