Rollup merge of #104504 - compiler-errors:fru-syntax-note, r=estebank
Add a detailed note for missing comma typo w/ FRU syntax Thanks to `@pierwill` for working on this with me! Fixes #104373, perhaps `@alice-i-cecile` can comment on the new error for the example provided on that issue -- feedback is welcome. ``` error[E0063]: missing field `defaulted` in initializer of `Outer` --> $DIR/multi-line-fru-suggestion.rs:14:5 | LL | Outer { | ^^^^^ missing `defaulted` | note: this expression may have been misinterpreted as a `..` range expression --> $DIR/multi-line-fru-suggestion.rs:16:16 | LL | inner: Inner { | ________________^ LL | | a: 1, LL | | b: 2, LL | | } | |_________^ this expression does not end in a comma... LL | ..Default::default() | ^^^^^^^^^^^^^^^^^^^^ ... so this is interpreted as a `..` range expression, instead of functional record update syntax help: to set the remaining fields from `Default::default()`, separate the last named field with a comma | LL | }, | + error: aborting due to previous error For more information about this error, try `rustc --explain E0063`. ```
This commit is contained in:
commit
fce077b053
@ -0,0 +1,8 @@
|
||||
hir_typeck_fru_note = this expression may have been misinterpreted as a `..` range expression
|
||||
hir_typeck_fru_expr = this expression does not end in a comma...
|
||||
hir_typeck_fru_expr2 = ... so this is interpreted as a `..` range expression, instead of functional record update syntax
|
||||
hir_typeck_fru_suggestion =
|
||||
to set the remaining fields{$expr ->
|
||||
[NONE]{""}
|
||||
*[other] {" "}from `{$expr}`
|
||||
}, separate the last named field with a comma
|
@ -51,6 +51,7 @@
|
||||
errors => "../locales/en-US/errors.ftl",
|
||||
expand => "../locales/en-US/expand.ftl",
|
||||
hir_analysis => "../locales/en-US/hir_analysis.ftl",
|
||||
hir_typeck => "../locales/en-US/hir_typeck.ftl",
|
||||
infer => "../locales/en-US/infer.ftl",
|
||||
interface => "../locales/en-US/interface.ftl",
|
||||
lint => "../locales/en-US/lint.ftl",
|
||||
|
@ -467,6 +467,9 @@ pub enum StashKey {
|
||||
/// When an invalid lifetime e.g. `'2` should be reinterpreted
|
||||
/// as a char literal in the parser
|
||||
LifetimeIsChar,
|
||||
/// Maybe there was a typo where a comma was forgotten before
|
||||
/// FRU syntax
|
||||
MaybeFruTypo,
|
||||
}
|
||||
|
||||
fn default_track_diagnostic(_: &Diagnostic) {}
|
||||
|
@ -1,4 +1,5 @@
|
||||
//! Errors emitted by `rustc_hir_analysis`.
|
||||
use rustc_errors::{AddToDiagnostic, Applicability, Diagnostic, MultiSpan, SubdiagnosticMessage};
|
||||
use rustc_macros::{Diagnostic, Subdiagnostic};
|
||||
use rustc_middle::ty::Ty;
|
||||
use rustc_span::{symbol::Ident, Span};
|
||||
@ -133,3 +134,41 @@ pub struct OpMethodGenericParams {
|
||||
pub span: Span,
|
||||
pub method_name: String,
|
||||
}
|
||||
|
||||
pub struct TypeMismatchFruTypo {
|
||||
/// Span of the LHS of the range
|
||||
pub expr_span: Span,
|
||||
/// Span of the `..RHS` part of the range
|
||||
pub fru_span: Span,
|
||||
/// Rendered expression of the RHS of the range
|
||||
pub expr: Option<String>,
|
||||
}
|
||||
|
||||
impl AddToDiagnostic for TypeMismatchFruTypo {
|
||||
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
|
||||
where
|
||||
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
|
||||
{
|
||||
diag.set_arg("expr", self.expr.as_deref().unwrap_or("NONE"));
|
||||
|
||||
// Only explain that `a ..b` is a range if it's split up
|
||||
if self.expr_span.between(self.fru_span).is_empty() {
|
||||
diag.span_note(
|
||||
self.expr_span.to(self.fru_span),
|
||||
rustc_errors::fluent::hir_typeck_fru_note,
|
||||
);
|
||||
} else {
|
||||
let mut multispan: MultiSpan = vec![self.expr_span, self.fru_span].into();
|
||||
multispan.push_span_label(self.expr_span, rustc_errors::fluent::hir_typeck_fru_expr);
|
||||
multispan.push_span_label(self.fru_span, rustc_errors::fluent::hir_typeck_fru_expr2);
|
||||
diag.span_note(multispan, rustc_errors::fluent::hir_typeck_fru_note);
|
||||
}
|
||||
|
||||
diag.span_suggestion(
|
||||
self.expr_span.shrink_to_hi(),
|
||||
rustc_errors::fluent::hir_typeck_fru_suggestion,
|
||||
", ",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,7 @@
|
||||
use crate::cast;
|
||||
use crate::coercion::CoerceMany;
|
||||
use crate::coercion::DynamicCoerceMany;
|
||||
use crate::errors::TypeMismatchFruTypo;
|
||||
use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive};
|
||||
use crate::errors::{
|
||||
FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct,
|
||||
@ -1616,10 +1617,16 @@ fn check_expr_struct_fields(
|
||||
self.demand_coerce_diag(&field.expr, ty, field_type, None, AllowTwoPhase::No);
|
||||
|
||||
if let Some(mut diag) = diag {
|
||||
if idx == ast_fields.len() - 1 && remaining_fields.is_empty() {
|
||||
self.suggest_fru_from_range(field, variant, substs, &mut diag);
|
||||
if idx == ast_fields.len() - 1 {
|
||||
if remaining_fields.is_empty() {
|
||||
self.suggest_fru_from_range(field, variant, substs, &mut diag);
|
||||
diag.emit();
|
||||
} else {
|
||||
diag.stash(field.span, StashKey::MaybeFruTypo);
|
||||
}
|
||||
} else {
|
||||
diag.emit();
|
||||
}
|
||||
diag.emit();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1877,19 +1884,39 @@ fn suggest_fru_from_range(
|
||||
.map(|adt| adt.did())
|
||||
!= range_def_id
|
||||
{
|
||||
let instead = self
|
||||
// Suppress any range expr type mismatches
|
||||
if let Some(mut diag) = self
|
||||
.tcx
|
||||
.sess
|
||||
.diagnostic()
|
||||
.steal_diagnostic(last_expr_field.span, StashKey::MaybeFruTypo)
|
||||
{
|
||||
diag.delay_as_bug();
|
||||
}
|
||||
|
||||
// Use a (somewhat arbitrary) filtering heuristic to avoid printing
|
||||
// expressions that are either too long, or have control character
|
||||
//such as newlines in them.
|
||||
let expr = self
|
||||
.tcx
|
||||
.sess
|
||||
.source_map()
|
||||
.span_to_snippet(range_end.expr.span)
|
||||
.map(|s| format!(" from `{s}`"))
|
||||
.unwrap_or_default();
|
||||
err.span_suggestion(
|
||||
range_start.span.shrink_to_hi(),
|
||||
&format!("to set the remaining fields{instead}, separate the last named field with a comma"),
|
||||
",",
|
||||
Applicability::MaybeIncorrect,
|
||||
);
|
||||
.ok()
|
||||
.filter(|s| s.len() < 25 && !s.contains(|c: char| c.is_control()));
|
||||
|
||||
let fru_span = self
|
||||
.tcx
|
||||
.sess
|
||||
.source_map()
|
||||
.span_extend_while(range_start.span, |c| c.is_whitespace())
|
||||
.unwrap_or(range_start.span).shrink_to_hi().to(range_end.span);
|
||||
|
||||
err.subdiagnostic(TypeMismatchFruTypo {
|
||||
expr_span: range_start.span,
|
||||
fru_span,
|
||||
expr,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
22
src/test/ui/structs/multi-line-fru-suggestion.rs
Normal file
22
src/test/ui/structs/multi-line-fru-suggestion.rs
Normal file
@ -0,0 +1,22 @@
|
||||
#[derive(Default)]
|
||||
struct Inner {
|
||||
a: u8,
|
||||
b: u8,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
struct Outer {
|
||||
inner: Inner,
|
||||
defaulted: u8,
|
||||
}
|
||||
|
||||
fn main(){
|
||||
Outer {
|
||||
//~^ ERROR missing field `defaulted` in initializer of `Outer`
|
||||
inner: Inner {
|
||||
a: 1,
|
||||
b: 2,
|
||||
}
|
||||
..Default::default()
|
||||
};
|
||||
}
|
25
src/test/ui/structs/multi-line-fru-suggestion.stderr
Normal file
25
src/test/ui/structs/multi-line-fru-suggestion.stderr
Normal file
@ -0,0 +1,25 @@
|
||||
error[E0063]: missing field `defaulted` in initializer of `Outer`
|
||||
--> $DIR/multi-line-fru-suggestion.rs:14:5
|
||||
|
|
||||
LL | Outer {
|
||||
| ^^^^^ missing `defaulted`
|
||||
|
|
||||
note: this expression may have been misinterpreted as a `..` range expression
|
||||
--> $DIR/multi-line-fru-suggestion.rs:16:16
|
||||
|
|
||||
LL | inner: Inner {
|
||||
| ________________^
|
||||
LL | | a: 1,
|
||||
LL | | b: 2,
|
||||
LL | | }
|
||||
| |_________^ this expression does not end in a comma...
|
||||
LL | ..Default::default()
|
||||
| ^^^^^^^^^^^^^^^^^^^^ ... so this is interpreted as a `..` range expression, instead of functional record update syntax
|
||||
help: to set the remaining fields from `Default::default()`, separate the last named field with a comma
|
||||
|
|
||||
LL | },
|
||||
| +
|
||||
|
||||
error: aborting due to previous error
|
||||
|
||||
For more information about this error, try `rustc --explain E0063`.
|
@ -7,9 +7,8 @@ struct A {
|
||||
}
|
||||
|
||||
fn a() {
|
||||
let q = A { c: 5,..Default::default() };
|
||||
//~^ ERROR mismatched types
|
||||
//~| ERROR missing fields
|
||||
let q = A { c: 5, ..Default::default() };
|
||||
//~^ ERROR missing fields
|
||||
//~| HELP separate the last named field with a comma
|
||||
let r = A { c: 5, ..Default::default() };
|
||||
assert_eq!(q, r);
|
||||
@ -21,7 +20,7 @@ struct B {
|
||||
}
|
||||
|
||||
fn b() {
|
||||
let q = B { b: 1,..Default::default() };
|
||||
let q = B { b: 1, ..Default::default() };
|
||||
//~^ ERROR mismatched types
|
||||
//~| HELP separate the last named field with a comma
|
||||
let r = B { b: 1 };
|
||||
|
@ -8,8 +8,7 @@ struct A {
|
||||
|
||||
fn a() {
|
||||
let q = A { c: 5..Default::default() };
|
||||
//~^ ERROR mismatched types
|
||||
//~| ERROR missing fields
|
||||
//~^ ERROR missing fields
|
||||
//~| HELP separate the last named field with a comma
|
||||
let r = A { c: 5, ..Default::default() };
|
||||
assert_eq!(q, r);
|
||||
|
@ -1,37 +1,38 @@
|
||||
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`
|
||||
|
|
||||
note: this expression may have been misinterpreted as a `..` range expression
|
||||
--> $DIR/struct-record-suggestion.rs:10:20
|
||||
|
|
||||
LL | let q = A { c: 5..Default::default() };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
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() };
|
||||
LL | let q = A { c: 5, ..Default::default() };
|
||||
| +
|
||||
|
||||
error[E0308]: mismatched types
|
||||
--> $DIR/struct-record-suggestion.rs:24:20
|
||||
--> $DIR/struct-record-suggestion.rs:23:20
|
||||
|
|
||||
LL | let q = B { b: 1..Default::default() };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found struct `std::ops::Range`
|
||||
|
|
||||
= note: expected type `u32`
|
||||
found struct `std::ops::Range<{integer}>`
|
||||
note: this expression may have been misinterpreted as a `..` range expression
|
||||
--> $DIR/struct-record-suggestion.rs:23:20
|
||||
|
|
||||
LL | let q = B { b: 1..Default::default() };
|
||||
| ^^^^^^^^^^^^^^^^^^^^^
|
||||
help: to set the remaining fields from `Default::default()`, separate the last named field with a comma
|
||||
|
|
||||
LL | let q = B { b: 1,..Default::default() };
|
||||
LL | let q = B { b: 1, ..Default::default() };
|
||||
| +
|
||||
|
||||
error: aborting due to 3 previous errors
|
||||
error: aborting due to 2 previous errors
|
||||
|
||||
Some errors have detailed explanations: E0063, E0308.
|
||||
For more information about an error, try `rustc --explain E0063`.
|
||||
|
Loading…
Reference in New Issue
Block a user