blob: 452ae316620f21d07fc4e0b55ee30f5d1e949af4 [file] [log] [blame]
//! Type inference for patterns.
use std::iter::repeat_with;
use hir_def::{
HasModule,
expr_store::{Body, path::Path},
hir::{Binding, BindingAnnotation, BindingId, Expr, ExprId, Literal, Pat, PatId},
};
use hir_expand::name::Name;
use rustc_ast_ir::Mutability;
use rustc_type_ir::inherent::{GenericArg as _, GenericArgs as _, IntoKind, SliceLike, Ty as _};
use stdx::TupleExt;
use crate::{
DeclContext, DeclOrigin, InferenceDiagnostic,
consteval::{self, try_const_usize, usize_const},
infer::{
AllowTwoPhase, BindingMode, Expectation, InferenceContext, TypeMismatch,
coerce::CoerceNever, expr::ExprIsRead,
},
lower_nextsolver::lower_mutability,
next_solver::{GenericArgs, Ty, TyKind},
};
impl<'db> InferenceContext<'_, 'db> {
/// 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>,
expected: Ty<'db>,
default_bm: BindingMode,
id: PatId,
ellipsis: Option<u32>,
subs: &[PatId],
decl: Option<DeclContext>,
) -> Ty<'db> {
let (ty, def) = self.resolve_variant(id.into(), path, true);
let var_data = def.map(|it| it.fields(self.db));
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);
match def {
_ if subs.is_empty() => {}
Some(def) => {
let field_types = self.db.field_types_ns(def);
let variant_data = def.fields(self.db);
let visibilities = self.db.field_visibilities(def);
let (pre, post) = match ellipsis {
Some(idx) => subs.split_at(idx as usize),
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());
let substs = ty.as_adt().map(TupleExt::tail);
for (i, &subpat) in pre_iter.chain(post_iter) {
let expected_ty = {
match variant_data.field(&Name::new_tuple_field(i)) {
Some(local_id) => {
if !visibilities[local_id]
.is_visible_from(self.db, self.resolver.module())
{
// FIXME(DIAGNOSE): private tuple field
}
let f = field_types[local_id];
let expected_ty = match substs {
Some(substs) => f.instantiate(self.interner(), substs),
None => f.instantiate(self.interner(), &[]),
};
self.process_remote_user_written_ty(expected_ty)
}
None => self.err_ty(),
}
};
self.infer_pat(subpat, expected_ty, default_bm, decl);
}
}
None => {
let err_ty = self.err_ty();
for &inner in subs {
self.infer_pat(inner, err_ty, default_bm, decl);
}
}
}
ty
}
/// Infers type for record pattern or its corresponding assignee expression.
pub(super) fn infer_record_pat_like(
&mut self,
path: Option<&Path>,
expected: Ty<'db>,
default_bm: BindingMode,
id: PatId,
subs: impl ExactSizeIterator<Item = (Name, PatId)>,
decl: Option<DeclContext>,
) -> Ty<'db> {
let (ty, def) = self.resolve_variant(id.into(), path, false);
if let Some(variant) = def {
self.write_variant_resolution(id.into(), variant);
}
self.unify(ty, expected);
match def {
_ if subs.len() == 0 => {}
Some(def) => {
let field_types = self.db.field_types_ns(def);
let variant_data = def.fields(self.db);
let visibilities = self.db.field_visibilities(def);
let substs = ty.as_adt().map(TupleExt::tail);
for (name, inner) in subs {
let expected_ty = {
match variant_data.field(&name) {
Some(local_id) => {
if !visibilities[local_id]
.is_visible_from(self.db, self.resolver.module())
{
self.push_diagnostic(InferenceDiagnostic::NoSuchField {
field: inner.into(),
private: Some(local_id),
variant: def,
});
}
let f = field_types[local_id];
let expected_ty = match substs {
Some(substs) => f.instantiate(self.interner(), substs),
None => f.instantiate(self.interner(), &[]),
};
self.process_remote_user_written_ty(expected_ty)
}
None => {
self.push_diagnostic(InferenceDiagnostic::NoSuchField {
field: inner.into(),
private: None,
variant: def,
});
self.err_ty()
}
}
};
self.infer_pat(inner, expected_ty, default_bm, decl);
}
}
None => {
let err_ty = self.err_ty();
for (_, inner) in subs {
self.infer_pat(inner, err_ty, default_bm, decl);
}
}
}
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<'db>,
default_bm: BindingMode,
ellipsis: Option<u32>,
subs: &[PatId],
decl: Option<DeclContext>,
) -> Ty<'db> {
let expected = self.table.structurally_resolve_type(expected);
let expectations = match expected.kind() {
TyKind::Tuple(parameters) => parameters,
_ => self.types.empty_tys,
};
let ((pre, post), n_uncovered_patterns) = match ellipsis {
Some(idx) => {
(subs.split_at(idx as usize), expectations.len().saturating_sub(subs.len()))
}
None => ((subs, &[][..]), 0),
};
let mut expectations_iter =
expectations.iter().chain(repeat_with(|| self.table.next_ty_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 = self.infer_pat(*pat, *ty, default_bm, decl);
}
// 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, decl);
}
Ty::new_tup_from_iter(self.interner(), inner_tys.into_iter())
}
/// The resolver needs to be updated to the surrounding expression when inside assignment
/// (because there, `Pat::Path` can refer to a variable).
pub(super) fn infer_top_pat(
&mut self,
pat: PatId,
expected: Ty<'db>,
decl: Option<DeclContext>,
) {
self.infer_pat(pat, expected, BindingMode::default(), decl);
}
fn infer_pat(
&mut self,
pat: PatId,
expected: Ty<'db>,
mut default_bm: BindingMode,
decl: Option<DeclContext>,
) -> Ty<'db> {
let mut expected = self.table.structurally_resolve_type(expected);
if matches!(&self.body[pat], Pat::Ref { .. }) || self.inside_assignment {
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);`
// Destructuring assignments also reset the binding mode and
// don't do match ergonomics.
default_bm = BindingMode::Move;
} else if self.is_non_ref_pat(self.body, pat) {
let mut pat_adjustments = Vec::new();
while let TyKind::Ref(_lifetime, inner, mutability) = expected.kind() {
pat_adjustments.push(expected);
expected = self.table.try_structurally_resolve_type(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);
}
}
// 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, decl)
}
Pat::Or(pats) => {
for pat in pats.iter() {
self.infer_pat(*pat, expected, default_bm, decl);
}
expected
}
&Pat::Ref { pat, mutability } => {
self.infer_ref_pat(pat, lower_mutability(mutability), expected, default_bm, decl)
}
Pat::TupleStruct { path: p, args: subpats, ellipsis } => self
.infer_tuple_struct_pat_like(
p.as_deref(),
expected,
default_bm,
pat,
*ellipsis,
subpats,
decl,
),
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, decl)
}
Pat::Path(path) => {
let ty = self.infer_path(path, pat.into()).unwrap_or_else(|| self.err_ty());
let ty_inserted_vars = self.insert_type_vars_shallow(ty);
match self.coerce(
pat.into(),
expected,
ty_inserted_vars,
AllowTwoPhase::No,
CoerceNever::Yes,
) {
Ok(coerced_ty) => {
self.write_pat_ty(pat, coerced_ty);
return self.pat_ty_after_adjustment(pat);
}
Err(_) => {
self.result.type_mismatches.insert(
pat.into(),
TypeMismatch { expected, actual: ty_inserted_vars },
);
self.write_pat_ty(pat, ty);
// We return `expected` to prevent cascading errors. I guess an alternative is to
// not emit type mismatches for error types and emit an error type here.
return expected;
}
}
}
Pat::Bind { id, subpat } => {
return self.infer_bind_pat(pat, *id, default_bm, *subpat, expected, decl);
}
Pat::Slice { prefix, slice, suffix } => {
self.infer_slice_pat(expected, prefix, *slice, suffix, default_bm, decl)
}
Pat::Wild => expected,
Pat::Range { .. } => {
// FIXME: do some checks here.
expected
}
&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.type_at(0), subst.as_slice().get(1).and_then(|a| a.as_type()))
}
_ => (self.types.error, None),
};
let inner_ty = self.infer_pat(*inner, inner_ty, default_bm, decl);
Ty::new_adt(
self.interner(),
box_adt,
GenericArgs::fill_with_defaults(
self.interner(),
box_adt.into(),
std::iter::once(inner_ty.into()).chain(alloc_ty.map(Into::into)),
|_, _, id, _| self.table.next_var_for_param(id),
),
)
}
None => self.err_ty(),
},
Pat::ConstBlock(expr) => {
let old_inside_assign = std::mem::replace(&mut self.inside_assignment, false);
let result =
self.infer_expr(*expr, &Expectation::has_type(expected), ExprIsRead::Yes);
self.inside_assignment = old_inside_assign;
result
}
Pat::Expr(expr) => {
let old_inside_assign = std::mem::replace(&mut self.inside_assignment, false);
// LHS of assignment doesn't constitute reads.
let result =
self.infer_expr_coerce(*expr, &Expectation::has_type(expected), ExprIsRead::No);
// We are returning early to avoid the unifiability check below.
let lhs_ty = self.insert_type_vars_shallow(result);
let ty = match self.coerce(
pat.into(),
expected,
lhs_ty,
AllowTwoPhase::No,
CoerceNever::Yes,
) {
Ok(ty) => ty,
Err(_) => {
self.result
.type_mismatches
.insert(pat.into(), TypeMismatch { expected, actual: lhs_ty });
// `rhs_ty` is returned so no further type mismatches are
// reported because of this mismatch.
expected
}
};
self.write_pat_ty(pat, ty);
self.inside_assignment = old_inside_assign;
return ty;
}
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 });
}
self.write_pat_ty(pat, ty);
self.pat_ty_after_adjustment(pat)
}
fn pat_ty_after_adjustment(&self, pat: PatId) -> Ty<'db> {
*self
.result
.pat_adjustments
.get(&pat)
.and_then(|it| it.first())
.unwrap_or(&self.result.type_of_pat[pat])
}
fn infer_ref_pat(
&mut self,
inner_pat: PatId,
mutability: Mutability,
expected: Ty<'db>,
default_bm: BindingMode,
decl: Option<DeclContext>,
) -> Ty<'db> {
let (expectation_type, expectation_lt) = match expected.kind() {
TyKind::Ref(lifetime, inner_ty, _exp_mut) => (inner_ty, lifetime),
_ => {
let inner_ty = self.table.next_ty_var();
let inner_lt = self.table.next_region_var();
let ref_ty = Ty::new_ref(self.interner(), inner_lt, inner_ty, mutability);
// Unification failure will be reported by the caller.
self.unify(ref_ty, expected);
(inner_ty, inner_lt)
}
};
let subty = self.infer_pat(inner_pat, expectation_type, default_bm, decl);
Ty::new_ref(self.interner(), expectation_lt, subty, mutability)
}
fn infer_bind_pat(
&mut self,
pat: PatId,
binding: BindingId,
default_bm: BindingMode,
subpat: Option<PatId>,
expected: Ty<'db>,
decl: Option<DeclContext>,
) -> Ty<'db> {
let Binding { mode, .. } = self.body[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, decl),
None => expected,
};
let inner_ty = self.insert_type_vars_shallow(inner_ty);
let bound_ty = match mode {
BindingMode::Ref(mutability) => {
let inner_lt = self.table.next_region_var();
Ty::new_ref(self.interner(), inner_lt, inner_ty, mutability)
}
BindingMode::Move => inner_ty,
};
self.write_pat_ty(pat, inner_ty);
self.write_binding_ty(binding, bound_ty);
inner_ty
}
fn infer_slice_pat(
&mut self,
expected: Ty<'db>,
prefix: &[PatId],
slice: Option<PatId>,
suffix: &[PatId],
default_bm: BindingMode,
decl: Option<DeclContext>,
) -> Ty<'db> {
let expected = self.table.structurally_resolve_type(expected);
// If `expected` is an infer ty, we try to equate it to an array if the given pattern
// allows it. See issue #16609
if self.pat_is_irrefutable(decl)
&& expected.is_ty_var()
&& let Some(resolved_array_ty) =
self.try_resolve_slice_ty_to_array_ty(prefix, suffix, slice)
{
self.unify(expected, resolved_array_ty);
}
let expected = self.table.try_structurally_resolve_type(expected);
let elem_ty = match expected.kind() {
TyKind::Array(st, _) | TyKind::Slice(st) => st,
_ => self.err_ty(),
};
for &pat_id in prefix.iter().chain(suffix.iter()) {
self.infer_pat(pat_id, elem_ty, default_bm, decl);
}
if let Some(slice_pat_id) = slice {
let rest_pat_ty = match expected.kind() {
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));
Ty::new_array_with_const_len(
self.interner(),
elem_ty,
usize_const(self.db, len, self.resolver.krate()),
)
}
_ => Ty::new_slice(self.interner(), elem_ty),
};
self.infer_pat(slice_pat_id, rest_pat_ty, default_bm, decl);
}
match expected.kind() {
TyKind::Array(_, const_) => {
Ty::new_array_with_const_len(self.interner(), elem_ty, const_)
}
_ => Ty::new_slice(self.interner(), elem_ty),
}
}
fn infer_lit_pat(&mut self, expr: ExprId, expected: Ty<'db>) -> Ty<'db> {
// Like slice patterns, byte string patterns can denote both `&[u8; N]` and `&[u8]`.
if let Expr::Literal(Literal::ByteString(_)) = self.body[expr]
&& let TyKind::Ref(_, inner, _) = expected.kind()
{
let inner = self.table.try_structurally_resolve_type(inner);
if matches!(inner.kind(), TyKind::Slice(_)) {
let elem_ty = self.types.u8;
let slice_ty = Ty::new_slice(self.interner(), elem_ty);
let ty =
Ty::new_ref(self.interner(), self.types.re_static, slice_ty, Mutability::Not);
self.write_expr_ty(expr, ty);
return ty;
}
}
self.infer_expr(expr, &Expectation::has_type(expected), ExprIsRead::Yes)
}
fn is_non_ref_pat(&mut self, body: &hir_def::expr_store::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(path) => {
// A const is a reference pattern, but other value ns things aren't (see #16131).
let resolved = self.resolve_value_path_inner(path, pat.into(), true);
resolved.is_some_and(|it| !matches!(it.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
| Pat::Expr(_) => false,
}
}
fn try_resolve_slice_ty_to_array_ty(
&mut self,
before: &[PatId],
suffix: &[PatId],
slice: Option<PatId>,
) -> Option<Ty<'db>> {
if slice.is_some() {
return None;
}
let len = before.len() + suffix.len();
let size = consteval::usize_const(self.db, Some(len as u128), self.owner.krate(self.db));
let elem_ty = self.table.next_ty_var();
let array_ty = Ty::new_array_with_const_len(self.interner(), elem_ty, size);
Some(array_ty)
}
/// Used to determine whether we can infer the expected type in the slice pattern to be of type array.
/// This is only possible if we're in an irrefutable pattern. If we were to allow this in refutable
/// patterns we wouldn't e.g. report ambiguity in the following situation:
///
/// ```ignore(rust)
/// struct Zeroes;
/// const ARR: [usize; 2] = [0; 2];
/// const ARR2: [usize; 2] = [2; 2];
///
/// impl Into<&'static [usize; 2]> for Zeroes {
/// fn into(self) -> &'static [usize; 2] {
/// &ARR
/// }
/// }
///
/// impl Into<&'static [usize]> for Zeroes {
/// fn into(self) -> &'static [usize] {
/// &ARR2
/// }
/// }
///
/// fn main() {
/// let &[a, b]: &[usize] = Zeroes.into() else {
/// ..
/// };
/// }
/// ```
///
/// If we're in an irrefutable pattern we prefer the array impl candidate given that
/// the slice impl candidate would be rejected anyway (if no ambiguity existed).
fn pat_is_irrefutable(&self, decl_ctxt: Option<DeclContext>) -> bool {
matches!(decl_ctxt, Some(DeclContext { origin: DeclOrigin::LocalDecl { has_else: 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[id].mode == BindingAnnotation::Ref);
});
res
}