Skip to content

Commit 301fc07

Browse files
committed
diagnostics: suggest missing comma in bad FRU syntax
Fixes #51103
1 parent 100f12d commit 301fc07

File tree

4 files changed

+111
-5
lines changed

4 files changed

+111
-5
lines changed

compiler/rustc_typeck/src/check/expr.rs

+55-5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ use rustc_hir as hir;
3232
use rustc_hir::def::{CtorKind, DefKind, Res};
3333
use rustc_hir::def_id::DefId;
3434
use rustc_hir::intravisit::Visitor;
35+
use rustc_hir::lang_items::LangItem;
3536
use rustc_hir::{ExprKind, HirId, QPath};
3637
use rustc_infer::infer;
3738
use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind};
@@ -1556,7 +1557,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15561557
if inaccessible_remaining_fields {
15571558
self.report_inaccessible_fields(adt_ty, span);
15581559
} else {
1559-
self.report_missing_fields(adt_ty, span, remaining_fields);
1560+
self.report_missing_fields(
1561+
adt_ty,
1562+
span,
1563+
remaining_fields,
1564+
variant,
1565+
ast_fields,
1566+
substs,
1567+
);
15601568
}
15611569
}
15621570
}
@@ -1590,6 +1598,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
15901598
adt_ty: Ty<'tcx>,
15911599
span: Span,
15921600
remaining_fields: FxHashMap<Ident, (usize, &ty::FieldDef)>,
1601+
variant: &'tcx ty::VariantDef,
1602+
ast_fields: &'tcx [hir::ExprField<'tcx>],
1603+
substs: SubstsRef<'tcx>,
15931604
) {
15941605
let len = remaining_fields.len();
15951606

@@ -1615,7 +1626,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16151626
}
16161627
};
16171628

1618-
struct_span_err!(
1629+
let mut err = struct_span_err!(
16191630
self.tcx.sess,
16201631
span,
16211632
E0063,
@@ -1624,9 +1635,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16241635
remaining_fields_names,
16251636
truncated_fields_error,
16261637
adt_ty
1627-
)
1628-
.span_label(span, format!("missing {}{}", remaining_fields_names, truncated_fields_error))
1629-
.emit();
1638+
);
1639+
err.span_label(
1640+
span,
1641+
format!("missing {}{}", remaining_fields_names, truncated_fields_error),
1642+
);
1643+
1644+
// If the last field is a range literal, but it isn't supposed to be, then they probably
1645+
// meant to use functional update syntax.
1646+
//
1647+
// I don't use 'is_range_literal' because only double-sided, half-open ranges count.
1648+
if let Some((
1649+
last,
1650+
ExprKind::Struct(
1651+
QPath::LangItem(LangItem::Range, ..),
1652+
&[ref range_start, ref range_end],
1653+
_,
1654+
),
1655+
)) = ast_fields.last().map(|last| (last, &last.expr.kind)) &&
1656+
let variant_field =
1657+
variant.fields.iter().find(|field| field.ident(self.tcx) == last.ident) &&
1658+
let range_def_id = self.tcx.lang_items().range_struct() &&
1659+
variant_field
1660+
.and_then(|field| field.ty(self.tcx, substs).ty_adt_def())
1661+
.map(|adt| adt.did())
1662+
!= range_def_id
1663+
{
1664+
let instead = self
1665+
.tcx
1666+
.sess
1667+
.source_map()
1668+
.span_to_snippet(range_end.expr.span)
1669+
.map(|s| format!(" from `{s}`"))
1670+
.unwrap_or(String::new());
1671+
err.span_suggestion(
1672+
range_start.span.shrink_to_hi(),
1673+
&format!("to set the remaining fields{instead}, separate the last named field with a comma"),
1674+
",".to_string(),
1675+
Applicability::MaybeIncorrect,
1676+
);
1677+
}
1678+
1679+
err.emit();
16301680
}
16311681

16321682
/// Report an error for a struct field expression when there are invisible fields.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#[derive(Debug, Default, Eq, PartialEq)]
3+
struct A {
4+
b: u32,
5+
c: u64,
6+
d: usize,
7+
}
8+
9+
fn main() {
10+
let q = A { c: 5, .. Default::default() };
11+
//~^ ERROR mismatched types
12+
//~| ERROR missing fields
13+
//~| HELP separate the last named field with a comma
14+
let r = A { c: 5, .. Default::default() };
15+
assert_eq!(q, r);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
// run-rustfix
2+
#[derive(Debug, Default, Eq, PartialEq)]
3+
struct A {
4+
b: u32,
5+
c: u64,
6+
d: usize,
7+
}
8+
9+
fn main() {
10+
let q = A { c: 5 .. Default::default() };
11+
//~^ ERROR mismatched types
12+
//~| ERROR missing fields
13+
//~| HELP separate the last named field with a comma
14+
let r = A { c: 5, .. Default::default() };
15+
assert_eq!(q, r);
16+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
error[E0308]: mismatched types
2+
--> $DIR/struct-record-suggestion.rs:10:20
3+
|
4+
LL | let q = A { c: 5 .. Default::default() };
5+
| ^^^^^^^^^^^^^^^^^^^^^^^ expected `u64`, found struct `std::ops::Range`
6+
|
7+
= note: expected type `u64`
8+
found struct `std::ops::Range<{integer}>`
9+
10+
error[E0063]: missing fields `b` and `d` in initializer of `A`
11+
--> $DIR/struct-record-suggestion.rs:10:13
12+
|
13+
LL | let q = A { c: 5 .. Default::default() };
14+
| ^ missing `b` and `d`
15+
|
16+
help: to set the remaining fields from `Default::default()`, separate the last named field with a comma
17+
|
18+
LL | let q = A { c: 5, .. Default::default() };
19+
| +
20+
21+
error: aborting due to 2 previous errors
22+
23+
Some errors have detailed explanations: E0063, E0308.
24+
For more information about an error, try `rustc --explain E0063`.

0 commit comments

Comments
 (0)