From 54fc546f203ac11b4a7b41171b4359844d0cd706 Mon Sep 17 00:00:00 2001 From: Usman Akinyemi Date: Sun, 18 Jan 2026 13:35:19 +0530 Subject: [PATCH] Recover from struct literals with placeholder or empty path MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Based on earlier work by León Orell Valerian Liehr. Co-authored-by: León Orell Valerian Liehr Signed-off-by: Usman Akinyemi --- compiler/rustc_parse/messages.ftl | 10 ++++ compiler/rustc_parse/src/errors.rs | 19 ++++++++ compiler/rustc_parse/src/parser/expr.rs | 45 +++++++++++++++++ tests/ui/parser/bare-struct-body.stderr | 10 ++-- .../struct-lit-placeholder-or-empty-path.rs | 14 ++++++ ...truct-lit-placeholder-or-empty-path.stderr | 48 +++++++++++++++++++ .../struct-lit-placeholder-path.rs | 21 ++++++++ .../struct-lit-placeholder-path.stderr | 26 ++++++++++ 8 files changed, 187 insertions(+), 6 deletions(-) create mode 100644 tests/ui/parser/struct-lit-placeholder-or-empty-path.rs create mode 100644 tests/ui/parser/struct-lit-placeholder-or-empty-path.stderr create mode 100644 tests/ui/suggestions/struct-lit-placeholder-path.rs create mode 100644 tests/ui/suggestions/struct-lit-placeholder-path.stderr diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index 1331d99c01ead..449d0b964fd48 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -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.) diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 60e4a240c85e9..42327c7e343d1 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -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, +} diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 8835fba1adcdc..c31a4798b471e 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -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>> { + 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, diff --git a/tests/ui/parser/bare-struct-body.stderr b/tests/ui/parser/bare-struct-body.stderr index 7d17ea59647ec..1b817e2b9ee20 100644 --- a/tests/ui/parser/bare-struct-body.stderr +++ b/tests/ui/parser/bare-struct-body.stderr @@ -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 diff --git a/tests/ui/parser/struct-lit-placeholder-or-empty-path.rs b/tests/ui/parser/struct-lit-placeholder-or-empty-path.rs new file mode 100644 index 0000000000000..8f91eb68b2eb3 --- /dev/null +++ b/tests/ui/parser/struct-lit-placeholder-or-empty-path.rs @@ -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 +} diff --git a/tests/ui/parser/struct-lit-placeholder-or-empty-path.stderr b/tests/ui/parser/struct-lit-placeholder-or-empty-path.stderr new file mode 100644 index 0000000000000..62a417aefc1ec --- /dev/null +++ b/tests/ui/parser/struct-lit-placeholder-or-empty-path.stderr @@ -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 + diff --git a/tests/ui/suggestions/struct-lit-placeholder-path.rs b/tests/ui/suggestions/struct-lit-placeholder-path.rs new file mode 100644 index 0000000000000..190440dd15697 --- /dev/null +++ b/tests/ui/suggestions/struct-lit-placeholder-path.rs @@ -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 +} diff --git a/tests/ui/suggestions/struct-lit-placeholder-path.stderr b/tests/ui/suggestions/struct-lit-placeholder-path.stderr new file mode 100644 index 0000000000000..8cd24b6f268d1 --- /dev/null +++ b/tests/ui/suggestions/struct-lit-placeholder-path.stderr @@ -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 +