From be2e80ea4061dd2128d1a561c6e0ae905093224a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Le=C3=B3n=20Orell=20Valerian=20Liehr?= Date: Sun, 10 Mar 2024 12:44:33 +0100 Subject: [PATCH] Recover from struct literals with placeholder path and suggest type from expectation --- compiler/rustc_errors/src/lib.rs | 1 + compiler/rustc_hir_typeck/src/expr.rs | 34 +++++++++++++++++++ compiler/rustc_parse/messages.ftl | 5 +++ compiler/rustc_parse/src/errors.rs | 9 +++++ .../rustc_parse/src/parser/diagnostics.rs | 4 +++ compiler/rustc_parse/src/parser/expr.rs | 26 ++++++++++++++ compiler/rustc_resolve/src/late.rs | 7 ++++ .../struct-lit-placeholder-path.rs | 21 ++++++++++++ .../struct-lit-placeholder-path.stderr | 20 +++++++++++ 9 files changed, 127 insertions(+) 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_errors/src/lib.rs b/compiler/rustc_errors/src/lib.rs index 76b44f73f47b8..a1bc4adeac88c 100644 --- a/compiler/rustc_errors/src/lib.rs +++ b/compiler/rustc_errors/src/lib.rs @@ -507,6 +507,7 @@ struct DiagCtxtInner { #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub enum StashKey { ItemNoType, + StructLitNoType, UnderscoreForArrayLengths, EarlySyntaxWarning, CallIntoMethod, diff --git a/compiler/rustc_hir_typeck/src/expr.rs b/compiler/rustc_hir_typeck/src/expr.rs index fcb490bcfecd5..adee56e135e4d 100644 --- a/compiler/rustc_hir_typeck/src/expr.rs +++ b/compiler/rustc_hir_typeck/src/expr.rs @@ -1640,6 +1640,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fields: &'tcx [hir::ExprField<'tcx>], base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>, ) -> Ty<'tcx> { + // FIXME(fmease): Move this into separate method. + // FIXME(fmease): This doesn't get called given `(_ { x: () }).x` (`hir::Field`). + // Figure out why. + if let QPath::Resolved(None, hir::Path { res: Res::Err, segments, .. }) = qpath + && let [segment] = segments + && segment.ident.name == kw::Empty + && let Expectation::ExpectHasType(ty) = expected + && ty.is_adt() + && let Some(guar) = self.dcx().try_steal_modify_and_emit_err( + expr.span, + StashKey::StructLitNoType, + |err| { + // The parser provided a sub-optimal `HasPlaceholders` suggestion for the type. + // We are typeck and have the real type, so remove that and suggest the actual type. + if let Ok(suggestions) = &mut err.suggestions { + suggestions.clear(); + } + + err.span_suggestion( + qpath.span(), + // FIXME(fmease): Make this translatable. + "replace it with the correct type", + // FIXME(fmease): This doesn't qualify paths within the type appropriately. + // FIXME(fmease): This doesn't use turbofish when emitting generic args. + // FIXME(fmease): Make the type suggestable. + ty.to_string(), + Applicability::MaybeIncorrect, + ); + }, + ) + { + return Ty::new_error(self.tcx, guar); + } + // Find the relevant variant let (variant, adt_ty) = match self.check_struct_path(qpath, expr.hir_id) { Ok(data) => data, diff --git a/compiler/rustc_parse/messages.ftl b/compiler/rustc_parse/messages.ftl index 60cc138fd7bc2..0c6a077ef5634 100644 --- a/compiler/rustc_parse/messages.ftl +++ b/compiler/rustc_parse/messages.ftl @@ -708,6 +708,11 @@ parse_struct_literal_needing_parens = parse_struct_literal_not_allowed_here = struct literals are not allowed here .suggestion = surround the struct literal with parentheses +parse_struct_literal_placeholder_path = + the placeholder `_` is not allowed for the path in struct literals + .label = not allowed in struct literals + .suggestion = replace it with an appropriate 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 c72b7e2cfa727..3cec0d6e9cd7f 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -2974,3 +2974,12 @@ pub(crate) struct AsyncImpl { #[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*/")] + pub span: Span, +} diff --git a/compiler/rustc_parse/src/parser/diagnostics.rs b/compiler/rustc_parse/src/parser/diagnostics.rs index 2f7ac7d3a12e5..e62282e557c30 100644 --- a/compiler/rustc_parse/src/parser/diagnostics.rs +++ b/compiler/rustc_parse/src/parser/diagnostics.rs @@ -951,6 +951,10 @@ impl<'a> Parser<'a> { return None; } } else { + // FIXME(fmease): Under certain conditions return a struct literal + // here and stash this diagnostic under StructLitNoType. + // FIXME(fmease): Furthermore don't suggest redundant curly braces + // around the potential struct literal if possible. self.dcx().emit_err(StructLiteralBodyWithoutPath { span: expr.span, sugg: StructLiteralBodyWithoutPathSugg { diff --git a/compiler/rustc_parse/src/parser/expr.rs b/compiler/rustc_parse/src/parser/expr.rs index 6cc358db9fcae..b4d9f1462f8ec 100644 --- a/compiler/rustc_parse/src/parser/expr.rs +++ b/compiler/rustc_parse/src/parser/expr.rs @@ -1505,6 +1505,10 @@ impl<'a> Parser<'a> { } else if this.check_keyword(kw::Let) { this.parse_expr_let(restrictions) } else if this.eat_keyword(kw::Underscore) { + if let Some(expr) = this.maybe_recover_bad_struct_literal_path()? { + 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. @@ -3720,6 +3724,28 @@ impl<'a> Parser<'a> { }); } + fn maybe_recover_bad_struct_literal_path(&mut self) -> PResult<'a, Option>> { + if self.may_recover() + && self.check_noexpect(&token::OpenDelim(Delimiter::Brace)) + && (!self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL) + || self.is_certainly_not_a_block()) + { + let span = self.prev_token.span; + self.bump(); + + let expr = + self.parse_expr_struct(None, Path::from_ident(Ident::new(kw::Empty, span)), false)?; + + self.dcx() + .create_err(errors::StructLiteralPlaceholderPath { span: expr.span }) + .stash(expr.span, StashKey::StructLitNoType); + + Ok(Some(expr)) + } else { + Ok(None) + } + } + fn err_dotdotdot_syntax(&self, span: Span) { self.dcx().emit_err(errors::DotDotDot { span }); } diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index 18abc5d22b711..1f368bdce2480 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -4304,6 +4304,13 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { } ExprKind::Struct(ref se) => { + // FIXME(fmease): Add a comment maybe. + if se.path == kw::Empty + && self.r.dcx().has_stashed_diagnostic(expr.span, StashKey::StructLitNoType) + { + return; + } + self.smart_resolve_path(expr.id, &se.qself, &se.path, PathSource::Struct); // This is the same as `visit::walk_expr(self, expr);`, but we want to pass the // parent in for accurate suggestions when encountering `Foo { bar }` that should 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..e450eaedc203e --- /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 the 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 the placeholder `_` is not allowed for the path in struct literals + //~| NOTE not allowed in struct literals + //~| HELP replace it with an appropriate 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..cbc252538c978 --- /dev/null +++ b/tests/ui/suggestions/struct-lit-placeholder-path.stderr @@ -0,0 +1,20 @@ +error: the 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: `Stuff` + +error: the 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 an appropriate type: `/*Type*/` + +error: aborting due to 2 previous errors +