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:
bors 2022-07-24 13:57:14 +00:00
commit 537cc1eec5
5 changed files with 86 additions and 22 deletions

View File

@ -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);

View File

@ -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)

View File

@ -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,
}; };

View File

@ -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(),

View File

@ -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(