diff --git a/compiler/rustc_typeck/src/check/expr.rs b/compiler/rustc_typeck/src/check/expr.rs index 0a720f15025..e5784259ce8 100644 --- a/compiler/rustc_typeck/src/check/expr.rs +++ b/compiler/rustc_typeck/src/check/expr.rs @@ -32,6 +32,7 @@ use rustc_hir as hir; use rustc_hir::def::{CtorKind, DefKind, Res}; use rustc_hir::def_id::DefId; use rustc_hir::intravisit::Visitor; +use rustc_hir::lang_items::LangItem; use rustc_hir::{ExprKind, HirId, QPath}; use rustc_infer::infer; use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; @@ -1556,7 +1557,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if inaccessible_remaining_fields { self.report_inaccessible_fields(adt_ty, span); } else { - self.report_missing_fields(adt_ty, span, remaining_fields); + self.report_missing_fields( + adt_ty, + span, + remaining_fields, + variant, + ast_fields, + substs, + ); } } } @@ -1590,6 +1598,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { adt_ty: Ty<'tcx>, span: Span, remaining_fields: FxHashMap, + variant: &'tcx ty::VariantDef, + ast_fields: &'tcx [hir::ExprField<'tcx>], + substs: SubstsRef<'tcx>, ) { let len = remaining_fields.len(); @@ -1615,7 +1626,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } }; - struct_span_err!( + let mut err = struct_span_err!( self.tcx.sess, span, E0063, @@ -1624,9 +1635,48 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { remaining_fields_names, truncated_fields_error, adt_ty - ) - .span_label(span, format!("missing {}{}", remaining_fields_names, truncated_fields_error)) - .emit(); + ); + err.span_label( + span, + format!("missing {}{}", remaining_fields_names, truncated_fields_error), + ); + + // If the last field is a range literal, but it isn't supposed to be, then they probably + // meant to use functional update syntax. + // + // I don't use 'is_range_literal' because only double-sided, half-open ranges count. + if let Some(( + last, + ExprKind::Struct( + QPath::LangItem(LangItem::Range, ..), + &[ref range_start, ref range_end], + _, + ), + )) = ast_fields.last().map(|last| (last, &last.expr.kind)) && + let variant_field = + variant.fields.iter().find(|field| field.ident(self.tcx) == last.ident) && + let range_def_id = self.tcx.lang_items().range_struct() && + variant_field + .and_then(|field| field.ty(self.tcx, substs).ty_adt_def()) + .map(|adt| adt.did()) + != range_def_id + { + let instead = self + .tcx + .sess + .source_map() + .span_to_snippet(range_end.expr.span) + .map(|s| format!(" from `{s}`")) + .unwrap_or(String::new()); + err.span_suggestion( + range_start.span.shrink_to_hi(), + &format!("to set the remaining fields{instead}, separate the last named field with a comma"), + ",".to_string(), + Applicability::MaybeIncorrect, + ); + } + + err.emit(); } /// Report an error for a struct field expression when there are invisible fields. diff --git a/src/test/ui/structs/struct-record-suggestion.fixed b/src/test/ui/structs/struct-record-suggestion.fixed new file mode 100644 index 00000000000..48144cd1ce2 --- /dev/null +++ b/src/test/ui/structs/struct-record-suggestion.fixed @@ -0,0 +1,16 @@ +// run-rustfix +#[derive(Debug, Default, Eq, PartialEq)] +struct A { + b: u32, + c: u64, + d: usize, +} + +fn main() { + let q = A { c: 5, .. Default::default() }; + //~^ ERROR mismatched types + //~| ERROR missing fields + //~| HELP separate the last named field with a comma + let r = A { c: 5, .. Default::default() }; + assert_eq!(q, r); +} diff --git a/src/test/ui/structs/struct-record-suggestion.rs b/src/test/ui/structs/struct-record-suggestion.rs new file mode 100644 index 00000000000..6d169d5c6db --- /dev/null +++ b/src/test/ui/structs/struct-record-suggestion.rs @@ -0,0 +1,16 @@ +// run-rustfix +#[derive(Debug, Default, Eq, PartialEq)] +struct A { + b: u32, + c: u64, + d: usize, +} + +fn main() { + let q = A { c: 5 .. Default::default() }; + //~^ ERROR mismatched types + //~| ERROR missing fields + //~| HELP separate the last named field with a comma + let r = A { c: 5, .. Default::default() }; + assert_eq!(q, r); +} diff --git a/src/test/ui/structs/struct-record-suggestion.stderr b/src/test/ui/structs/struct-record-suggestion.stderr new file mode 100644 index 00000000000..e5bd03117b9 --- /dev/null +++ b/src/test/ui/structs/struct-record-suggestion.stderr @@ -0,0 +1,24 @@ +error[E0308]: mismatched types + --> $DIR/struct-record-suggestion.rs:10:20 + | +LL | let q = A { c: 5 .. Default::default() }; + | ^^^^^^^^^^^^^^^^^^^^^^^ expected `u64`, found struct `std::ops::Range` + | + = note: expected type `u64` + found struct `std::ops::Range<{integer}>` + +error[E0063]: missing fields `b` and `d` in initializer of `A` + --> $DIR/struct-record-suggestion.rs:10:13 + | +LL | let q = A { c: 5 .. Default::default() }; + | ^ missing `b` and `d` + | +help: to set the remaining fields from `Default::default()`, separate the last named field with a comma + | +LL | let q = A { c: 5, .. Default::default() }; + | + + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0063, E0308. +For more information about an error, try `rustc --explain E0063`.