Skip to content

Commit 537cc1e

Browse files
committed
Auto merge of rust-lang#12863 - lowr:fix/missing-fields-on-destructuring-assignment, r=Veykril
Fix missing fields check on destructuring assignment Fixes rust-lang#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.
2 parents 894d6a2 + 805ac66 commit 537cc1e

File tree

5 files changed

+85
-21
lines changed

5 files changed

+85
-21
lines changed

crates/hir-def/src/body/lower.rs

+33-7
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ pub(super) fn lower(
9696
expander,
9797
name_to_pat_grouping: Default::default(),
9898
is_lowering_inside_or_pat: false,
99+
is_lowering_assignee_expr: false,
99100
}
100101
.collect(params, body)
101102
}
@@ -109,6 +110,7 @@ struct ExprCollector<'a> {
109110
// a poor-mans union-find?
110111
name_to_pat_grouping: FxHashMap<Name, Vec<PatId>>,
111112
is_lowering_inside_or_pat: bool,
113+
is_lowering_assignee_expr: bool,
112114
}
113115

114116
impl ExprCollector<'_> {
@@ -283,7 +285,10 @@ impl ExprCollector<'_> {
283285
} else {
284286
Box::default()
285287
};
286-
self.alloc_expr(Expr::Call { callee, args }, syntax_ptr)
288+
self.alloc_expr(
289+
Expr::Call { callee, args, is_assignee_expr: self.is_lowering_assignee_expr },
290+
syntax_ptr,
291+
)
287292
}
288293
ast::Expr::MethodCallExpr(e) => {
289294
let receiver = self.collect_expr_opt(e.receiver());
@@ -359,6 +364,7 @@ impl ExprCollector<'_> {
359364
ast::Expr::RecordExpr(e) => {
360365
let path =
361366
e.path().and_then(|path| self.expander.parse_path(self.db, path)).map(Box::new);
367+
let is_assignee_expr = self.is_lowering_assignee_expr;
362368
let record_lit = if let Some(nfl) = e.record_expr_field_list() {
363369
let fields = nfl
364370
.fields()
@@ -378,9 +384,16 @@ impl ExprCollector<'_> {
378384
})
379385
.collect();
380386
let spread = nfl.spread().map(|s| self.collect_expr(s));
381-
Expr::RecordLit { path, fields, spread }
387+
let ellipsis = nfl.dotdot_token().is_some();
388+
Expr::RecordLit { path, fields, spread, ellipsis, is_assignee_expr }
382389
} else {
383-
Expr::RecordLit { path, fields: Box::default(), spread: None }
390+
Expr::RecordLit {
391+
path,
392+
fields: Box::default(),
393+
spread: None,
394+
ellipsis: false,
395+
is_assignee_expr,
396+
}
384397
};
385398

386399
self.alloc_expr(record_lit, syntax_ptr)
@@ -458,14 +471,21 @@ impl ExprCollector<'_> {
458471
)
459472
}
460473
ast::Expr::BinExpr(e) => {
474+
let op = e.op_kind();
475+
if let Some(ast::BinaryOp::Assignment { op: None }) = op {
476+
self.is_lowering_assignee_expr = true;
477+
}
461478
let lhs = self.collect_expr_opt(e.lhs());
479+
self.is_lowering_assignee_expr = false;
462480
let rhs = self.collect_expr_opt(e.rhs());
463-
let op = e.op_kind();
464481
self.alloc_expr(Expr::BinaryOp { lhs, rhs, op }, syntax_ptr)
465482
}
466483
ast::Expr::TupleExpr(e) => {
467484
let exprs = e.fields().map(|expr| self.collect_expr(expr)).collect();
468-
self.alloc_expr(Expr::Tuple { exprs }, syntax_ptr)
485+
self.alloc_expr(
486+
Expr::Tuple { exprs, is_assignee_expr: self.is_lowering_assignee_expr },
487+
syntax_ptr,
488+
)
469489
}
470490
ast::Expr::BoxExpr(e) => {
471491
let expr = self.collect_expr_opt(e.expr());
@@ -477,8 +497,14 @@ impl ExprCollector<'_> {
477497

478498
match kind {
479499
ArrayExprKind::ElementList(e) => {
480-
let exprs = e.map(|expr| self.collect_expr(expr)).collect();
481-
self.alloc_expr(Expr::Array(Array::ElementList(exprs)), syntax_ptr)
500+
let elements = e.map(|expr| self.collect_expr(expr)).collect();
501+
self.alloc_expr(
502+
Expr::Array(Array::ElementList {
503+
elements,
504+
is_assignee_expr: self.is_lowering_assignee_expr,
505+
}),
506+
syntax_ptr,
507+
)
482508
}
483509
ArrayExprKind::Repeat { initializer, repeat } => {
484510
let initializer = self.collect_expr_opt(initializer);

crates/hir-def/src/expr.rs

+8-4
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ pub enum Expr {
110110
Call {
111111
callee: ExprId,
112112
args: Box<[ExprId]>,
113+
is_assignee_expr: bool,
113114
},
114115
MethodCall {
115116
receiver: ExprId,
@@ -138,6 +139,8 @@ pub enum Expr {
138139
path: Option<Box<Path>>,
139140
fields: Box<[RecordLitField]>,
140141
spread: Option<ExprId>,
142+
ellipsis: bool,
143+
is_assignee_expr: bool,
141144
},
142145
Field {
143146
expr: ExprId,
@@ -196,6 +199,7 @@ pub enum Expr {
196199
},
197200
Tuple {
198201
exprs: Box<[ExprId]>,
202+
is_assignee_expr: bool,
199203
},
200204
Unsafe {
201205
body: ExprId,
@@ -211,7 +215,7 @@ pub enum Expr {
211215

212216
#[derive(Debug, Clone, Eq, PartialEq)]
213217
pub enum Array {
214-
ElementList(Box<[ExprId]>),
218+
ElementList { elements: Box<[ExprId]>, is_assignee_expr: bool },
215219
Repeat { initializer: ExprId, repeat: ExprId },
216220
}
217221

@@ -285,7 +289,7 @@ impl Expr {
285289
f(*iterable);
286290
f(*body);
287291
}
288-
Expr::Call { callee, args } => {
292+
Expr::Call { callee, args, .. } => {
289293
f(*callee);
290294
args.iter().copied().for_each(f);
291295
}
@@ -339,9 +343,9 @@ impl Expr {
339343
| Expr::Box { expr } => {
340344
f(*expr);
341345
}
342-
Expr::Tuple { exprs } => exprs.iter().copied().for_each(f),
346+
Expr::Tuple { exprs, .. } => exprs.iter().copied().for_each(f),
343347
Expr::Array(a) => match a {
344-
Array::ElementList(exprs) => exprs.iter().copied().for_each(f),
348+
Array::ElementList { elements, .. } => elements.iter().copied().for_each(f),
345349
Array::Repeat { initializer, repeat } => {
346350
f(*initializer);
347351
f(*repeat)

crates/hir-ty/src/diagnostics/expr.rs

+4-1
Original file line numberDiff line numberDiff line change
@@ -305,7 +305,10 @@ pub fn record_literal_missing_fields(
305305
expr: &Expr,
306306
) -> Option<(VariantId, Vec<LocalFieldId>, /*exhaustive*/ bool)> {
307307
let (fields, exhaustive) = match expr {
308-
Expr::RecordLit { path: _, fields, spread } => (fields, spread.is_none()),
308+
Expr::RecordLit { fields, spread, ellipsis, is_assignee_expr, .. } => {
309+
let exhaustive = if *is_assignee_expr { !*ellipsis } else { spread.is_none() };
310+
(fields, exhaustive)
311+
}
309312
_ => return None,
310313
};
311314

crates/hir-ty/src/infer/expr.rs

+9-9
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,7 @@ impl<'a> InferenceContext<'a> {
276276

277277
closure_ty
278278
}
279-
Expr::Call { callee, args } => {
279+
Expr::Call { callee, args, .. } => {
280280
let callee_ty = self.infer_expr(*callee, &Expectation::none());
281281
let mut derefs = Autoderef::new(&mut self.table, callee_ty.clone());
282282
let mut res = None;
@@ -421,7 +421,7 @@ impl<'a> InferenceContext<'a> {
421421
}
422422
TyKind::Never.intern(Interner)
423423
}
424-
Expr::RecordLit { path, fields, spread } => {
424+
Expr::RecordLit { path, fields, spread, .. } => {
425425
let (ty, def_id) = self.resolve_variant(path.as_deref(), false);
426426
if let Some(variant) = def_id {
427427
self.write_variant_resolution(tgt_expr.into(), variant);
@@ -693,7 +693,7 @@ impl<'a> InferenceContext<'a> {
693693
self.err_ty()
694694
}
695695
}
696-
Expr::Tuple { exprs } => {
696+
Expr::Tuple { exprs, .. } => {
697697
let mut tys = match expected
698698
.only_has_type(&mut self.table)
699699
.as_ref()
@@ -724,12 +724,12 @@ impl<'a> InferenceContext<'a> {
724724

725725
let expected = Expectation::has_type(elem_ty.clone());
726726
let len = match array {
727-
Array::ElementList(items) => {
728-
for &expr in items.iter() {
727+
Array::ElementList { elements, .. } => {
728+
for &expr in elements.iter() {
729729
let cur_elem_ty = self.infer_expr_inner(expr, &expected);
730730
coerce.coerce(self, Some(expr), &cur_elem_ty);
731731
}
732-
consteval::usize_const(Some(items.len() as u128))
732+
consteval::usize_const(Some(elements.len() as u128))
733733
}
734734
&Array::Repeat { initializer, repeat } => {
735735
self.infer_expr_coerce(initializer, &Expectation::has_type(elem_ty));
@@ -850,15 +850,15 @@ impl<'a> InferenceContext<'a> {
850850
let rhs_ty = self.resolve_ty_shallow(rhs_ty);
851851

852852
let ty = match &self.body[lhs] {
853-
Expr::Tuple { exprs } => {
853+
Expr::Tuple { exprs, .. } => {
854854
// We don't consider multiple ellipses. This is analogous to
855855
// `hir_def::body::lower::ExprCollector::collect_tuple_pat()`.
856856
let ellipsis = exprs.iter().position(|e| is_rest_expr(*e));
857857
let exprs: Vec<_> = exprs.iter().filter(|e| !is_rest_expr(**e)).copied().collect();
858858

859859
self.infer_tuple_pat_like(&rhs_ty, (), ellipsis, &exprs)
860860
}
861-
Expr::Call { callee, args } => {
861+
Expr::Call { callee, args, .. } => {
862862
// Tuple structs
863863
let path = match &self.body[*callee] {
864864
Expr::Path(path) => Some(path),
@@ -872,7 +872,7 @@ impl<'a> InferenceContext<'a> {
872872

873873
self.infer_tuple_struct_pat_like(path, &rhs_ty, (), lhs, ellipsis, &args)
874874
}
875-
Expr::Array(Array::ElementList(elements)) => {
875+
Expr::Array(Array::ElementList { elements, .. }) => {
876876
let elem_ty = match rhs_ty.kind(Interner) {
877877
TyKind::Array(st, _) => st.clone(),
878878
_ => self.err_ty(),

crates/ide-diagnostics/src/handlers/missing_fields.rs

+31
Original file line numberDiff line numberDiff line change
@@ -292,6 +292,37 @@ fn x(a: S) {
292292
)
293293
}
294294

295+
#[test]
296+
fn missing_record_expr_in_assignee_expr() {
297+
check_diagnostics(
298+
r"
299+
struct S { s: usize, t: usize }
300+
struct S2 { s: S, t: () }
301+
struct T(S);
302+
fn regular(a: S) {
303+
let s;
304+
S { s, .. } = a;
305+
}
306+
fn nested(a: S2) {
307+
let s;
308+
S2 { s: S { s, .. }, .. } = a;
309+
}
310+
fn in_tuple(a: (S,)) {
311+
let s;
312+
(S { s, .. },) = a;
313+
}
314+
fn in_array(a: [S;1]) {
315+
let s;
316+
[S { s, .. },] = a;
317+
}
318+
fn in_tuple_struct(a: T) {
319+
let s;
320+
T(S { s, .. }) = a;
321+
}
322+
",
323+
);
324+
}
325+
295326
#[test]
296327
fn range_mapping_out_of_macros() {
297328
check_fix(

0 commit comments

Comments
 (0)