Skip to content

Commit be2e80e

Browse files
committed
Recover from struct literals with placeholder path and suggest type from expectation
1 parent 1c580bc commit be2e80e

File tree

9 files changed

+127
-0
lines changed

9 files changed

+127
-0
lines changed

compiler/rustc_errors/src/lib.rs

+1
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ struct DiagCtxtInner {
507507
#[derive(Copy, Clone, PartialEq, Eq, Hash)]
508508
pub enum StashKey {
509509
ItemNoType,
510+
StructLitNoType,
510511
UnderscoreForArrayLengths,
511512
EarlySyntaxWarning,
512513
CallIntoMethod,

compiler/rustc_hir_typeck/src/expr.rs

+34
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,40 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
16401640
fields: &'tcx [hir::ExprField<'tcx>],
16411641
base_expr: &'tcx Option<&'tcx hir::Expr<'tcx>>,
16421642
) -> Ty<'tcx> {
1643+
// FIXME(fmease): Move this into separate method.
1644+
// FIXME(fmease): This doesn't get called given `(_ { x: () }).x` (`hir::Field`).
1645+
// Figure out why.
1646+
if let QPath::Resolved(None, hir::Path { res: Res::Err, segments, .. }) = qpath
1647+
&& let [segment] = segments
1648+
&& segment.ident.name == kw::Empty
1649+
&& let Expectation::ExpectHasType(ty) = expected
1650+
&& ty.is_adt()
1651+
&& let Some(guar) = self.dcx().try_steal_modify_and_emit_err(
1652+
expr.span,
1653+
StashKey::StructLitNoType,
1654+
|err| {
1655+
// The parser provided a sub-optimal `HasPlaceholders` suggestion for the type.
1656+
// We are typeck and have the real type, so remove that and suggest the actual type.
1657+
if let Ok(suggestions) = &mut err.suggestions {
1658+
suggestions.clear();
1659+
}
1660+
1661+
err.span_suggestion(
1662+
qpath.span(),
1663+
// FIXME(fmease): Make this translatable.
1664+
"replace it with the correct type",
1665+
// FIXME(fmease): This doesn't qualify paths within the type appropriately.
1666+
// FIXME(fmease): This doesn't use turbofish when emitting generic args.
1667+
// FIXME(fmease): Make the type suggestable.
1668+
ty.to_string(),
1669+
Applicability::MaybeIncorrect,
1670+
);
1671+
},
1672+
)
1673+
{
1674+
return Ty::new_error(self.tcx, guar);
1675+
}
1676+
16431677
// Find the relevant variant
16441678
let (variant, adt_ty) = match self.check_struct_path(qpath, expr.hir_id) {
16451679
Ok(data) => data,

compiler/rustc_parse/messages.ftl

+5
Original file line numberDiff line numberDiff line change
@@ -708,6 +708,11 @@ parse_struct_literal_needing_parens =
708708
parse_struct_literal_not_allowed_here = struct literals are not allowed here
709709
.suggestion = surround the struct literal with parentheses
710710
711+
parse_struct_literal_placeholder_path =
712+
the placeholder `_` is not allowed for the path in struct literals
713+
.label = not allowed in struct literals
714+
.suggestion = replace it with an appropriate type
715+
711716
parse_suffixed_literal_in_attribute = suffixed literals are not allowed in attributes
712717
.help = instead of using a suffixed literal (`1u8`, `1.0f32`, etc.), use an unsuffixed version (`1`, `1.0`, etc.)
713718

compiler/rustc_parse/src/errors.rs

+9
Original file line numberDiff line numberDiff line change
@@ -2974,3 +2974,12 @@ pub(crate) struct AsyncImpl {
29742974
#[primary_span]
29752975
pub span: Span,
29762976
}
2977+
2978+
#[derive(Diagnostic)]
2979+
#[diag(parse_struct_literal_placeholder_path)]
2980+
pub(crate) struct StructLiteralPlaceholderPath {
2981+
#[primary_span]
2982+
#[label]
2983+
#[suggestion(applicability = "has-placeholders", code = "/*Type*/")]
2984+
pub span: Span,
2985+
}

compiler/rustc_parse/src/parser/diagnostics.rs

+4
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,10 @@ impl<'a> Parser<'a> {
951951
return None;
952952
}
953953
} else {
954+
// FIXME(fmease): Under certain conditions return a struct literal
955+
// here and stash this diagnostic under StructLitNoType.
956+
// FIXME(fmease): Furthermore don't suggest redundant curly braces
957+
// around the potential struct literal if possible.
954958
self.dcx().emit_err(StructLiteralBodyWithoutPath {
955959
span: expr.span,
956960
sugg: StructLiteralBodyWithoutPathSugg {

compiler/rustc_parse/src/parser/expr.rs

+26
Original file line numberDiff line numberDiff line change
@@ -1505,6 +1505,10 @@ impl<'a> Parser<'a> {
15051505
} else if this.check_keyword(kw::Let) {
15061506
this.parse_expr_let(restrictions)
15071507
} else if this.eat_keyword(kw::Underscore) {
1508+
if let Some(expr) = this.maybe_recover_bad_struct_literal_path()? {
1509+
return Ok(expr);
1510+
}
1511+
15081512
Ok(this.mk_expr(this.prev_token.span, ExprKind::Underscore))
15091513
} else if this.token.uninterpolated_span().at_least_rust_2018() {
15101514
// `Span::at_least_rust_2018()` is somewhat expensive; don't get it repeatedly.
@@ -3720,6 +3724,28 @@ impl<'a> Parser<'a> {
37203724
});
37213725
}
37223726

3727+
fn maybe_recover_bad_struct_literal_path(&mut self) -> PResult<'a, Option<P<Expr>>> {
3728+
if self.may_recover()
3729+
&& self.check_noexpect(&token::OpenDelim(Delimiter::Brace))
3730+
&& (!self.restrictions.contains(Restrictions::NO_STRUCT_LITERAL)
3731+
|| self.is_certainly_not_a_block())
3732+
{
3733+
let span = self.prev_token.span;
3734+
self.bump();
3735+
3736+
let expr =
3737+
self.parse_expr_struct(None, Path::from_ident(Ident::new(kw::Empty, span)), false)?;
3738+
3739+
self.dcx()
3740+
.create_err(errors::StructLiteralPlaceholderPath { span: expr.span })
3741+
.stash(expr.span, StashKey::StructLitNoType);
3742+
3743+
Ok(Some(expr))
3744+
} else {
3745+
Ok(None)
3746+
}
3747+
}
3748+
37233749
fn err_dotdotdot_syntax(&self, span: Span) {
37243750
self.dcx().emit_err(errors::DotDotDot { span });
37253751
}

compiler/rustc_resolve/src/late.rs

+7
Original file line numberDiff line numberDiff line change
@@ -4304,6 +4304,13 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> {
43044304
}
43054305

43064306
ExprKind::Struct(ref se) => {
4307+
// FIXME(fmease): Add a comment maybe.
4308+
if se.path == kw::Empty
4309+
&& self.r.dcx().has_stashed_diagnostic(expr.span, StashKey::StructLitNoType)
4310+
{
4311+
return;
4312+
}
4313+
43074314
self.smart_resolve_path(expr.id, &se.qself, &se.path, PathSource::Struct);
43084315
// This is the same as `visit::walk_expr(self, expr);`, but we want to pass the
43094316
// parent in for accurate suggestions when encountering `Foo { bar }` that should
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+
}
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)