Skip to content

Commit 93bfefa

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 93bfefa

10 files changed

Lines changed: 181 additions & 18 deletions

File tree

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::EmptyStructType
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: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,9 +822,19 @@ parse_struct_literal_body_without_path =
822822
struct literal body without path
823823
.suggestion = you might have forgotten to add the struct literal inside the block
824824
825+
parse_struct_literal_body_without_path_late =
826+
struct literal body without path
827+
.label = absent of struct name for this struct literal
828+
.suggestion = replace it with an appropriate type
829+
825830
parse_struct_literal_not_allowed_here = struct literals are not allowed here
826831
.suggestion = surround the struct literal with parentheses
827832
833+
parse_struct_literal_placeholder_path =
834+
the placeholder `_` is not allowed for the path in struct literals
835+
.label = not allowed in struct literals
836+
.suggestion = replace it with an appropriate type
837+
828838
parse_suffixed_literal_in_attribute = suffixed literals are not allowed in attributes
829839
.help = instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.)
830840

compiler/rustc_parse/src/errors.rs

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3684,3 +3684,21 @@ 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+
}
3696+
3697+
#[derive(Diagnostic)]
3698+
#[diag(parse_struct_literal_body_without_path_late)]
3699+
pub(crate) struct StructLiteralWithoutPathLate {
3700+
#[primary_span]
3701+
#[label]
3702+
#[suggestion(applicability = "has-placeholders", code = "/*Type*/")]
3703+
pub span: Span,
3704+
}

compiler/rustc_parse/src/parser/expr.rs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1468,6 +1468,9 @@ impl<'a> Parser<'a> {
14681468
} else if this.check(exp!(OpenParen)) {
14691469
this.parse_expr_tuple_parens(restrictions)
14701470
} else if this.check(exp!(OpenBrace)) {
1471+
if let Some(expr) = this.maybe_recover_bad_struct_literal_path(false)? {
1472+
return Ok(expr);
1473+
}
14711474
this.parse_expr_block(None, lo, BlockCheckMode::Default)
14721475
} else if this.check(exp!(Or)) || this.check(exp!(OrOr)) {
14731476
this.parse_expr_closure().map_err(|mut err| {
@@ -1542,6 +1545,9 @@ impl<'a> Parser<'a> {
15421545
} else if this.check_keyword(exp!(Let)) {
15431546
this.parse_expr_let(restrictions)
15441547
} else if this.eat_keyword(exp!(Underscore)) {
1548+
if let Some(expr) = this.maybe_recover_bad_struct_literal_path(true)? {
1549+
return Ok(expr);
1550+
}
15451551
Ok(this.mk_expr(this.prev_token.span, ExprKind::Underscore))
15461552
} else if this.token_uninterpolated_span().at_least_rust_2018() {
15471553
// `Span::at_least_rust_2018()` is somewhat expensive; don't get it repeatedly.
@@ -3698,6 +3704,46 @@ impl<'a> Parser<'a> {
36983704
}
36993705
}
37003706

3707+
fn maybe_recover_bad_struct_literal_path(
3708+
&mut self,
3709+
is_underscore_entry_point: bool,
3710+
) -> PResult<'a, Option<Box<Expr>>> {
3711+
if self.may_recover()
3712+
&& self.check_noexpect(&token::OpenBrace)
3713+
&& (!self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
3714+
&& self.is_likely_struct_lit())
3715+
{
3716+
let span = if is_underscore_entry_point {
3717+
self.prev_token.span
3718+
} else {
3719+
self.prev_token.span.shrink_to_hi()
3720+
};
3721+
3722+
// { consumed
3723+
self.bump();
3724+
3725+
let expr = self.parse_expr_struct(
3726+
None,
3727+
Path::from_ident(Ident::new(kw::EmptyStructType, span)),
3728+
false,
3729+
)?;
3730+
3731+
if is_underscore_entry_point {
3732+
self.dcx()
3733+
.create_err(errors::StructLiteralPlaceholderPath { span: expr.span })
3734+
.stash(expr.span, StashKey::StructLitNoType);
3735+
} else {
3736+
self.dcx()
3737+
.create_err(errors::StructLiteralWithoutPathLate { span: expr.span })
3738+
.stash(expr.span, StashKey::StructLitNoType);
3739+
}
3740+
3741+
Ok(Some(expr))
3742+
} else {
3743+
Ok(None)
3744+
}
3745+
}
3746+
37013747
pub(super) fn parse_struct_fields(
37023748
&mut self,
37033749
pth: ast::Path,

compiler/rustc_resolve/src/late.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5016,6 +5016,21 @@ 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+
// Used delay span error
5025+
if se.path == kw::EmptyStructType {
5026+
if self.r.dcx().has_stashed_diagnostic(expr.span, StashKey::StructLitNoType) {
5027+
return;
5028+
}
5029+
self.r.dcx().span_delayed_bug(expr.span,
5030+
"skipped name resolution for struct literal with empty path but no stashed diagnostic was present",
5031+
);
5032+
}
5033+
50195034
self.smart_resolve_path(expr.id, &se.qself, &se.path, PathSource::Struct(parent));
50205035
// This is the same as `visit::walk_expr(self, expr);`, but we want to pass the
50215036
// parent in for accurate suggestions when encountering `Foo { bar }` that should

compiler/rustc_span/src/symbol.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,8 @@ symbols! {
138138
Union: "union",
139139
Yeet: "yeet",
140140
// tidy-alphabetical-end
141+
142+
EmptyStructType: "empty_struct_type",
141143
}
142144

143145
// Pre-interned symbols that can be referred to with `rustc_span::sym::*`.

tests/ui/parser/bare-struct-body.stderr

Lines changed: 12 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,22 +14,6 @@ LL | val: (),
1414
LL ~ } }
1515
|
1616

17-
error: struct literal body without path
18-
--> $DIR/bare-struct-body.rs:12:13
19-
|
20-
LL | let x = {
21-
| _____________^
22-
LL | | val: (),
23-
LL | | };
24-
| |_____^
25-
|
26-
help: you might have forgotten to add the struct literal inside the block
27-
|
28-
LL ~ let x = { SomeStruct {
29-
LL | val: (),
30-
LL ~ } };
31-
|
32-
3317
error[E0308]: mismatched types
3418
--> $DIR/bare-struct-body.rs:11:14
3519
|
@@ -38,6 +22,18 @@ LL | x.val == 42;
3822
| |
3923
| expected because this is `()`
4024

25+
error: struct literal body without path
26+
--> $DIR/bare-struct-body.rs:12:12
27+
|
28+
LL | let x = {
29+
| ____________^
30+
LL | | val: (),
31+
LL | | };
32+
| | ^
33+
| | |
34+
| |_____absent of struct name for this struct literal
35+
| help: replace it with an appropriate type: `/*Type*/`
36+
4137
error: aborting due to 3 previous errors
4238

4339
For more information about this error, try `rustc --explain E0308`.
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)