Auto merge of #12863 - lowr:fix/missing-fields-on-destructuring-assignment, r=Veykril
Fix missing fields check on destructuring assignment Fixes #12838 When checking if the record literal in question is an assignee expression or not, the new fn `is_assignee_record_literal` iterates over its ancestors until it is sure. This isn't super efficient, as we don't cache anything and does the iteration for every record literal during missing fields check. Alternatively, we may want to have a field like `assignee` on `hir_def::Expr::{RecordLit, Array, Tuple, Call}` to tell if it's an assignee expression, which would be O(1) when checking later but have some memory overhead for the field.
This commit is contained in:
commit
537cc1eec5
@ -96,6 +96,7 @@ pub(super) fn lower(
|
|||||||
expander,
|
expander,
|
||||||
name_to_pat_grouping: Default::default(),
|
name_to_pat_grouping: Default::default(),
|
||||||
is_lowering_inside_or_pat: false,
|
is_lowering_inside_or_pat: false,
|
||||||
|
is_lowering_assignee_expr: false,
|
||||||
}
|
}
|
||||||
.collect(params, body)
|
.collect(params, body)
|
||||||
}
|
}
|
||||||
@ -109,6 +110,7 @@ struct ExprCollector<'a> {
|
|||||||
// a poor-mans union-find?
|
// a poor-mans union-find?
|
||||||
name_to_pat_grouping: FxHashMap<Name, Vec<PatId>>,
|
name_to_pat_grouping: FxHashMap<Name, Vec<PatId>>,
|
||||||
is_lowering_inside_or_pat: bool,
|
is_lowering_inside_or_pat: bool,
|
||||||
|
is_lowering_assignee_expr: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ExprCollector<'_> {
|
impl ExprCollector<'_> {
|
||||||
@ -283,7 +285,10 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
|
|||||||
} else {
|
} else {
|
||||||
Box::default()
|
Box::default()
|
||||||
};
|
};
|
||||||
self.alloc_expr(Expr::Call { callee, args }, syntax_ptr)
|
self.alloc_expr(
|
||||||
|
Expr::Call { callee, args, is_assignee_expr: self.is_lowering_assignee_expr },
|
||||||
|
syntax_ptr,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ast::Expr::MethodCallExpr(e) => {
|
ast::Expr::MethodCallExpr(e) => {
|
||||||
let receiver = self.collect_expr_opt(e.receiver());
|
let receiver = self.collect_expr_opt(e.receiver());
|
||||||
@ -359,6 +364,7 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
|
|||||||
ast::Expr::RecordExpr(e) => {
|
ast::Expr::RecordExpr(e) => {
|
||||||
let path =
|
let path =
|
||||||
e.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
|
e.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
|
||||||
|
let is_assignee_expr = self.is_lowering_assignee_expr;
|
||||||
let record_lit = if let Some(nfl) = e.record_expr_field_list() {
|
let record_lit = if let Some(nfl) = e.record_expr_field_list() {
|
||||||
let fields = nfl
|
let fields = nfl
|
||||||
.fields()
|
.fields()
|
||||||
@ -378,9 +384,16 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
|
|||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
let spread = nfl.spread().map(|s| self.collect_expr(s));
|
let spread = nfl.spread().map(|s| self.collect_expr(s));
|
||||||
Expr::RecordLit { path, fields, spread }
|
let ellipsis = nfl.dotdot_token().is_some();
|
||||||
|
Expr::RecordLit { path, fields, spread, ellipsis, is_assignee_expr }
|
||||||
} else {
|
} else {
|
||||||
Expr::RecordLit { path, fields: Box::default(), spread: None }
|
Expr::RecordLit {
|
||||||
|
path,
|
||||||
|
fields: Box::default(),
|
||||||
|
spread: None,
|
||||||
|
ellipsis: false,
|
||||||
|
is_assignee_expr,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
self.alloc_expr(record_lit, syntax_ptr)
|
self.alloc_expr(record_lit, syntax_ptr)
|
||||||
@ -458,14 +471,21 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
ast::Expr::BinExpr(e) => {
|
ast::Expr::BinExpr(e) => {
|
||||||
let lhs = self.collect_expr_opt(e.lhs());
|
|
||||||
let rhs = self.collect_expr_opt(e.rhs());
|
|
||||||
let op = e.op_kind();
|
let op = e.op_kind();
|
||||||
|
if let Some(ast::BinaryOp::Assignment { op: None }) = op {
|
||||||
|
self.is_lowering_assignee_expr = true;
|
||||||
|
}
|
||||||
|
let lhs = self.collect_expr_opt(e.lhs());
|
||||||
|
self.is_lowering_assignee_expr = false;
|
||||||
|
let rhs = self.collect_expr_opt(e.rhs());
|
||||||
self.alloc_expr(Expr::BinaryOp { lhs, rhs, op }, syntax_ptr)
|
self.alloc_expr(Expr::BinaryOp { lhs, rhs, op }, syntax_ptr)
|
||||||
}
|
}
|
||||||
ast::Expr::TupleExpr(e) => {
|
ast::Expr::TupleExpr(e) => {
|
||||||
let exprs = e.fields().map(|expr| self.collect_expr(expr)).collect();
|
let exprs = e.fields().map(|expr| self.collect_expr(expr)).collect();
|
||||||
self.alloc_expr(Expr::Tuple { exprs }, syntax_ptr)
|
self.alloc_expr(
|
||||||
|
Expr::Tuple { exprs, is_assignee_expr: self.is_lowering_assignee_expr },
|
||||||
|
syntax_ptr,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ast::Expr::BoxExpr(e) => {
|
ast::Expr::BoxExpr(e) => {
|
||||||
let expr = self.collect_expr_opt(e.expr());
|
let expr = self.collect_expr_opt(e.expr());
|
||||||
@ -477,8 +497,14 @@ fn maybe_collect_expr(&mut self, expr: ast::Expr) -> Option<ExprId> {
|
|||||||
|
|
||||||
match kind {
|
match kind {
|
||||||
ArrayExprKind::ElementList(e) => {
|
ArrayExprKind::ElementList(e) => {
|
||||||
let exprs = e.map(|expr| self.collect_expr(expr)).collect();
|
let elements = e.map(|expr| self.collect_expr(expr)).collect();
|
||||||
self.alloc_expr(Expr::Array(Array::ElementList(exprs)), syntax_ptr)
|
self.alloc_expr(
|
||||||
|
Expr::Array(Array::ElementList {
|
||||||
|
elements,
|
||||||
|
is_assignee_expr: self.is_lowering_assignee_expr,
|
||||||
|
}),
|
||||||
|
syntax_ptr,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ArrayExprKind::Repeat { initializer, repeat } => {
|
ArrayExprKind::Repeat { initializer, repeat } => {
|
||||||
let initializer = self.collect_expr_opt(initializer);
|
let initializer = self.collect_expr_opt(initializer);
|
||||||
|
@ -110,6 +110,7 @@ pub enum Expr {
|
|||||||
Call {
|
Call {
|
||||||
callee: ExprId,
|
callee: ExprId,
|
||||||
args: Box<[ExprId]>,
|
args: Box<[ExprId]>,
|
||||||
|
is_assignee_expr: bool,
|
||||||
},
|
},
|
||||||
MethodCall {
|
MethodCall {
|
||||||
receiver: ExprId,
|
receiver: ExprId,
|
||||||
@ -138,6 +139,8 @@ pub enum Expr {
|
|||||||
path: Option<Box<Path>>,
|
path: Option<Box<Path>>,
|
||||||
fields: Box<[RecordLitField]>,
|
fields: Box<[RecordLitField]>,
|
||||||
spread: Option<ExprId>,
|
spread: Option<ExprId>,
|
||||||
|
ellipsis: bool,
|
||||||
|
is_assignee_expr: bool,
|
||||||
},
|
},
|
||||||
Field {
|
Field {
|
||||||
expr: ExprId,
|
expr: ExprId,
|
||||||
@ -196,6 +199,7 @@ pub enum Expr {
|
|||||||
},
|
},
|
||||||
Tuple {
|
Tuple {
|
||||||
exprs: Box<[ExprId]>,
|
exprs: Box<[ExprId]>,
|
||||||
|
is_assignee_expr: bool,
|
||||||
},
|
},
|
||||||
Unsafe {
|
Unsafe {
|
||||||
body: ExprId,
|
body: ExprId,
|
||||||
@ -211,7 +215,7 @@ pub enum Expr {
|
|||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub enum Array {
|
pub enum Array {
|
||||||
ElementList(Box<[ExprId]>),
|
ElementList { elements: Box<[ExprId]>, is_assignee_expr: bool },
|
||||||
Repeat { initializer: ExprId, repeat: ExprId },
|
Repeat { initializer: ExprId, repeat: ExprId },
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -285,7 +289,7 @@ pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
|
|||||||
f(*iterable);
|
f(*iterable);
|
||||||
f(*body);
|
f(*body);
|
||||||
}
|
}
|
||||||
Expr::Call { callee, args } => {
|
Expr::Call { callee, args, .. } => {
|
||||||
f(*callee);
|
f(*callee);
|
||||||
args.iter().copied().for_each(f);
|
args.iter().copied().for_each(f);
|
||||||
}
|
}
|
||||||
@ -339,9 +343,9 @@ pub fn walk_child_exprs(&self, mut f: impl FnMut(ExprId)) {
|
|||||||
| Expr::Box { expr } => {
|
| Expr::Box { expr } => {
|
||||||
f(*expr);
|
f(*expr);
|
||||||
}
|
}
|
||||||
Expr::Tuple { exprs } => exprs.iter().copied().for_each(f),
|
Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f),
|
||||||
Expr::Array(a) => match a {
|
Expr::Array(a) => match a {
|
||||||
Array::ElementList(exprs) => exprs.iter().copied().for_each(f),
|
Array::ElementList { elements, .. } => elements.iter().copied().for_each(f),
|
||||||
Array::Repeat { initializer, repeat } => {
|
Array::Repeat { initializer, repeat } => {
|
||||||
f(*initializer);
|
f(*initializer);
|
||||||
f(*repeat)
|
f(*repeat)
|
||||||
|
@ -305,7 +305,10 @@ pub fn record_literal_missing_fields(
|
|||||||
expr: &Expr,
|
expr: &Expr,
|
||||||
) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
|
) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
|
||||||
let (fields, exhaustive) = match expr {
|
let (fields, exhaustive) = match expr {
|
||||||
Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
|
Expr::RecordLit { fields, spread, ellipsis, is_assignee_expr, .. } => {
|
||||||
|
let exhaustive = if *is_assignee_expr { !*ellipsis } else { spread.is_none() };
|
||||||
|
(fields, exhaustive)
|
||||||
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -276,7 +276,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
|
|||||||
|
|
||||||
closure_ty
|
closure_ty
|
||||||
}
|
}
|
||||||
Expr::Call { callee, args } => {
|
Expr::Call { callee, args, .. } => {
|
||||||
let callee_ty = self.infer_expr(*callee, &Expectation::none());
|
let callee_ty = self.infer_expr(*callee, &Expectation::none());
|
||||||
let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone());
|
let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone());
|
||||||
let mut res = None;
|
let mut res = None;
|
||||||
@ -421,7 +421,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
|
|||||||
}
|
}
|
||||||
TyKind::Never.intern(Interner)
|
TyKind::Never.intern(Interner)
|
||||||
}
|
}
|
||||||
Expr::RecordLit { path, fields, spread } => {
|
Expr::RecordLit { path, fields, spread, .. } => {
|
||||||
let (ty, def_id) = self.resolve_variant(path.as_deref(), false);
|
let (ty, def_id) = self.resolve_variant(path.as_deref(), false);
|
||||||
if let Some(variant) = def_id {
|
if let Some(variant) = def_id {
|
||||||
self.write_variant_resolution(tgt_expr.into(), variant);
|
self.write_variant_resolution(tgt_expr.into(), variant);
|
||||||
@ -693,7 +693,7 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
|
|||||||
self.err_ty()
|
self.err_ty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Expr::Tuple { exprs } => {
|
Expr::Tuple { exprs, .. } => {
|
||||||
let mut tys = match expected
|
let mut tys = match expected
|
||||||
.only_has_type(&mut self.table)
|
.only_has_type(&mut self.table)
|
||||||
.as_ref()
|
.as_ref()
|
||||||
@ -724,12 +724,12 @@ fn infer_expr_inner(&mut self, tgt_expr: ExprId, expected: &Expectation) -> Ty {
|
|||||||
|
|
||||||
let expected = Expectation::has_type(elem_ty.clone());
|
let expected = Expectation::has_type(elem_ty.clone());
|
||||||
let len = match array {
|
let len = match array {
|
||||||
Array::ElementList(items) => {
|
Array::ElementList { elements, .. } => {
|
||||||
for &expr in items.iter() {
|
for &expr in elements.iter() {
|
||||||
let cur_elem_ty = self.infer_expr_inner(expr, &expected);
|
let cur_elem_ty = self.infer_expr_inner(expr, &expected);
|
||||||
coerce.coerce(self, Some(expr), &cur_elem_ty);
|
coerce.coerce(self, Some(expr), &cur_elem_ty);
|
||||||
}
|
}
|
||||||
consteval::usize_const(Some(items.len() as u128))
|
consteval::usize_const(Some(elements.len() as u128))
|
||||||
}
|
}
|
||||||
&Array::Repeat { initializer, repeat } => {
|
&Array::Repeat { initializer, repeat } => {
|
||||||
self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty));
|
self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty));
|
||||||
@ -850,7 +850,7 @@ pub(super) fn infer_assignee_expr(&mut self, lhs: ExprId, rhs_ty: &Ty) -> Ty {
|
|||||||
let rhs_ty = self.resolve_ty_shallow(rhs_ty);
|
let rhs_ty = self.resolve_ty_shallow(rhs_ty);
|
||||||
|
|
||||||
let ty = match &self.body[lhs] {
|
let ty = match &self.body[lhs] {
|
||||||
Expr::Tuple { exprs } => {
|
Expr::Tuple { exprs, .. } => {
|
||||||
// We don't consider multiple ellipses. This is analogous to
|
// We don't consider multiple ellipses. This is analogous to
|
||||||
// `hir_def::body::lower::ExprCollector::collect_tuple_pat()`.
|
// `hir_def::body::lower::ExprCollector::collect_tuple_pat()`.
|
||||||
let ellipsis = exprs.iter().position(|e| is_rest_expr(*e));
|
let ellipsis = exprs.iter().position(|e| is_rest_expr(*e));
|
||||||
@ -858,7 +858,7 @@ pub(super) fn infer_assignee_expr(&mut self, lhs: ExprId, rhs_ty: &Ty) -> Ty {
|
|||||||
|
|
||||||
self.infer_tuple_pat_like(&rhs_ty, (), ellipsis, &exprs)
|
self.infer_tuple_pat_like(&rhs_ty, (), ellipsis, &exprs)
|
||||||
}
|
}
|
||||||
Expr::Call { callee, args } => {
|
Expr::Call { callee, args, .. } => {
|
||||||
// Tuple structs
|
// Tuple structs
|
||||||
let path = match &self.body[*callee] {
|
let path = match &self.body[*callee] {
|
||||||
Expr::Path(path) => Some(path),
|
Expr::Path(path) => Some(path),
|
||||||
@ -872,7 +872,7 @@ pub(super) fn infer_assignee_expr(&mut self, lhs: ExprId, rhs_ty: &Ty) -> Ty {
|
|||||||
|
|
||||||
self.infer_tuple_struct_pat_like(path, &rhs_ty, (), lhs, ellipsis, &args)
|
self.infer_tuple_struct_pat_like(path, &rhs_ty, (), lhs, ellipsis, &args)
|
||||||
}
|
}
|
||||||
Expr::Array(Array::ElementList(elements)) => {
|
Expr::Array(Array::ElementList { elements, .. }) => {
|
||||||
let elem_ty = match rhs_ty.kind(Interner) {
|
let elem_ty = match rhs_ty.kind(Interner) {
|
||||||
TyKind::Array(st, _) => st.clone(),
|
TyKind::Array(st, _) => st.clone(),
|
||||||
_ => self.err_ty(),
|
_ => self.err_ty(),
|
||||||
|
@ -292,6 +292,37 @@ fn x(a: S) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn missing_record_expr_in_assignee_expr() {
|
||||||
|
check_diagnostics(
|
||||||
|
r"
|
||||||
|
struct S { s: usize, t: usize }
|
||||||
|
struct S2 { s: S, t: () }
|
||||||
|
struct T(S);
|
||||||
|
fn regular(a: S) {
|
||||||
|
let s;
|
||||||
|
S { s, .. } = a;
|
||||||
|
}
|
||||||
|
fn nested(a: S2) {
|
||||||
|
let s;
|
||||||
|
S2 { s: S { s, .. }, .. } = a;
|
||||||
|
}
|
||||||
|
fn in_tuple(a: (S,)) {
|
||||||
|
let s;
|
||||||
|
(S { s, .. },) = a;
|
||||||
|
}
|
||||||
|
fn in_array(a: [S;1]) {
|
||||||
|
let s;
|
||||||
|
[S { s, .. },] = a;
|
||||||
|
}
|
||||||
|
fn in_tuple_struct(a: T) {
|
||||||
|
let s;
|
||||||
|
T(S { s, .. }) = a;
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn range_mapping_out_of_macros() {
|
fn range_mapping_out_of_macros() {
|
||||||
check_fix(
|
check_fix(
|
||||||
|
Loading…
Reference in New Issue
Block a user