Skip to content

Commit da39c2a

Browse files
Unique-Usmanfmease
andcommitted
Recover from struct literals with placeholder path
Based on earlier work by León Orell Valerian Liehr. Co-authored-by: León Orell Valerian Liehr <me@fmease.dev> Signed-off-by: Usman Akinyemi <uniqueusman@archlinux>
1 parent 7704328 commit da39c2a

File tree

9 files changed

+131
-2
lines changed

9 files changed

+131
-2
lines changed

compiler/rustc_errors/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ pub enum StashKey {
660660
/// it's a method call without parens. If later on in `hir_typeck` we find out that this is
661661
/// the case we suppress this message and we give a better suggestion.
662662
GenericInFieldExpr,
663+
StructLitNoType,
663664
}
664665

665666
fn default_track_diagnostic<R>(diag: DiagInner, f: &mut dyn FnMut(DiagInner) -> R) -> R {

compiler/rustc_hir_typeck/src/expr.rs

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,8 @@ use rustc_data_structures::stack::ensure_sufficient_stack;
1212
use rustc_data_structures::unord::UnordMap;
1313
use rustc_errors::codes::*;
1414
use rustc_errors::{
15-
Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, listify, pluralize,
16-
struct_span_code_err,
15+
Applicability, Diag, ErrorGuaranteed, MultiSpan, StashKey, Subdiagnostic, Suggestions, listify,
16+
pluralize, struct_span_code_err,
1717
};
1818
use rustc_hir::attrs::AttributeKind;
1919
use rustc_hir::def::{CtorKind, DefKind, Res};
@@ -1830,6 +1830,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
18301830
fields: &'tcx [hir::ExprField<'tcx>],
18311831
base_expr: &'tcx hir::StructTailExpr<'tcx>,
18321832
) -> Ty<'tcx> {
1833+
// FIXME(fmease): Move this into separate method.
1834+
// FIXME(fmease): This doesn't get called given `(_ { x: () }).x` (`hir::Field`).
1835+
// Figure out why.
1836+
if let QPath::Resolved(None, hir::Path { res: Res::Err, segments, .. }) = qpath
1837+
&& let [segment] = segments
1838+
&& segment.ident.name == kw::Empty
1839+
&& let Expectation::ExpectHasType(ty) = expected
1840+
&& ty.is_adt()
1841+
&& let Some(guar) = self.dcx().try_steal_modify_and_emit_err(
1842+
expr.span,
1843+
StashKey::StructLitNoType,
1844+
|err| {
1845+
// The parser provided a sub-optimal `HasPlaceholders` suggestion for the type.
1846+
// We are typeck and have the real type, so remove that and suggest the actual type.
1847+
if let Suggestions::Enabled(suggestions) = &mut err.suggestions {
1848+
suggestions.clear();
1849+
}
1850+
1851+
err.span_suggestion(
1852+
qpath.span(),
1853+
// FIXME(fmease): Make this translatable.
1854+
"replace it with the correct type",
1855+
// FIXME(fmease): This doesn't qualify paths within the type appropriately.
1856+
// FIXME(fmease): This doesn't use turbofish when emitting generic args.
1857+
// FIXME(fmease): Make the type suggestable.
1858+
ty.to_string(),
1859+
Applicability::MaybeIncorrect,
1860+
);
1861+
},
1862+
)
1863+
{
1864+
return Ty::new_error(self.tcx, guar);
1865+
}
1866+
18331867
// Find the relevant variant
18341868
let (variant, adt_ty) = match self.check_struct_path(qpath, expr.hir_id) {
18351869
Ok(data) => data,

compiler/rustc_parse/messages.ftl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,11 @@ parse_struct_literal_body_without_path =
825825
parse_struct_literal_not_allowed_here = struct literals are not allowed here
826826
.suggestion = surround the struct literal with parentheses
827827
828+
parse_struct_literal_placeholder_path =
829+
the placeholder `_` is not allowed for the path in struct literals
830+
.label = not allowed in struct literals
831+
.suggestion = replace it with an appropriate type
832+
828833
parse_suffixed_literal_in_attribute = suffixed literals are not allowed in attributes
829834
.help = instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.)
830835

compiler/rustc_parse/src/errors.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3684,3 +3684,12 @@ pub(crate) struct ImplReuseInherentImpl {
36843684
#[primary_span]
36853685
pub span: Span,
36863686
}
3687+
3688+
#[derive(Diagnostic)]
3689+
#[diag(parse_struct_literal_placeholder_path)]
3690+
pub(crate) struct StructLiteralPlaceholderPath {
3691+
#[primary_span]
3692+
#[label]
3693+
#[suggestion(applicability = "has-placeholders", code = "/*Type*/")]
3694+
pub span: Span,
3695+
}

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1542,6 +1542,9 @@ impl<'a> Parser<'a> {
15421542
} else if this.check_keyword(exp!(Let)) {
15431543
this.parse_expr_let(restrictions)
15441544
} else if this.eat_keyword(exp!(Underscore)) {
1545+
if let Some(expr) = this.maybe_recover_bad_struct_literal_path()? {
1546+
return Ok(expr);
1547+
}
15451548
Ok(this.mk_expr(this.prev_token.span, ExprKind::Underscore))
15461549
} else if this.token_uninterpolated_span().at_least_rust_2018() {
15471550
// `Span::at_least_rust_2018()` is somewhat expensive; don't get it repeatedly.
@@ -3698,6 +3701,28 @@ impl<'a> Parser<'a> {
36983701
}
36993702
}
37003703

3704+
fn maybe_recover_bad_struct_literal_path(&mut self) -> PResult<'a, Option<Box<Expr>>> {
3705+
if self.may_recover()
3706+
&& self.check_noexpect(&token::OpenBrace)
3707+
&& (!self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
3708+
&& self.is_likely_struct_lit())
3709+
{
3710+
let span = self.prev_token.span;
3711+
self.bump();
3712+
3713+
let expr =
3714+
self.parse_expr_struct(None, Path::from_ident(Ident::new(kw::Empty, span)), false)?;
3715+
3716+
self.dcx()
3717+
.create_err(errors::StructLiteralPlaceholderPath { span: expr.span })
3718+
.stash(expr.span, StashKey::StructLitNoType);
3719+
3720+
Ok(Some(expr))
3721+
} else {
3722+
Ok(None)
3723+
}
3724+
}
3725+
37013726
pub(super) fn parse_struct_fields(
37023727
&mut self,
37033728
pth: ast::Path,

compiler/rustc_resolve/src/late.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5016,6 +5016,17 @@ impl<'a, 'ast, 'ra, 'tcx> LateResolutionVisitor<'a, 'ast, 'ra, 'tcx> {
50165016
}
50175017

50185018
ExprKind::Struct(ref se) => {
5019+
// Skip resolution for recovered struct literals with a placeholder or missing path.
5020+
// This handles both `_ { ... }` and `{ ... }` forms. Since we already stashed
5021+
// a diagnostic for this expression during parsing, we don’t attempt name resolution here.
5022+
// Resolving an empty path would produce redundant or confusing diagnostics; later phases
5023+
// (e.g., typeck) will produce a more accurate error and suggestion.
5024+
if se.path == kw::Empty
5025+
&& self.r.dcx().has_stashed_diagnostic(expr.span, StashKey::StructLitNoType)
5026+
{
5027+
return;
5028+
}
5029+
50195030
self.smart_resolve_path(expr.id, &se.qself, &se.path, PathSource::Struct(parent));
50205031
// This is the same as `visit::walk_expr(self, expr);`, but we want to pass the
50215032
// parent in for accurate suggestions when encountering `Foo { bar }` that should

compiler/rustc_span/src/symbol.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ symbols! {
138138
Union: "union",
139139
Yeet: "yeet",
140140
// tidy-alphabetical-end
141+
142+
// Empty structliteral
143+
Empty: "empty",
141144
}
142145

143146
// Pre-interned symbols that can be referred to with `rustc_span::sym::*`.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Regression test for issue #98282.
2+
3+
mod blah {
4+
pub struct Stuff { x: i32 }
5+
pub fn do_stuff(_: Stuff) {}
6+
}
7+
8+
fn main() {
9+
blah::do_stuff(_ { x: 10 });
10+
//~^ ERROR the placeholder `_` is not allowed for the path in struct literals
11+
//~| NOTE not allowed in struct literals
12+
//~| HELP replace it with the correct type
13+
}
14+
15+
#[cfg(FALSE)]
16+
fn disabled() {
17+
blah::do_stuff(_ { x: 10 });
18+
//~^ ERROR the placeholder `_` is not allowed for the path in struct literals
19+
//~| NOTE not allowed in struct literals
20+
//~| HELP replace it with an appropriate type
21+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
error: the placeholder `_` is not allowed for the path in struct literals
2+
--> $DIR/struct-lit-placeholder-path.rs:9:20
3+
|
4+
LL | blah::do_stuff(_ { x: 10 });
5+
| -^^^^^^^^^^
6+
| |
7+
| not allowed in struct literals
8+
| help: replace it with the correct type: `Stuff`
9+
10+
error: the placeholder `_` is not allowed for the path in struct literals
11+
--> $DIR/struct-lit-placeholder-path.rs:17:20
12+
|
13+
LL | blah::do_stuff(_ { x: 10 });
14+
| ^^^^^^^^^^^
15+
| |
16+
| not allowed in struct literals
17+
| help: replace it with an appropriate type: `/*Type*/`
18+
19+
error: aborting due to 2 previous errors
20+

0 commit comments

Comments
 (0)