Skip to content

Commit eb36bc6

Browse files
authored
Rollup merge of #76808 - LeSeulArtichaut:diagnose-functions-struct, r=jackh726
Improve diagnostics for functions in `struct` definitions Tries to implement #76421. This is probably going to need unit tests, but I wanted to hear from review all the cases tests should cover. I'd like to follow up with the "mechanically applicable suggestion here that adds an impl block" step, but I'd need guidance. My idea for now would be to try to parse a function, and if that succeeds, create a dummy `ast::Item` impl block to then format it using `pprust`. Would that be a viable approach? Is there a better alternative? r? `@matklad` cc `@estebank`
2 parents 770792f + 6717f81 commit eb36bc6

File tree

4 files changed

+120
-30
lines changed

4 files changed

+120
-30
lines changed

compiler/rustc_parse/src/parser/item.rs

+40-12
Original file line numberDiff line numberDiff line change
@@ -1124,11 +1124,11 @@ impl<'a> Parser<'a> {
11241124
if !this.recover_nested_adt_item(kw::Enum)? {
11251125
return Ok((None, TrailingToken::None));
11261126
}
1127-
let ident = this.parse_ident()?;
1127+
let ident = this.parse_field_ident("enum", vlo)?;
11281128

11291129
let struct_def = if this.check(&token::OpenDelim(token::Brace)) {
11301130
// Parse a struct variant.
1131-
let (fields, recovered) = this.parse_record_struct_body()?;
1131+
let (fields, recovered) = this.parse_record_struct_body("struct")?;
11321132
VariantData::Struct(fields, recovered)
11331133
} else if this.check(&token::OpenDelim(token::Paren)) {
11341134
VariantData::Tuple(this.parse_tuple_struct_body()?, DUMMY_NODE_ID)
@@ -1182,15 +1182,15 @@ impl<'a> Parser<'a> {
11821182
VariantData::Unit(DUMMY_NODE_ID)
11831183
} else {
11841184
// If we see: `struct Foo<T> where T: Copy { ... }`
1185-
let (fields, recovered) = self.parse_record_struct_body()?;
1185+
let (fields, recovered) = self.parse_record_struct_body("struct")?;
11861186
VariantData::Struct(fields, recovered)
11871187
}
11881188
// No `where` so: `struct Foo<T>;`
11891189
} else if self.eat(&token::Semi) {
11901190
VariantData::Unit(DUMMY_NODE_ID)
11911191
// Record-style struct definition
11921192
} else if self.token == token::OpenDelim(token::Brace) {
1193-
let (fields, recovered) = self.parse_record_struct_body()?;
1193+
let (fields, recovered) = self.parse_record_struct_body("struct")?;
11941194
VariantData::Struct(fields, recovered)
11951195
// Tuple-style struct definition with optional where-clause.
11961196
} else if self.token == token::OpenDelim(token::Paren) {
@@ -1220,10 +1220,10 @@ impl<'a> Parser<'a> {
12201220

12211221
let vdata = if self.token.is_keyword(kw::Where) {
12221222
generics.where_clause = self.parse_where_clause()?;
1223-
let (fields, recovered) = self.parse_record_struct_body()?;
1223+
let (fields, recovered) = self.parse_record_struct_body("union")?;
12241224
VariantData::Struct(fields, recovered)
12251225
} else if self.token == token::OpenDelim(token::Brace) {
1226-
let (fields, recovered) = self.parse_record_struct_body()?;
1226+
let (fields, recovered) = self.parse_record_struct_body("union")?;
12271227
VariantData::Struct(fields, recovered)
12281228
} else {
12291229
let token_str = super::token_descr(&self.token);
@@ -1236,12 +1236,15 @@ impl<'a> Parser<'a> {
12361236
Ok((class_name, ItemKind::Union(vdata, generics)))
12371237
}
12381238

1239-
fn parse_record_struct_body(&mut self) -> PResult<'a, (Vec<FieldDef>, /* recovered */ bool)> {
1239+
fn parse_record_struct_body(
1240+
&mut self,
1241+
adt_ty: &str,
1242+
) -> PResult<'a, (Vec<FieldDef>, /* recovered */ bool)> {
12401243
let mut fields = Vec::new();
12411244
let mut recovered = false;
12421245
if self.eat(&token::OpenDelim(token::Brace)) {
12431246
while self.token != token::CloseDelim(token::Brace) {
1244-
let field = self.parse_field_def().map_err(|e| {
1247+
let field = self.parse_field_def(adt_ty).map_err(|e| {
12451248
self.consume_block(token::Brace, ConsumeClosingDelim::No);
12461249
recovered = true;
12471250
e
@@ -1294,24 +1297,25 @@ impl<'a> Parser<'a> {
12941297
}
12951298

12961299
/// Parses an element of a struct declaration.
1297-
fn parse_field_def(&mut self) -> PResult<'a, FieldDef> {
1300+
fn parse_field_def(&mut self, adt_ty: &str) -> PResult<'a, FieldDef> {
12981301
let attrs = self.parse_outer_attributes()?;
12991302
self.collect_tokens_trailing_token(attrs, ForceCollect::No, |this, attrs| {
13001303
let lo = this.token.span;
13011304
let vis = this.parse_visibility(FollowedByType::No)?;
1302-
Ok((this.parse_single_struct_field(lo, vis, attrs)?, TrailingToken::None))
1305+
Ok((this.parse_single_struct_field(adt_ty, lo, vis, attrs)?, TrailingToken::None))
13031306
})
13041307
}
13051308

13061309
/// Parses a structure field declaration.
13071310
fn parse_single_struct_field(
13081311
&mut self,
1312+
adt_ty: &str,
13091313
lo: Span,
13101314
vis: Visibility,
13111315
attrs: Vec<Attribute>,
13121316
) -> PResult<'a, FieldDef> {
13131317
let mut seen_comma: bool = false;
1314-
let a_var = self.parse_name_and_ty(lo, vis, attrs)?;
1318+
let a_var = self.parse_name_and_ty(adt_ty, lo, vis, attrs)?;
13151319
if self.token == token::Comma {
13161320
seen_comma = true;
13171321
}
@@ -1398,11 +1402,12 @@ impl<'a> Parser<'a> {
13981402
/// Parses a structure field.
13991403
fn parse_name_and_ty(
14001404
&mut self,
1405+
adt_ty: &str,
14011406
lo: Span,
14021407
vis: Visibility,
14031408
attrs: Vec<Attribute>,
14041409
) -> PResult<'a, FieldDef> {
1405-
let name = self.parse_ident_common(false)?;
1410+
let name = self.parse_field_ident(adt_ty, lo)?;
14061411
self.expect(&token::Colon)?;
14071412
let ty = self.parse_ty()?;
14081413
Ok(FieldDef {
@@ -1416,6 +1421,29 @@ impl<'a> Parser<'a> {
14161421
})
14171422
}
14181423

1424+
/// Parses a field identifier. Specialized version of `parse_ident_common`
1425+
/// for better diagnostics and suggestions.
1426+
fn parse_field_ident(&mut self, adt_ty: &str, lo: Span) -> PResult<'a, Ident> {
1427+
let (ident, is_raw) = self.ident_or_err()?;
1428+
if !is_raw && ident.is_reserved() {
1429+
let err = if self.check_fn_front_matter(false) {
1430+
let _ = self.parse_fn(&mut Vec::new(), |_| true, lo);
1431+
let mut err = self.struct_span_err(
1432+
lo.to(self.prev_token.span),
1433+
&format!("functions are not allowed in {} definitions", adt_ty),
1434+
);
1435+
err.help("unlike in C++, Java, and C#, functions are declared in `impl` blocks");
1436+
err.help("see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information");
1437+
err
1438+
} else {
1439+
self.expected_ident_found()
1440+
};
1441+
return Err(err);
1442+
}
1443+
self.bump();
1444+
Ok(ident)
1445+
}
1446+
14191447
/// Parses a declarative macro 2.0 definition.
14201448
/// The `macro` keyword has already been parsed.
14211449
/// ```

compiler/rustc_parse/src/parser/mod.rs

+18-18
Original file line numberDiff line numberDiff line change
@@ -522,27 +522,27 @@ impl<'a> Parser<'a> {
522522
self.parse_ident_common(true)
523523
}
524524

525+
fn ident_or_err(&mut self) -> PResult<'a, (Ident, /* is_raw */ bool)> {
526+
self.token.ident().ok_or_else(|| match self.prev_token.kind {
527+
TokenKind::DocComment(..) => {
528+
self.span_fatal_err(self.prev_token.span, Error::UselessDocComment)
529+
}
530+
_ => self.expected_ident_found(),
531+
})
532+
}
533+
525534
fn parse_ident_common(&mut self, recover: bool) -> PResult<'a, Ident> {
526-
match self.token.ident() {
527-
Some((ident, is_raw)) => {
528-
if !is_raw && ident.is_reserved() {
529-
let mut err = self.expected_ident_found();
530-
if recover {
531-
err.emit();
532-
} else {
533-
return Err(err);
534-
}
535-
}
536-
self.bump();
537-
Ok(ident)
535+
let (ident, is_raw) = self.ident_or_err()?;
536+
if !is_raw && ident.is_reserved() {
537+
let mut err = self.expected_ident_found();
538+
if recover {
539+
err.emit();
540+
} else {
541+
return Err(err);
538542
}
539-
_ => Err(match self.prev_token.kind {
540-
TokenKind::DocComment(..) => {
541-
self.span_fatal_err(self.prev_token.span, Error::UselessDocComment)
542-
}
543-
_ => self.expected_ident_found(),
544-
}),
545543
}
544+
self.bump();
545+
Ok(ident)
546546
}
547547

548548
/// Checks if the next token is `tok`, and returns `true` if so.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
// It might be intuitive for a user coming from languages like Java
2+
// to declare a method directly in a struct's definition. Make sure
3+
// rustc can give a helpful suggestion.
4+
// Suggested in issue #76421
5+
6+
struct S {
7+
field: usize,
8+
9+
fn foo() {}
10+
//~^ ERROR functions are not allowed in struct definitions
11+
//~| HELP unlike in C++, Java, and C#, functions are declared in `impl` blocks
12+
//~| HELP see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information
13+
}
14+
15+
union U {
16+
variant: usize,
17+
18+
fn foo() {}
19+
//~^ ERROR functions are not allowed in union definitions
20+
//~| HELP unlike in C++, Java, and C#, functions are declared in `impl` blocks
21+
//~| HELP see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information
22+
}
23+
24+
enum E {
25+
Variant,
26+
27+
fn foo() {}
28+
//~^ ERROR functions are not allowed in enum definitions
29+
//~| HELP unlike in C++, Java, and C#, functions are declared in `impl` blocks
30+
//~| HELP see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information
31+
}
32+
33+
fn main() {}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
error: functions are not allowed in struct definitions
2+
--> $DIR/struct-fn-in-definition.rs:9:5
3+
|
4+
LL | fn foo() {}
5+
| ^^^^^^^^^^^
6+
|
7+
= help: unlike in C++, Java, and C#, functions are declared in `impl` blocks
8+
= help: see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information
9+
10+
error: functions are not allowed in union definitions
11+
--> $DIR/struct-fn-in-definition.rs:18:5
12+
|
13+
LL | fn foo() {}
14+
| ^^^^^^^^^^^
15+
|
16+
= help: unlike in C++, Java, and C#, functions are declared in `impl` blocks
17+
= help: see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information
18+
19+
error: functions are not allowed in enum definitions
20+
--> $DIR/struct-fn-in-definition.rs:27:5
21+
|
22+
LL | fn foo() {}
23+
| ^^^^^^^^^^^
24+
|
25+
= help: unlike in C++, Java, and C#, functions are declared in `impl` blocks
26+
= help: see https://doc.rust-lang.org/book/ch05-03-method-syntax.html for more information
27+
28+
error: aborting due to 3 previous errors
29+

0 commit comments

Comments
 (0)