Skip to content

Commit

Permalink
Implement type inference for assignee expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
lowr committed May 31, 2022
1 parent 62d6b5a commit b7a4175
Show file tree
Hide file tree
Showing 4 changed files with 416 additions and 3 deletions.
8 changes: 8 additions & 0 deletions crates/hir-ty/src/infer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,14 @@ trait PatLike: Into<ExprOrPatId> + 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;

Expand Down
93 changes: 91 additions & 2 deletions crates/hir-ty/src/infer/expr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(_)) => {
Expand Down Expand Up @@ -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,
Expand Down
18 changes: 18 additions & 0 deletions crates/hir-ty/src/tests/coercion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
Loading

0 comments on commit b7a4175

Please sign in to comment.