Skip to content

Commit

Permalink
Auto merge of #127419 - trevyn:issue-125446, r=fee1-dead
Browse files Browse the repository at this point in the history
Add suggestions for possible missing `fn`, `struct`, or `enum` keywords

Closes #125446
Closes #65381
  • Loading branch information
bors committed Jul 10, 2024
2 parents c092b28 + b40adc9 commit 0c81f94
Show file tree
Hide file tree
Showing 42 changed files with 309 additions and 147 deletions.
19 changes: 12 additions & 7 deletions compiler/rustc_parse/messages.ftl
Original file line number Diff line number Diff line change
@@ -1,9 +1,5 @@
parse_add_paren = try adding parentheses
parse_ambiguous_missing_keyword_for_item_definition = missing `fn` or `struct` for function or struct definition
.suggestion = if you meant to call a macro, try
.help = if you meant to call a macro, remove the `pub` and add a trailing `!` after the identifier
parse_ambiguous_range_pattern = the range pattern here has ambiguous interpretation
.suggestion = add parentheses to clarify the precedence
Expand Down Expand Up @@ -528,14 +524,23 @@ parse_missing_comma_after_match_arm = expected `,` following `match` arm
parse_missing_const_type = missing type for `{$kind}` item
.suggestion = provide a type for the item
parse_missing_enum_for_enum_definition = missing `enum` for enum definition
.suggestion = add `enum` here to parse `{$ident}` as an enum
parse_missing_enum_or_struct_for_item_definition = missing `enum` or `struct` for enum or struct definition
parse_missing_expression_in_for_loop = missing expression to iterate on in `for` loop
.suggestion = try adding an expression to the `for` loop
parse_missing_fn_for_function_definition = missing `fn` for function definition
.suggestion = add `fn` here to parse `{$ident}` as a public function
.suggestion = add `fn` here to parse `{$ident}` as a function
parse_missing_fn_for_method_definition = missing `fn` for method definition
.suggestion = add `fn` here to parse `{$ident}` as a public method
.suggestion = add `fn` here to parse `{$ident}` as a method
parse_missing_fn_or_struct_for_item_definition = missing `fn` or `struct` for function or struct definition
.suggestion = if you meant to call a macro, try
.help = if you meant to call a macro, remove the `pub` and add a trailing `!` after the identifier
parse_missing_fn_params = missing parameters for function definition
.suggestion = add a parameter list
Expand All @@ -555,7 +560,7 @@ parse_missing_semicolon_before_array = expected `;`, found `[`
.suggestion = consider adding `;` here
parse_missing_struct_for_struct_definition = missing `struct` for struct definition
.suggestion = add `struct` here to parse `{$ident}` as a public struct
.suggestion = add `struct` here to parse `{$ident}` as a struct
parse_missing_trait_in_trait_impl = missing trait in a trait impl
.suggestion_add_trait = add a trait here
Expand Down
31 changes: 26 additions & 5 deletions compiler/rustc_parse/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1612,28 +1612,44 @@ pub(crate) struct DefaultNotFollowedByItem {

#[derive(Diagnostic)]
pub(crate) enum MissingKeywordForItemDefinition {
#[diag(parse_missing_enum_for_enum_definition)]
Enum {
#[primary_span]
span: Span,
#[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "enum ")]
insert_span: Span,
ident: Ident,
},
#[diag(parse_missing_enum_or_struct_for_item_definition)]
EnumOrStruct {
#[primary_span]
span: Span,
},
#[diag(parse_missing_struct_for_struct_definition)]
Struct {
#[primary_span]
#[suggestion(style = "short", applicability = "maybe-incorrect", code = " struct ")]
span: Span,
#[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "struct ")]
insert_span: Span,
ident: Ident,
},
#[diag(parse_missing_fn_for_function_definition)]
Function {
#[primary_span]
#[suggestion(style = "short", applicability = "maybe-incorrect", code = " fn ")]
span: Span,
#[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "fn ")]
insert_span: Span,
ident: Ident,
},
#[diag(parse_missing_fn_for_method_definition)]
Method {
#[primary_span]
#[suggestion(style = "short", applicability = "maybe-incorrect", code = " fn ")]
span: Span,
#[suggestion(style = "verbose", applicability = "maybe-incorrect", code = "fn ")]
insert_span: Span,
ident: Ident,
},
#[diag(parse_ambiguous_missing_keyword_for_item_definition)]
#[diag(parse_missing_fn_or_struct_for_item_definition)]
Ambiguous {
#[primary_span]
span: Span,
Expand All @@ -1644,7 +1660,12 @@ pub(crate) enum MissingKeywordForItemDefinition {

#[derive(Subdiagnostic)]
pub(crate) enum AmbiguousMissingKwForItemSub {
#[suggestion(parse_suggestion, applicability = "maybe-incorrect", code = "{snippet}!")]
#[suggestion(
parse_suggestion,
style = "verbose",
applicability = "maybe-incorrect",
code = "{snippet}!"
)]
SuggestMacro {
#[primary_span]
span: Span,
Expand Down
112 changes: 63 additions & 49 deletions compiler/rustc_parse/src/parser/item.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,7 @@ impl<'a> Parser<'a> {
self.recover_const_impl(const_span, attrs, def_())?
} else {
self.recover_const_mut(const_span);
self.recover_missing_kw_before_item()?;
let (ident, generics, ty, expr) = self.parse_const_item()?;
(
ident,
Expand Down Expand Up @@ -311,6 +312,9 @@ impl<'a> Parser<'a> {
Case::Insensitive,
);
} else if macros_allowed && self.check_path() {
if self.isnt_macro_invocation() {
self.recover_missing_kw_before_item()?;
}
// MACRO INVOCATION ITEM
(Ident::empty(), ItemKind::MacCall(P(self.parse_item_macro(vis)?)))
} else {
Expand Down Expand Up @@ -374,25 +378,25 @@ impl<'a> Parser<'a> {
self.check_ident() && self.look_ahead(1, |t| *t != token::Not && *t != token::PathSep)
}

/// Recover on encountering a struct or method definition where the user
/// forgot to add the `struct` or `fn` keyword after writing `pub`: `pub S {}`.
/// Recover on encountering a struct, enum, or method definition where the user
/// forgot to add the `struct`, `enum`, or `fn` keyword
fn recover_missing_kw_before_item(&mut self) -> PResult<'a, ()> {
// Space between `pub` keyword and the identifier
//
// pub S {}
// ^^^ `sp` points here
let sp = self.prev_token.span.between(self.token.span);
let full_sp = self.prev_token.span.to(self.token.span);
let ident_sp = self.token.span;

let ident = if self.look_ahead(1, |t| {
[
token::Lt,
token::OpenDelim(Delimiter::Brace),
token::OpenDelim(Delimiter::Parenthesis),
]
.contains(&t.kind)
}) {
let is_pub = self.prev_token.is_keyword(kw::Pub);
let is_const = self.prev_token.is_keyword(kw::Const);
let ident_span = self.token.span;
let span = if is_pub { self.prev_token.span.to(ident_span) } else { ident_span };
let insert_span = ident_span.shrink_to_lo();

let ident = if (!is_const
|| self.look_ahead(1, |t| *t == token::OpenDelim(Delimiter::Parenthesis)))
&& self.look_ahead(1, |t| {
[
token::Lt,
token::OpenDelim(Delimiter::Brace),
token::OpenDelim(Delimiter::Parenthesis),
]
.contains(&t.kind)
}) {
self.parse_ident().unwrap()
} else {
return Ok(());
Expand All @@ -406,46 +410,56 @@ impl<'a> Parser<'a> {
}

let err = if self.check(&token::OpenDelim(Delimiter::Brace)) {
// possible public struct definition where `struct` was forgotten
Some(errors::MissingKeywordForItemDefinition::Struct { span: sp, ident })
// possible struct or enum definition where `struct` or `enum` was forgotten
if self.look_ahead(1, |t| *t == token::CloseDelim(Delimiter::Brace)) {
// `S {}` could be unit enum or struct
Some(errors::MissingKeywordForItemDefinition::EnumOrStruct { span })
} else if self.look_ahead(2, |t| *t == token::Colon)
|| self.look_ahead(3, |t| *t == token::Colon)
{
// `S { f:` or `S { pub f:`
Some(errors::MissingKeywordForItemDefinition::Struct { span, insert_span, ident })
} else {
Some(errors::MissingKeywordForItemDefinition::Enum { span, insert_span, ident })
}
} else if self.check(&token::OpenDelim(Delimiter::Parenthesis)) {
// possible public function or tuple struct definition where `fn`/`struct` was
// forgotten
// possible function or tuple struct definition where `fn` or `struct` was forgotten
self.bump(); // `(`
let is_method = self.recover_self_param();

self.consume_block(Delimiter::Parenthesis, ConsumeClosingDelim::Yes);

let err =
if self.check(&token::RArrow) || self.check(&token::OpenDelim(Delimiter::Brace)) {
self.eat_to_tokens(&[&token::OpenDelim(Delimiter::Brace)]);
self.bump(); // `{`
self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes);
if is_method {
errors::MissingKeywordForItemDefinition::Method { span: sp, ident }
} else {
errors::MissingKeywordForItemDefinition::Function { span: sp, ident }
}
} else if self.check(&token::Semi) {
errors::MissingKeywordForItemDefinition::Struct { span: sp, ident }
let err = if self.check(&token::RArrow)
|| self.check(&token::OpenDelim(Delimiter::Brace))
{
self.eat_to_tokens(&[&token::OpenDelim(Delimiter::Brace)]);
self.bump(); // `{`
self.consume_block(Delimiter::Brace, ConsumeClosingDelim::Yes);
if is_method {
errors::MissingKeywordForItemDefinition::Method { span, insert_span, ident }
} else {
errors::MissingKeywordForItemDefinition::Ambiguous {
span: sp,
subdiag: if found_generics {
None
} else if let Ok(snippet) = self.span_to_snippet(ident_sp) {
Some(errors::AmbiguousMissingKwForItemSub::SuggestMacro {
span: full_sp,
snippet,
})
} else {
Some(errors::AmbiguousMissingKwForItemSub::HelpMacro)
},
}
};
errors::MissingKeywordForItemDefinition::Function { span, insert_span, ident }
}
} else if is_pub && self.check(&token::Semi) {
errors::MissingKeywordForItemDefinition::Struct { span, insert_span, ident }
} else {
errors::MissingKeywordForItemDefinition::Ambiguous {
span,
subdiag: if found_generics {
None
} else if let Ok(snippet) = self.span_to_snippet(ident_span) {
Some(errors::AmbiguousMissingKwForItemSub::SuggestMacro {
span: ident_span,
snippet,
})
} else {
Some(errors::AmbiguousMissingKwForItemSub::HelpMacro)
},
}
};
Some(err)
} else if found_generics {
Some(errors::MissingKeywordForItemDefinition::Ambiguous { span: sp, subdiag: None })
Some(errors::MissingKeywordForItemDefinition::Ambiguous { span, subdiag: None })
} else {
None
};
Expand Down
4 changes: 2 additions & 2 deletions tests/ui/did_you_mean/issue-40006.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@ impl dyn A {
struct S;

trait X {
X() {} //~ ERROR expected one of `!` or `::`, found `(`
X() {} //~ ERROR missing `fn` for function definition
fn xxx() { ### }
L = M;
Z = { 2 + 3 };
::Y ();
}

trait A {
X() {} //~ ERROR expected one of `!` or `::`, found `(`
X() {} //~ ERROR missing `fn` for function definition
}
trait B {
fn xxx() { ### } //~ ERROR expected
Expand Down
28 changes: 19 additions & 9 deletions tests/ui/did_you_mean/issue-40006.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,36 @@ LL | }
| unexpected token
| the item list ends here

error: expected one of `!` or `::`, found `(`
--> $DIR/issue-40006.rs:8:6
error: missing `fn` for function definition
--> $DIR/issue-40006.rs:8:5
|
LL | trait X {
| - while parsing this item list starting here
LL | X() {}
| ^ expected one of `!` or `::`
| ^
...
LL | }
| - the item list ends here
|
help: add `fn` here to parse `X` as a function
|
LL | fn X() {}
| ++

error: expected one of `!` or `::`, found `(`
--> $DIR/issue-40006.rs:16:6
error: missing `fn` for function definition
--> $DIR/issue-40006.rs:16:5
|
LL | trait A {
| - while parsing this item list starting here
LL | X() {}
| ^ expected one of `!` or `::`
| ^
LL | }
| - the item list ends here
|
help: add `fn` here to parse `X` as a function
|
LL | fn X() {}
| ++

error: expected one of `!` or `[`, found `#`
--> $DIR/issue-40006.rs:19:17
Expand Down Expand Up @@ -69,17 +79,17 @@ LL | }
| - the item list ends here

error: missing `fn` for method definition
--> $DIR/issue-40006.rs:32:8
--> $DIR/issue-40006.rs:32:5
|
LL | impl S {
| - while parsing this item list starting here
LL | pub hello_method(&self) {
| ^
| ^^^^^^^^^^^^^^^^
...
LL | }
| - the item list ends here
|
help: add `fn` here to parse `hello_method` as a public method
help: add `fn` here to parse `hello_method` as a method
|
LL | pub fn hello_method(&self) {
| ++
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/mismatched_types/recovered-block.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ pub fn foo() -> Foo {

pub Foo { text }
}
//~^^ ERROR missing `struct` for struct definition
//~^^ ERROR missing `enum` for enum definition

fn main() {}
12 changes: 6 additions & 6 deletions tests/ui/mismatched_types/recovered-block.stderr
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
error: missing `struct` for struct definition
--> $DIR/recovered-block.rs:11:8
error: missing `enum` for enum definition
--> $DIR/recovered-block.rs:11:5
|
LL | pub Foo { text }
| ^
| ^^^^^^^
|
help: add `struct` here to parse `Foo` as a public struct
help: add `enum` here to parse `Foo` as an enum
|
LL | pub struct Foo { text }
| ++++++
LL | pub enum Foo { text }
| ++++

error: aborting due to 1 previous error

4 changes: 3 additions & 1 deletion tests/ui/parser/extern-no-fn.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
extern "C" {
f(); //~ ERROR expected one of `!` or `::`, found `(`
f();
//~^ ERROR missing `fn` or `struct` for function or struct definition
//~| HELP if you meant to call a macro, try
}

fn main() {
Expand Down
12 changes: 9 additions & 3 deletions tests/ui/parser/extern-no-fn.stderr
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
error: expected one of `!` or `::`, found `(`
--> $DIR/extern-no-fn.rs:2:6
error: missing `fn` or `struct` for function or struct definition
--> $DIR/extern-no-fn.rs:2:5
|
LL | extern "C" {
| - while parsing this item list starting here
LL | f();
| ^ expected one of `!` or `::`
| ^
...
LL | }
| - the item list ends here
|
help: if you meant to call a macro, try
|
LL | f!();
| ~~

error: aborting due to 1 previous error

Loading

0 comments on commit 0c81f94

Please sign in to comment.