Skip to content

Commit fce077b

Browse files
authored
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`. ```
2 parents 7a10c4a + bb0cb9a commit fce077b

File tree

10 files changed

+155
-31
lines changed

10 files changed

+155
-31
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
hir_typeck_fru_note = this expression may have been misinterpreted as a `..` range expression
2+
hir_typeck_fru_expr = this expression does not end in a comma...
3+
hir_typeck_fru_expr2 = ... so this is interpreted as a `..` range expression, instead of functional record update syntax
4+
hir_typeck_fru_suggestion =
5+
to set the remaining fields{$expr ->
6+
[NONE]{""}
7+
*[other] {" "}from `{$expr}`
8+
}, separate the last named field with a comma

compiler/rustc_error_messages/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ fluent_messages! {
5151
errors => "../locales/en-US/errors.ftl",
5252
expand => "../locales/en-US/expand.ftl",
5353
hir_analysis => "../locales/en-US/hir_analysis.ftl",
54+
hir_typeck => "../locales/en-US/hir_typeck.ftl",
5455
infer => "../locales/en-US/infer.ftl",
5556
interface => "../locales/en-US/interface.ftl",
5657
lint => "../locales/en-US/lint.ftl",

compiler/rustc_errors/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,9 @@ pub enum StashKey {
467467
/// When an invalid lifetime e.g. `'2` should be reinterpreted
468468
/// as a char literal in the parser
469469
LifetimeIsChar,
470+
/// Maybe there was a typo where a comma was forgotten before
471+
/// FRU syntax
472+
MaybeFruTypo,
470473
}
471474

472475
fn default_track_diagnostic(_: &Diagnostic) {}

compiler/rustc_hir_typeck/src/errors.rs

+39
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
//! Errors emitted by `rustc_hir_analysis`.
2+
use rustc_errors::{AddToDiagnostic, Applicability, Diagnostic, MultiSpan, SubdiagnosticMessage};
23
use rustc_macros::{Diagnostic, Subdiagnostic};
34
use rustc_middle::ty::Ty;
45
use rustc_span::{symbol::Ident, Span};
@@ -133,3 +134,41 @@ pub struct OpMethodGenericParams {
133134
pub span: Span,
134135
pub method_name: String,
135136
}
137+
138+
pub struct TypeMismatchFruTypo {
139+
/// Span of the LHS of the range
140+
pub expr_span: Span,
141+
/// Span of the `..RHS` part of the range
142+
pub fru_span: Span,
143+
/// Rendered expression of the RHS of the range
144+
pub expr: Option<String>,
145+
}
146+
147+
impl AddToDiagnostic for TypeMismatchFruTypo {
148+
fn add_to_diagnostic_with<F>(self, diag: &mut Diagnostic, _: F)
149+
where
150+
F: Fn(&mut Diagnostic, SubdiagnosticMessage) -> SubdiagnosticMessage,
151+
{
152+
diag.set_arg("expr", self.expr.as_deref().unwrap_or("NONE"));
153+
154+
// Only explain that `a ..b` is a range if it's split up
155+
if self.expr_span.between(self.fru_span).is_empty() {
156+
diag.span_note(
157+
self.expr_span.to(self.fru_span),
158+
rustc_errors::fluent::hir_typeck_fru_note,
159+
);
160+
} else {
161+
let mut multispan: MultiSpan = vec![self.expr_span, self.fru_span].into();
162+
multispan.push_span_label(self.expr_span, rustc_errors::fluent::hir_typeck_fru_expr);
163+
multispan.push_span_label(self.fru_span, rustc_errors::fluent::hir_typeck_fru_expr2);
164+
diag.span_note(multispan, rustc_errors::fluent::hir_typeck_fru_note);
165+
}
166+
167+
diag.span_suggestion(
168+
self.expr_span.shrink_to_hi(),
169+
rustc_errors::fluent::hir_typeck_fru_suggestion,
170+
", ",
171+
Applicability::MaybeIncorrect,
172+
);
173+
}
174+
}

compiler/rustc_hir_typeck/src/expr.rs

+39-12
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
use crate::cast;
66
use crate::coercion::CoerceMany;
77
use crate::coercion::DynamicCoerceMany;
8+
use crate::errors::TypeMismatchFruTypo;
89
use crate::errors::{AddressOfTemporaryTaken, ReturnStmtOutsideOfFnBody, StructExprNonExhaustive};
910
use crate::errors::{
1011
FieldMultiplySpecifiedInInitializer, FunctionalRecordUpdateOnNonStruct,
@@ -1616,10 +1617,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16161617
self.demand_coerce_diag(&field.expr, ty, field_type, None, AllowTwoPhase::No);
16171618

16181619
if let Some(mut diag) = diag {
1619-
if idx == ast_fields.len() - 1 && remaining_fields.is_empty() {
1620-
self.suggest_fru_from_range(field, variant, substs, &mut diag);
1620+
if idx == ast_fields.len() - 1 {
1621+
if remaining_fields.is_empty() {
1622+
self.suggest_fru_from_range(field, variant, substs, &mut diag);
1623+
diag.emit();
1624+
} else {
1625+
diag.stash(field.span, StashKey::MaybeFruTypo);
1626+
}
1627+
} else {
1628+
diag.emit();
16211629
}
1622-
diag.emit();
16231630
}
16241631
}
16251632

@@ -1877,19 +1884,39 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18771884
.map(|adt| adt.did())
18781885
!= range_def_id
18791886
{
1880-
let instead = self
1887+
// Suppress any range expr type mismatches
1888+
if let Some(mut diag) = self
1889+
.tcx
1890+
.sess
1891+
.diagnostic()
1892+
.steal_diagnostic(last_expr_field.span, StashKey::MaybeFruTypo)
1893+
{
1894+
diag.delay_as_bug();
1895+
}
1896+
1897+
// Use a (somewhat arbitrary) filtering heuristic to avoid printing
1898+
// expressions that are either too long, or have control character
1899+
//such as newlines in them.
1900+
let expr = self
18811901
.tcx
18821902
.sess
18831903
.source_map()
18841904
.span_to_snippet(range_end.expr.span)
1885-
.map(|s| format!(" from `{s}`"))
1886-
.unwrap_or_default();
1887-
err.span_suggestion(
1888-
range_start.span.shrink_to_hi(),
1889-
&format!("to set the remaining fields{instead}, separate the last named field with a comma"),
1890-
",",
1891-
Applicability::MaybeIncorrect,
1892-
);
1905+
.ok()
1906+
.filter(|s| s.len() < 25 && !s.contains(|c: char| c.is_control()));
1907+
1908+
let fru_span = self
1909+
.tcx
1910+
.sess
1911+
.source_map()
1912+
.span_extend_while(range_start.span, |c| c.is_whitespace())
1913+
.unwrap_or(range_start.span).shrink_to_hi().to(range_end.span);
1914+
1915+
err.subdiagnostic(TypeMismatchFruTypo {
1916+
expr_span: range_start.span,
1917+
fru_span,
1918+
expr,
1919+
});
18931920
}
18941921
}
18951922

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
#[derive(Default)]
2+
struct Inner {
3+
a: u8,
4+
b: u8,
5+
}
6+
7+
#[derive(Default)]
8+
struct Outer {
9+
inner: Inner,
10+
defaulted: u8,
11+
}
12+
13+
fn main(){
14+
Outer {
15+
//~^ ERROR missing field `defaulted` in initializer of `Outer`
16+
inner: Inner {
17+
a: 1,
18+
b: 2,
19+
}
20+
..Default::default()
21+
};
22+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
error[E0063]: missing field `defaulted` in initializer of `Outer`
2+
--> $DIR/multi-line-fru-suggestion.rs:14:5
3+
|
4+
LL | Outer {
5+
| ^^^^^ missing `defaulted`
6+
|
7+
note: this expression may have been misinterpreted as a `..` range expression
8+
--> $DIR/multi-line-fru-suggestion.rs:16:16
9+
|
10+
LL | inner: Inner {
11+
| ________________^
12+
LL | | a: 1,
13+
LL | | b: 2,
14+
LL | | }
15+
| |_________^ this expression does not end in a comma...
16+
LL | ..Default::default()
17+
| ^^^^^^^^^^^^^^^^^^^^ ... so this is interpreted as a `..` range expression, instead of functional record update syntax
18+
help: to set the remaining fields from `Default::default()`, separate the last named field with a comma
19+
|
20+
LL | },
21+
| +
22+
23+
error: aborting due to previous error
24+
25+
For more information about this error, try `rustc --explain E0063`.

src/test/ui/structs/struct-record-suggestion.fixed

+3-4
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,8 @@ struct A {
77
}
88

99
fn a() {
10-
let q = A { c: 5,..Default::default() };
11-
//~^ ERROR mismatched types
12-
//~| ERROR missing fields
10+
let q = A { c: 5, ..Default::default() };
11+
//~^ ERROR missing fields
1312
//~| HELP separate the last named field with a comma
1413
let r = A { c: 5, ..Default::default() };
1514
assert_eq!(q, r);
@@ -21,7 +20,7 @@ struct B {
2120
}
2221

2322
fn b() {
24-
let q = B { b: 1,..Default::default() };
23+
let q = B { b: 1, ..Default::default() };
2524
//~^ ERROR mismatched types
2625
//~| HELP separate the last named field with a comma
2726
let r = B { b: 1 };

src/test/ui/structs/struct-record-suggestion.rs

+1-2
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,7 @@ struct A {
88

99
fn a() {
1010
let q = A { c: 5..Default::default() };
11-
//~^ ERROR mismatched types
12-
//~| ERROR missing fields
11+
//~^ ERROR missing fields
1312
//~| HELP separate the last named field with a comma
1413
let r = A { c: 5, ..Default::default() };
1514
assert_eq!(q, r);
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,38 @@
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-
101
error[E0063]: missing fields `b` and `d` in initializer of `A`
112
--> $DIR/struct-record-suggestion.rs:10:13
123
|
134
LL | let q = A { c: 5..Default::default() };
145
| ^ missing `b` and `d`
156
|
7+
note: this expression may have been misinterpreted as a `..` range expression
8+
--> $DIR/struct-record-suggestion.rs:10:20
9+
|
10+
LL | let q = A { c: 5..Default::default() };
11+
| ^^^^^^^^^^^^^^^^^^^^^
1612
help: to set the remaining fields from `Default::default()`, separate the last named field with a comma
1713
|
18-
LL | let q = A { c: 5,..Default::default() };
14+
LL | let q = A { c: 5, ..Default::default() };
1915
| +
2016

2117
error[E0308]: mismatched types
22-
--> $DIR/struct-record-suggestion.rs:24:20
18+
--> $DIR/struct-record-suggestion.rs:23:20
2319
|
2420
LL | let q = B { b: 1..Default::default() };
2521
| ^^^^^^^^^^^^^^^^^^^^^ expected `u32`, found struct `std::ops::Range`
2622
|
2723
= note: expected type `u32`
2824
found struct `std::ops::Range<{integer}>`
25+
note: this expression may have been misinterpreted as a `..` range expression
26+
--> $DIR/struct-record-suggestion.rs:23:20
27+
|
28+
LL | let q = B { b: 1..Default::default() };
29+
| ^^^^^^^^^^^^^^^^^^^^^
2930
help: to set the remaining fields from `Default::default()`, separate the last named field with a comma
3031
|
31-
LL | let q = B { b: 1,..Default::default() };
32+
LL | let q = B { b: 1, ..Default::default() };
3233
| +
3334

34-
error: aborting due to 3 previous errors
35+
error: aborting due to 2 previous errors
3536

3637
Some errors have detailed explanations: E0063, E0308.
3738
For more information about an error, try `rustc --explain E0063`.

0 commit comments

Comments
 (0)