mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-09-27 20:42:04 +00:00
Auto merge of #12428 - lowr:experimental/destructuring-assignment, r=flodiebold
feat: implement destructuring assignment This is an attempt to implement destructuring assignments, or more specifically, type inference for [assignee expressions](https://doc.rust-lang.org/reference/expressions.html#place-expressions-and-value-expressions). I'm not sure if this is the right approach, so I don't even expect this to be merged (hence the branch name 😉) but rather want to propose one direction we could choose. I don't mind getting merged if this is good enough though! Some notes on the implementation choices: - Assignee expressions are **not** desugared on HIR level unlike rustc, but are inferred directly along with other expressions. This matches the processing of other syntaxes that are desugared in rustc but not in r-a. I find this reasonable because r-a only needs to infer types and it's easier to relate AST nodes and HIR nodes, so I followed it. - Assignee expressions obviously resemble patterns, so type inference for each kind of pattern and its corresponding assignee expressions share a significant amount of logic. I tried to reuse the type inference functions for patterns by introducing `PatLike` trait which generalizes assignee expressions and patterns. - This is not the most elegant solution I suspect (and I really don't like the name of the trait!), but it's cleaner and the change is smaller than other ways I experimented, like making the functions generic without such trait, or making them take `Either<ExprId, PatId>` in place of `PatId`. in case this is merged: Closes #11532 Closes #11839 Closes #12322
This commit is contained in:
commit
2ff505ab48
7 changed files with 532 additions and 68 deletions
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue