| //! Type inference for patterns. |
| |
| use std::iter::repeat_with; |
| |
| use chalk_ir::Mutability; |
| use hir_def::{ |
| body::Body, |
| hir::{Binding, BindingAnnotation, BindingId, Expr, ExprId, ExprOrPatId, Literal, Pat, PatId}, |
| path::Path, |
| }; |
| use hir_expand::name::Name; |
| |
| use crate::{ |
| consteval::{try_const_usize, usize_const}, |
| infer::{BindingMode, Expectation, InferenceContext, TypeMismatch}, |
| lower::lower_to_chalk_mutability, |
| primitive::UintTy, |
| static_lifetime, InferenceDiagnostic, Interner, Scalar, Substitution, Ty, TyBuilder, TyExt, |
| TyKind, |
| }; |
| |
| /// Used to generalize patterns and assignee expressions. |
| pub(super) trait PatLike: Into<ExprOrPatId> + 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) |
| } |
| } |
| |
| impl InferenceContext<'_> { |
| /// 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<T: PatLike>( |
| &mut self, |
| path: Option<&Path>, |
| expected: &Ty, |
| default_bm: T::BindingMode, |
| id: T, |
| ellipsis: Option<usize>, |
| subs: &[T], |
| ) -> Ty { |
| let (ty, def) = self.resolve_variant(path, true); |
| let var_data = def.map(|it| it.variant_data(self.db.upcast())); |
| if let Some(variant) = def { |
| self.write_variant_resolution(id.into(), variant); |
| } |
| if let Some(var) = &var_data { |
| let cmp = if ellipsis.is_some() { usize::gt } else { usize::ne }; |
| |
| if cmp(&subs.len(), &var.fields().len()) { |
| self.push_diagnostic(InferenceDiagnostic::MismatchedTupleStructPatArgCount { |
| pat: id.into(), |
| expected: var.fields().len(), |
| found: subs.len(), |
| }); |
| } |
| } |
| |
| self.unify(&ty, expected); |
| |
| let substs = |
| ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner)); |
| |
| match def { |
| _ if subs.is_empty() => {} |
| Some(def) => { |
| let field_types = self.db.field_types(def); |
| let variant_data = def.variant_data(self.db.upcast()); |
| let visibilities = self.db.field_visibilities(def); |
| |
| let (pre, post) = match ellipsis { |
| Some(idx) => subs.split_at(idx), |
| None => (subs, &[][..]), |
| }; |
| let post_idx_offset = field_types.iter().count().saturating_sub(post.len()); |
| |
| let pre_iter = pre.iter().enumerate(); |
| let post_iter = (post_idx_offset..).zip(post.iter()); |
| |
| for (i, &subpat) in pre_iter.chain(post_iter) { |
| let field_def = { |
| match variant_data.field(&Name::new_tuple_field(i)) { |
| Some(local_id) => { |
| if !visibilities[local_id] |
| .is_visible_from(self.db.upcast(), self.resolver.module()) |
| { |
| // FIXME(DIAGNOSE): private tuple field |
| } |
| Some(local_id) |
| } |
| None => None, |
| } |
| }; |
| |
| let expected_ty = field_def.map_or(self.err_ty(), |f| { |
| field_types[f].clone().substitute(Interner, &substs) |
| }); |
| let expected_ty = self.normalize_associated_types_in(expected_ty); |
| |
| T::infer(self, subpat, &expected_ty, default_bm); |
| } |
| } |
| None => { |
| let err_ty = self.err_ty(); |
| for &inner in subs { |
| T::infer(self, inner, &err_ty, default_bm); |
| } |
| } |
| } |
| |
| ty |
| } |
| |
| /// Infers type for record pattern or its corresponding assignee expression. |
| pub(super) fn infer_record_pat_like<T: PatLike>( |
| &mut self, |
| path: Option<&Path>, |
| expected: &Ty, |
| default_bm: T::BindingMode, |
| id: T, |
| subs: impl Iterator<Item = (Name, T)> + ExactSizeIterator, |
| ) -> Ty { |
| let (ty, def) = self.resolve_variant(path, false); |
| if let Some(variant) = def { |
| self.write_variant_resolution(id.into(), variant); |
| } |
| |
| self.unify(&ty, expected); |
| |
| let substs = |
| ty.as_adt().map(|(_, s)| s.clone()).unwrap_or_else(|| Substitution::empty(Interner)); |
| |
| match def { |
| _ if subs.len() == 0 => {} |
| Some(def) => { |
| let field_types = self.db.field_types(def); |
| let variant_data = def.variant_data(self.db.upcast()); |
| let visibilities = self.db.field_visibilities(def); |
| |
| for (name, inner) in subs { |
| let field_def = { |
| match variant_data.field(&name) { |
| Some(local_id) => { |
| if !visibilities[local_id] |
| .is_visible_from(self.db.upcast(), self.resolver.module()) |
| { |
| self.push_diagnostic(InferenceDiagnostic::NoSuchField { |
| field: inner.into(), |
| private: true, |
| }); |
| } |
| Some(local_id) |
| } |
| None => { |
| self.push_diagnostic(InferenceDiagnostic::NoSuchField { |
| field: inner.into(), |
| private: false, |
| }); |
| None |
| } |
| } |
| }; |
| |
| let expected_ty = field_def.map_or(self.err_ty(), |f| { |
| field_types[f].clone().substitute(Interner, &substs) |
| }); |
| let expected_ty = self.normalize_associated_types_in(expected_ty); |
| |
| T::infer(self, inner, &expected_ty, default_bm); |
| } |
| } |
| None => { |
| let err_ty = self.err_ty(); |
| for (_, inner) in subs { |
| T::infer(self, inner, &err_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<T: PatLike>( |
| &mut self, |
| expected: &Ty, |
| default_bm: T::BindingMode, |
| ellipsis: Option<usize>, |
| subs: &[T], |
| ) -> Ty { |
| let expected = self.resolve_ty_shallow(expected); |
| 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() |
| .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_top_pat(&mut self, pat: PatId, expected: &Ty) { |
| self.infer_pat(pat, expected, BindingMode::default()); |
| } |
| |
| fn infer_pat(&mut self, pat: PatId, expected: &Ty, mut default_bm: BindingMode) -> Ty { |
| let mut expected = self.resolve_ty_shallow(expected); |
| |
| if self.is_non_ref_pat(self.body, pat) { |
| let mut pat_adjustments = Vec::new(); |
| while let Some((inner, _lifetime, mutability)) = expected.as_reference() { |
| pat_adjustments.push(expected.clone()); |
| expected = self.resolve_ty_shallow(inner); |
| default_bm = match default_bm { |
| BindingMode::Move => BindingMode::Ref(mutability), |
| BindingMode::Ref(Mutability::Not) => BindingMode::Ref(Mutability::Not), |
| BindingMode::Ref(Mutability::Mut) => BindingMode::Ref(mutability), |
| } |
| } |
| |
| if !pat_adjustments.is_empty() { |
| pat_adjustments.shrink_to_fit(); |
| self.result.pat_adjustments.insert(pat, pat_adjustments); |
| } |
| } else if let Pat::Ref { .. } = &self.body[pat] { |
| cov_mark::hit!(match_ergonomics_ref); |
| // When you encounter a `&pat` pattern, reset to Move. |
| // This is so that `w` is by value: `let (_, &w) = &(1, &2);` |
| default_bm = BindingMode::Move; |
| } |
| |
| // Lose mutability. |
| let default_bm = default_bm; |
| let expected = expected; |
| |
| let ty = match &self.body[pat] { |
| Pat::Tuple { args, ellipsis } => { |
| self.infer_tuple_pat_like(&expected, default_bm, *ellipsis, args) |
| } |
| Pat::Or(pats) => { |
| for pat in pats.iter() { |
| self.infer_pat(*pat, &expected, default_bm); |
| } |
| expected.clone() |
| } |
| &Pat::Ref { pat, mutability } => self.infer_ref_pat( |
| pat, |
| lower_to_chalk_mutability(mutability), |
| &expected, |
| default_bm, |
| ), |
| 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: _ } => { |
| let subs = fields.iter().map(|f| (f.name.clone(), f.pat)); |
| self.infer_record_pat_like(p.as_deref(), &expected, default_bm, pat, subs) |
| } |
| Pat::Path(path) => { |
| // FIXME update resolver for the surrounding expression |
| self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty()) |
| } |
| Pat::Bind { id, subpat } => { |
| return self.infer_bind_pat(pat, *id, default_bm, *subpat, &expected); |
| } |
| Pat::Slice { prefix, slice, suffix } => { |
| self.infer_slice_pat(&expected, prefix, slice, suffix, default_bm) |
| } |
| Pat::Wild => expected.clone(), |
| Pat::Range { .. } => { |
| // FIXME: do some checks here. |
| expected.clone() |
| } |
| &Pat::Lit(expr) => { |
| // Don't emit type mismatches again, the expression lowering already did that. |
| let ty = self.infer_lit_pat(expr, &expected); |
| self.write_pat_ty(pat, ty); |
| return self.pat_ty_after_adjustment(pat); |
| } |
| Pat::Box { inner } => match self.resolve_boxed_box() { |
| Some(box_adt) => { |
| let (inner_ty, alloc_ty) = match expected.as_adt() { |
| Some((adt, subst)) if adt == box_adt => ( |
| subst.at(Interner, 0).assert_ty_ref(Interner).clone(), |
| subst.as_slice(Interner).get(1).and_then(|a| a.ty(Interner).cloned()), |
| ), |
| _ => (self.result.standard_types.unknown.clone(), None), |
| }; |
| |
| let inner_ty = self.infer_pat(*inner, &inner_ty, default_bm); |
| let mut b = TyBuilder::adt(self.db, box_adt).push(inner_ty); |
| |
| if let Some(alloc_ty) = alloc_ty { |
| b = b.push(alloc_ty); |
| } |
| b.fill_with_defaults(self.db, || self.table.new_type_var()).build() |
| } |
| None => self.err_ty(), |
| }, |
| Pat::ConstBlock(expr) => { |
| self.infer_expr(*expr, &Expectation::has_type(expected.clone())) |
| } |
| Pat::Missing => self.err_ty(), |
| }; |
| // use a new type variable if we got error type here |
| let ty = self.insert_type_vars_shallow(ty); |
| // FIXME: This never check is odd, but required with out we do inference right now |
| if !expected.is_never() && !self.unify(&ty, &expected) { |
| self.result |
| .type_mismatches |
| .insert(pat.into(), TypeMismatch { expected, actual: ty.clone() }); |
| } |
| self.write_pat_ty(pat, ty); |
| self.pat_ty_after_adjustment(pat) |
| } |
| |
| fn pat_ty_after_adjustment(&self, pat: PatId) -> Ty { |
| self.result |
| .pat_adjustments |
| .get(&pat) |
| .and_then(|it| it.first()) |
| .unwrap_or(&self.result.type_of_pat[pat]) |
| .clone() |
| } |
| |
| fn infer_ref_pat( |
| &mut self, |
| inner_pat: PatId, |
| mutability: Mutability, |
| expected: &Ty, |
| default_bm: BindingMode, |
| ) -> Ty { |
| let expectation = match expected.as_reference() { |
| Some((inner_ty, _lifetime, _exp_mut)) => inner_ty.clone(), |
| None => { |
| let inner_ty = self.table.new_type_var(); |
| let ref_ty = |
| TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()).intern(Interner); |
| // Unification failure will be reported by the caller. |
| self.unify(&ref_ty, expected); |
| inner_ty |
| } |
| }; |
| let subty = self.infer_pat(inner_pat, &expectation, default_bm); |
| TyKind::Ref(mutability, static_lifetime(), subty).intern(Interner) |
| } |
| |
| fn infer_bind_pat( |
| &mut self, |
| pat: PatId, |
| binding: BindingId, |
| default_bm: BindingMode, |
| subpat: Option<PatId>, |
| expected: &Ty, |
| ) -> Ty { |
| let Binding { mode, .. } = self.body.bindings[binding]; |
| let mode = if mode == BindingAnnotation::Unannotated { |
| default_bm |
| } else { |
| BindingMode::convert(mode) |
| }; |
| self.result.binding_modes.insert(pat, mode); |
| |
| let inner_ty = match subpat { |
| Some(subpat) => self.infer_pat(subpat, expected, default_bm), |
| None => expected.clone(), |
| }; |
| let inner_ty = self.insert_type_vars_shallow(inner_ty); |
| |
| let bound_ty = match mode { |
| BindingMode::Ref(mutability) => { |
| TyKind::Ref(mutability, static_lifetime(), inner_ty.clone()).intern(Interner) |
| } |
| BindingMode::Move => inner_ty.clone(), |
| }; |
| self.write_pat_ty(pat, inner_ty.clone()); |
| self.write_binding_ty(binding, bound_ty); |
| inner_ty |
| } |
| |
| fn infer_slice_pat( |
| &mut self, |
| expected: &Ty, |
| prefix: &[PatId], |
| slice: &Option<PatId>, |
| suffix: &[PatId], |
| default_bm: BindingMode, |
| ) -> Ty { |
| let elem_ty = match expected.kind(Interner) { |
| TyKind::Array(st, _) | TyKind::Slice(st) => st.clone(), |
| _ => self.err_ty(), |
| }; |
| |
| for &pat_id in prefix.iter().chain(suffix.iter()) { |
| self.infer_pat(pat_id, &elem_ty, default_bm); |
| } |
| |
| if let &Some(slice_pat_id) = slice { |
| let rest_pat_ty = match expected.kind(Interner) { |
| TyKind::Array(_, length) => { |
| let len = try_const_usize(self.db, length); |
| let len = |
| len.and_then(|len| len.checked_sub((prefix.len() + suffix.len()) as u128)); |
| TyKind::Array(elem_ty.clone(), usize_const(self.db, len, self.resolver.krate())) |
| } |
| _ => TyKind::Slice(elem_ty.clone()), |
| } |
| .intern(Interner); |
| self.infer_pat(slice_pat_id, &rest_pat_ty, default_bm); |
| } |
| |
| match expected.kind(Interner) { |
| TyKind::Array(_, const_) => TyKind::Array(elem_ty, const_.clone()), |
| _ => TyKind::Slice(elem_ty), |
| } |
| .intern(Interner) |
| } |
| |
| fn infer_lit_pat(&mut self, expr: ExprId, expected: &Ty) -> Ty { |
| // Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`. |
| if let Expr::Literal(Literal::ByteString(_)) = self.body[expr] { |
| if let Some((inner, ..)) = expected.as_reference() { |
| let inner = self.resolve_ty_shallow(inner); |
| if matches!(inner.kind(Interner), TyKind::Slice(_)) { |
| let elem_ty = TyKind::Scalar(Scalar::Uint(UintTy::U8)).intern(Interner); |
| let slice_ty = TyKind::Slice(elem_ty).intern(Interner); |
| let ty = |
| TyKind::Ref(Mutability::Not, static_lifetime(), slice_ty).intern(Interner); |
| self.write_expr_ty(expr, ty.clone()); |
| return ty; |
| } |
| } |
| } |
| |
| self.infer_expr(expr, &Expectation::has_type(expected.clone())) |
| } |
| |
| fn is_non_ref_pat(&mut self, body: &hir_def::body::Body, pat: PatId) -> bool { |
| match &body[pat] { |
| Pat::Tuple { .. } |
| | Pat::TupleStruct { .. } |
| | Pat::Record { .. } |
| | Pat::Range { .. } |
| | Pat::Slice { .. } => true, |
| Pat::Or(pats) => pats.iter().all(|p| self.is_non_ref_pat(body, *p)), |
| Pat::Path(p) => { |
| let v = self.resolve_value_path_inner(p, pat.into()); |
| v.is_some_and(|x| !matches!(x.0, hir_def::resolver::ValueNs::ConstId(_))) |
| } |
| Pat::ConstBlock(..) => false, |
| Pat::Lit(expr) => !matches!( |
| body[*expr], |
| Expr::Literal(Literal::String(..) | Literal::CString(..) | Literal::ByteString(..)) |
| ), |
| Pat::Wild | Pat::Bind { .. } | Pat::Ref { .. } | Pat::Box { .. } | Pat::Missing => { |
| false |
| } |
| } |
| } |
| } |
| |
| pub(super) fn contains_explicit_ref_binding(body: &Body, pat_id: PatId) -> bool { |
| let mut res = false; |
| body.walk_pats(pat_id, &mut |pat| { |
| res |= matches!(body[pat], Pat::Bind { id, .. } if body.bindings[id].mode == BindingAnnotation::Ref); |
| }); |
| res |
| } |