Rollup merge of #151290 - Unique-Usman:ua/nostruct, r=estebank

Recover from struct literals with placeholder or empty path

Based on earlier work by León Orell Valerian Liehr.
This commit is contained in:
Jonathan Brouwer 2026-01-26 18:19:12 +01:00 committed by GitHub
commit 6ff5bb3968
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 187 additions and 6 deletions

View file

@ -822,9 +822,19 @@ parse_struct_literal_body_without_path =
struct literal body without path
.suggestion = you might have forgotten to add the struct literal inside the block
parse_struct_literal_body_without_path_late =
struct literal body without path
.label = struct name missing for struct literal
.suggestion = add the correct type
parse_struct_literal_not_allowed_here = struct literals are not allowed here
.suggestion = surround the struct literal with parentheses
parse_struct_literal_placeholder_path =
placeholder `_` is not allowed for the path in struct literals
.label = not allowed in struct literals
.suggestion = replace it with the correct type
parse_suffixed_literal_in_attribute = suffixed literals are not allowed in attributes
.help = instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.)

View file

@ -3684,3 +3684,22 @@ pub(crate) struct ImplReuseInherentImpl {
#[primary_span]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parse_struct_literal_placeholder_path)]
pub(crate) struct StructLiteralPlaceholderPath {
#[primary_span]
#[label]
#[suggestion(applicability = "has-placeholders", code = "/* Type */", style = "verbose")]
pub span: Span,
}
#[derive(Diagnostic)]
#[diag(parse_struct_literal_body_without_path_late)]
pub(crate) struct StructLiteralWithoutPathLate {
#[primary_span]
#[label]
pub span: Span,
#[suggestion(applicability = "has-placeholders", code = "/* Type */ ", style = "verbose")]
pub suggestion_span: Span,
}

View file

@ -1468,6 +1468,9 @@ impl<'a> Parser<'a> {
} else if this.check(exp!(OpenParen)) {
this.parse_expr_tuple_parens(restrictions)
} else if this.check(exp!(OpenBrace)) {
if let Some(expr) = this.maybe_recover_bad_struct_literal_path(false)? {
return Ok(expr);
}
this.parse_expr_block(None, lo, BlockCheckMode::Default)
} else if this.check(exp!(Or)) || this.check(exp!(OrOr)) {
this.parse_expr_closure().map_err(|mut err| {
@ -1542,6 +1545,9 @@ impl<'a> Parser<'a> {
} else if this.check_keyword(exp!(Let)) {
this.parse_expr_let(restrictions)
} else if this.eat_keyword(exp!(Underscore)) {
if let Some(expr) = this.maybe_recover_bad_struct_literal_path(true)? {
return Ok(expr);
}
Ok(this.mk_expr(this.prev_token.span, ExprKind::Underscore))
} else if this.token_uninterpolated_span().at_least_rust_2018() {
// `Span::at_least_rust_2018()` is somewhat expensive; don't get it repeatedly.
@ -3698,6 +3704,45 @@ impl<'a> Parser<'a> {
}
}
fn maybe_recover_bad_struct_literal_path(
&mut self,
is_underscore_entry_point: bool,
) -> PResult<'a, Option<Box<Expr>>> {
if self.may_recover()
&& self.check_noexpect(&token::OpenBrace)
&& (!self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
&& self.is_likely_struct_lit())
{
let span = if is_underscore_entry_point {
self.prev_token.span
} else {
self.token.span.shrink_to_lo()
};
self.bump(); // {
let expr = self.parse_expr_struct(
None,
Path::from_ident(Ident::new(kw::Underscore, span)),
false,
)?;
let guar = if is_underscore_entry_point {
self.dcx().create_err(errors::StructLiteralPlaceholderPath { span }).emit()
} else {
self.dcx()
.create_err(errors::StructLiteralWithoutPathLate {
span: expr.span,
suggestion_span: expr.span.shrink_to_lo(),
})
.emit()
};
Ok(Some(self.mk_expr_err(expr.span, guar)))
} else {
Ok(None)
}
}
pub(super) fn parse_struct_fields(
&mut self,
pth: ast::Path,

View file

@ -21,14 +21,12 @@ LL | let x = {
| _____________^
LL | | val: (),
LL | | };
| |_____^
| |_____^ struct name missing for struct literal
|
help: you might have forgotten to add the struct literal inside the block
|
LL ~ let x = { SomeStruct {
LL | val: (),
LL ~ } };
help: add the correct type
|
LL | let x = /* Type */ {
| ++++++++++
error[E0308]: mismatched types
--> $DIR/bare-struct-body.rs:11:14

View file

@ -0,0 +1,14 @@
fn main() {
let _ = {foo: (), bar: {} }; //~ ERROR struct literal body without path
//~| NOTE struct name missing for struct literal
//~| HELP add the correct type
let _ = _ {foo: (), bar: {} }; //~ ERROR placeholder `_` is not allowed for the path in struct literals
//~| NOTE not allowed in struct literals
//~| HELP replace it with the correct type
let _ = {foo: ()}; //~ ERROR struct literal body without path
//~| NOTE struct name missing for struct literal
//~| HELP add the correct type
let _ = _ {foo: ()}; //~ ERROR placeholder `_` is not allowed for the path in struct literals
//~| NOTE not allowed in struct literals
//~| HELP replace it with the correct type
}

View file

@ -0,0 +1,48 @@
error: struct literal body without path
--> $DIR/struct-lit-placeholder-or-empty-path.rs:2:13
|
LL | let _ = {foo: (), bar: {} };
| ^^^^^^^^^^^^^^^^^^^ struct name missing for struct literal
|
help: add the correct type
|
LL | let _ = /* Type */ {foo: (), bar: {} };
| ++++++++++
error: placeholder `_` is not allowed for the path in struct literals
--> $DIR/struct-lit-placeholder-or-empty-path.rs:5:13
|
LL | let _ = _ {foo: (), bar: {} };
| ^ not allowed in struct literals
|
help: replace it with the correct type
|
LL - let _ = _ {foo: (), bar: {} };
LL + let _ = /* Type */ {foo: (), bar: {} };
|
error: struct literal body without path
--> $DIR/struct-lit-placeholder-or-empty-path.rs:8:13
|
LL | let _ = {foo: ()};
| ^^^^^^^^^ struct name missing for struct literal
|
help: add the correct type
|
LL | let _ = /* Type */ {foo: ()};
| ++++++++++
error: placeholder `_` is not allowed for the path in struct literals
--> $DIR/struct-lit-placeholder-or-empty-path.rs:11:13
|
LL | let _ = _ {foo: ()};
| ^ not allowed in struct literals
|
help: replace it with the correct type
|
LL - let _ = _ {foo: ()};
LL + let _ = /* Type */ {foo: ()};
|
error: aborting due to 4 previous errors

View file

@ -0,0 +1,21 @@
// Regression test for issue #98282.
mod blah {
pub struct Stuff { x: i32 }
pub fn do_stuff(_: Stuff) {}
}
fn main() {
blah::do_stuff(_ { x: 10 });
//~^ ERROR placeholder `_` is not allowed for the path in struct literals
//~| NOTE not allowed in struct literals
//~| HELP replace it with the correct type
}
#[cfg(FALSE)]
fn disabled() {
blah::do_stuff(_ { x: 10 });
//~^ ERROR placeholder `_` is not allowed for the path in struct literals
//~| NOTE not allowed in struct literals
//~| HELP replace it with the correct type
}

View file

@ -0,0 +1,26 @@
error: placeholder `_` is not allowed for the path in struct literals
--> $DIR/struct-lit-placeholder-path.rs:9:20
|
LL | blah::do_stuff(_ { x: 10 });
| ^ not allowed in struct literals
|
help: replace it with the correct type
|
LL - blah::do_stuff(_ { x: 10 });
LL + blah::do_stuff(/* Type */ { x: 10 });
|
error: placeholder `_` is not allowed for the path in struct literals
--> $DIR/struct-lit-placeholder-path.rs:17:20
|
LL | blah::do_stuff(_ { x: 10 });
| ^ not allowed in struct literals
|
help: replace it with the correct type
|
LL - blah::do_stuff(_ { x: 10 });
LL + blah::do_stuff(/* Type */ { x: 10 });
|
error: aborting due to 2 previous errors