Rollup merge of #65640 - estebank:recover-missing-semi, r=Centril

Use heuristics to recover parsing of missing `;`

- Detect `,` and `:` typos where `;` was intended.
- When the next token could have been the start of a new statement,
  detect a missing semicolon.

Fix #48160, fix #44767 (after adding note about statements).
This commit is contained in:
Mazdak Farrokhzad 2019-10-28 21:35:58 +01:00 committed by GitHub
commit 2fe6f22dea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
36 changed files with 175 additions and 157 deletions

View file

@ -6,7 +6,7 @@ use crate::ast::{
self, Param, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item, ItemKind,
Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind,
};
use crate::parse::token::{self, TokenKind};
use crate::parse::token::{self, TokenKind, token_can_begin_expr};
use crate::print::pprust;
use crate::ptr::P;
use crate::symbol::{kw, sym};
@ -274,23 +274,23 @@ impl<'a> Parser<'a> {
expected.sort_by_cached_key(|x| x.to_string());
expected.dedup();
let expect = tokens_to_string(&expected[..]);
let actual = self.this_token_to_string();
let actual = self.this_token_descr();
let (msg_exp, (label_sp, label_exp)) = if expected.len() > 1 {
let short_expect = if expected.len() > 6 {
format!("{} possible tokens", expected.len())
} else {
expect.clone()
};
(format!("expected one of {}, found `{}`", expect, actual),
(format!("expected one of {}, found {}", expect, actual),
(self.sess.source_map().next_point(self.prev_span),
format!("expected one of {} here", short_expect)))
} else if expected.is_empty() {
(format!("unexpected token: `{}`", actual),
(format!("unexpected token: {}", actual),
(self.prev_span, "unexpected token after this".to_string()))
} else {
(format!("expected {}, found `{}`", expect, actual),
(format!("expected {}, found {}", expect, actual),
(self.sess.source_map().next_point(self.prev_span),
format!("expected {} here", expect)))
format!("expected {}", expect)))
};
self.last_unexpected_token_span = Some(self.token.span);
let mut err = self.fatal(&msg_exp);
@ -326,58 +326,28 @@ impl<'a> Parser<'a> {
}
}
let is_semi_suggestable = expected.iter().any(|t| match t {
TokenType::Token(token::Semi) => true, // We expect a `;` here.
_ => false,
}) && ( // A `;` would be expected before the current keyword.
self.token.is_keyword(kw::Break) ||
self.token.is_keyword(kw::Continue) ||
self.token.is_keyword(kw::For) ||
self.token.is_keyword(kw::If) ||
self.token.is_keyword(kw::Let) ||
self.token.is_keyword(kw::Loop) ||
self.token.is_keyword(kw::Match) ||
self.token.is_keyword(kw::Return) ||
self.token.is_keyword(kw::While)
);
let sm = self.sess.source_map();
match (sm.lookup_line(self.token.span.lo()), sm.lookup_line(sp.lo())) {
(Ok(ref a), Ok(ref b)) if a.line != b.line && is_semi_suggestable => {
// The spans are in different lines, expected `;` and found `let` or `return`.
// High likelihood that it is only a missing `;`.
err.span_suggestion_short(
label_sp,
"a semicolon may be missing here",
";".to_string(),
Applicability::MaybeIncorrect,
);
err.emit();
return Ok(true);
}
(Ok(ref a), Ok(ref b)) if a.line == b.line => {
// When the spans are in the same line, it means that the only content between
// them is whitespace, point at the found token in that case:
//
// X | () => { syntax error };
// | ^^^^^ expected one of 8 possible tokens here
//
// instead of having:
//
// X | () => { syntax error };
// | -^^^^^ unexpected token
// | |
// | expected one of 8 possible tokens here
err.span_label(self.token.span, label_exp);
}
_ if self.prev_span == syntax_pos::DUMMY_SP => {
// Account for macro context where the previous span might not be
// available to avoid incorrect output (#54841).
err.span_label(self.token.span, "unexpected token");
}
_ => {
err.span_label(sp, label_exp);
err.span_label(self.token.span, "unexpected token");
}
if self.prev_span == DUMMY_SP {
// Account for macro context where the previous span might not be
// available to avoid incorrect output (#54841).
err.span_label(self.token.span, label_exp);
} else if !sm.is_multiline(self.token.span.shrink_to_hi().until(sp.shrink_to_lo())) {
// When the spans are in the same line, it means that the only content between
// them is whitespace, point at the found token in that case:
//
// X | () => { syntax error };
// | ^^^^^ expected one of 8 possible tokens here
//
// instead of having:
//
// X | () => { syntax error };
// | -^^^^^ unexpected token
// | |
// | expected one of 8 possible tokens here
err.span_label(self.token.span, label_exp);
} else {
err.span_label(sp, label_exp);
err.span_label(self.token.span, "unexpected token");
}
self.maybe_annotate_with_ascription(&mut err, false);
Err(err)
@ -902,20 +872,64 @@ impl<'a> Parser<'a> {
}
}
let sm = self.sess.source_map();
match (sm.lookup_line(prev_sp.lo()), sm.lookup_line(sp.lo())) {
(Ok(ref a), Ok(ref b)) if a.line == b.line => {
// When the spans are in the same line, it means that the only content
// between them is whitespace, point only at the found token.
err.span_label(sp, label_exp);
}
_ => {
err.span_label(prev_sp, label_exp);
err.span_label(sp, "unexpected token");
}
if !sm.is_multiline(prev_sp.until(sp)) {
// When the spans are in the same line, it means that the only content
// between them is whitespace, point only at the found token.
err.span_label(sp, label_exp);
} else {
err.span_label(prev_sp, label_exp);
err.span_label(sp, "unexpected token");
}
Err(err)
}
pub(super) fn expect_semi(&mut self) -> PResult<'a, ()> {
if self.eat(&token::Semi) {
return Ok(());
}
let sm = self.sess.source_map();
let msg = format!("expected `;`, found `{}`", self.this_token_descr());
let appl = Applicability::MachineApplicable;
if self.token.span == DUMMY_SP || self.prev_span == DUMMY_SP {
// Likely inside a macro, can't provide meaninful suggestions.
return self.expect(&token::Semi).map(|_| ());
} else if !sm.is_multiline(self.prev_span.until(self.token.span)) {
// The current token is in the same line as the prior token, not recoverable.
} else if self.look_ahead(1, |t| t == &token::CloseDelim(token::Brace)
|| token_can_begin_expr(t) && t.kind != token::Colon
) && [token::Comma, token::Colon].contains(&self.token.kind) {
// Likely typo: `,` → `;` or `:` → `;`. This is triggered if the current token is
// either `,` or `:`, and the next token could either start a new statement or is a
// block close. For example:
//
// let x = 32:
// let y = 42;
self.bump();
let sp = self.prev_span;
self.struct_span_err(sp, &msg)
.span_suggestion(sp, "change this to `;`", ";".to_string(), appl)
.emit();
return Ok(())
} else if self.look_ahead(0, |t| t == &token::CloseDelim(token::Brace) || (
token_can_begin_expr(t)
&& t != &token::Semi
&& t != &token::Pound // Avoid triggering with too many trailing `#` in raw string.
)) {
// Missing semicolon typo. This is triggered if the next token could either start a
// new statement or is a block close. For example:
//
// let x = 32
// let y = 42;
let sp = self.prev_span.shrink_to_hi();
self.struct_span_err(sp, &msg)
.span_label(self.token.span, "unexpected token")
.span_suggestion_short(sp, "add `;` here", ";".to_string(), appl)
.emit();
return Ok(())
}
self.expect(&token::Semi).map(|_| ()) // Error unconditionally
}
pub(super) fn parse_semi_or_incorrect_foreign_fn_body(
&mut self,
ident: &Ident,
@ -943,7 +957,7 @@ impl<'a> Parser<'a> {
Err(mut err) => {
err.cancel();
mem::replace(self, parser_snapshot);
self.expect(&token::Semi)?;
self.expect_semi()?;
}
}
} else {

View file

@ -98,7 +98,7 @@ impl<'a> Parser<'a> {
if self.eat_keyword(kw::Use) {
// USE ITEM
let item_ = ItemKind::Use(P(self.parse_use_tree()?));
self.expect(&token::Semi)?;
self.expect_semi()?;
let span = lo.to(self.prev_span);
let item = self.mk_item(span, Ident::invalid(), item_, vis, attrs);
@ -526,7 +526,7 @@ impl<'a> Parser<'a> {
// eat a matched-delimiter token tree:
let (delim, tts) = self.expect_delimited_token_tree()?;
if delim != MacDelimiter::Brace {
self.expect(&token::Semi)?;
self.expect_semi()?;
}
Ok(Some(Mac {
@ -776,7 +776,7 @@ impl<'a> Parser<'a> {
let typ = self.parse_ty()?;
self.expect(&token::Eq)?;
let expr = self.parse_expr()?;
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok((name, ImplItemKind::Const(typ, expr), Generics::default()))
}
@ -813,7 +813,7 @@ impl<'a> Parser<'a> {
let bounds = self.parse_generic_bounds(None)?;
tps.where_clause = self.parse_where_clause()?;
self.expect(&token::Semi)?;
self.expect_semi()?;
let whole_span = lo.to(self.prev_span);
if is_auto == IsAuto::Yes {
@ -927,7 +927,7 @@ impl<'a> Parser<'a> {
} else {
None
};
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok((ident, TraitItemKind::Const(ty, default), Generics::default()))
}
@ -951,7 +951,7 @@ impl<'a> Parser<'a> {
} else {
None
};
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok((ident, TraitItemKind::Type(bounds, default), generics))
}
@ -1054,7 +1054,7 @@ impl<'a> Parser<'a> {
} else {
(orig_name, None)
};
self.expect(&token::Semi)?;
self.expect_semi()?;
let span = lo.to(self.prev_span);
Ok(self.mk_item(span, item_name, ItemKind::ExternCrate(orig_name), visibility, attrs))
@ -1217,7 +1217,7 @@ impl<'a> Parser<'a> {
self.expect(&token::Colon)?;
let ty = self.parse_ty()?;
let hi = self.token.span;
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok(ForeignItem {
ident,
attrs,
@ -1235,7 +1235,7 @@ impl<'a> Parser<'a> {
let ident = self.parse_ident()?;
let hi = self.token.span;
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok(ast::ForeignItem {
ident,
attrs,
@ -1282,7 +1282,7 @@ impl<'a> Parser<'a> {
self.expect(&token::Eq)?;
let e = self.parse_expr()?;
self.expect(&token::Semi)?;
self.expect_semi()?;
let item = match m {
Some(m) => ItemKind::Static(ty, m, e),
None => ItemKind::Const(ty, e),
@ -1344,7 +1344,7 @@ impl<'a> Parser<'a> {
let ty = self.parse_ty()?;
AliasKind::Weak(ty)
};
self.expect(&token::Semi)?;
self.expect_semi()?;
Ok((ident, alias, tps))
}
@ -1468,7 +1468,7 @@ impl<'a> Parser<'a> {
} else if self.token == token::OpenDelim(token::Paren) {
let body = VariantData::Tuple(self.parse_tuple_struct_body()?, DUMMY_NODE_ID);
generics.where_clause = self.parse_where_clause()?;
self.expect(&token::Semi)?;
self.expect_semi()?;
body
} else {
let token_str = self.this_token_descr();

View file

@ -432,6 +432,7 @@ impl<'a> Parser<'a> {
None => return Ok(None),
};
let mut eat_semi = true;
match stmt.kind {
StmtKind::Expr(ref expr) if self.token != token::Eof => {
// expression without semicolon
@ -453,13 +454,14 @@ impl<'a> Parser<'a> {
if macro_legacy_warnings && self.token != token::Semi {
self.warn_missing_semicolon();
} else {
self.expect_one_of(&[], &[token::Semi])?;
self.expect_semi()?;
eat_semi = false;
}
}
_ => {}
}
if self.eat(&token::Semi) {
if eat_semi && self.eat(&token::Semi) {
stmt = stmt.add_trailing_semicolon();
}
stmt.span = stmt.span.to(self.prev_span);

View file

@ -143,34 +143,35 @@ impl Lit {
pub(crate) fn ident_can_begin_expr(name: ast::Name, span: Span, is_raw: bool) -> bool {
let ident_token = Token::new(Ident(name, is_raw), span);
token_can_begin_expr(&ident_token)
}
pub(crate) fn token_can_begin_expr(ident_token: &Token) -> bool {
!ident_token.is_reserved_ident() ||
ident_token.is_path_segment_keyword() ||
[
kw::Async,
// FIXME: remove when `await!(..)` syntax is removed
// https://github.com/rust-lang/rust/issues/60610
kw::Await,
kw::Do,
kw::Box,
kw::Break,
kw::Continue,
kw::False,
kw::For,
kw::If,
kw::Let,
kw::Loop,
kw::Match,
kw::Move,
kw::Return,
kw::True,
kw::Unsafe,
kw::While,
kw::Yield,
kw::Static,
].contains(&name)
match ident_token.kind {
TokenKind::Ident(ident, _) => [
kw::Async,
kw::Do,
kw::Box,
kw::Break,
kw::Continue,
kw::False,
kw::For,
kw::If,
kw::Let,
kw::Loop,
kw::Match,
kw::Move,
kw::Return,
kw::True,
kw::Unsafe,
kw::While,
kw::Yield,
kw::Static,
].contains(&ident),
_=> false,
}
}
fn ident_can_begin_type(name: ast::Name, span: Span, is_raw: bool) -> bool {

View file

@ -3,4 +3,4 @@
// compile-flags: --crate-type lib
pub async const fn x() {}
//~^ ERROR expected one of `fn` or `unsafe`, found `const`
//~^ ERROR expected one of `fn` or `unsafe`, found keyword `const`

View file

@ -1,4 +1,4 @@
error: expected one of `fn` or `unsafe`, found `const`
error: expected one of `fn` or `unsafe`, found keyword `const`
--> $DIR/no-async-const.rs:5:11
|
LL | pub async const fn x() {}

View file

@ -4,8 +4,8 @@ struct S;
impl S {
#[cfg(FALSE)]
unsafe async fn g() {} //~ ERROR expected one of `extern` or `fn`, found `async`
unsafe async fn g() {} //~ ERROR expected one of `extern` or `fn`, found keyword `async`
}
#[cfg(FALSE)]
unsafe async fn f() {} //~ ERROR expected one of `extern`, `fn`, or `{`, found `async`
unsafe async fn f() {} //~ ERROR expected one of `extern`, `fn`, or `{`, found keyword `async`

View file

@ -1,10 +1,10 @@
error: expected one of `extern` or `fn`, found `async`
error: expected one of `extern` or `fn`, found keyword `async`
--> $DIR/no-unsafe-async.rs:7:12
|
LL | unsafe async fn g() {}
| ^^^^^ expected one of `extern` or `fn` here
error: expected one of `extern`, `fn`, or `{`, found `async`
error: expected one of `extern`, `fn`, or `{`, found keyword `async`
--> $DIR/no-unsafe-async.rs:11:8
|
LL | unsafe async fn f() {}

View file

@ -16,5 +16,5 @@ pub fn main() {
return break as ();
}
return enum; //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found `enum`
return enum; //~ ERROR expected one of `.`, `;`, `?`, `}`, or an operator, found keyword `enum`
}

View file

@ -1,4 +1,4 @@
error: expected one of `.`, `;`, `?`, `}`, or an operator, found `enum`
error: expected one of `.`, `;`, `?`, `}`, or an operator, found keyword `enum`
--> $DIR/can-begin-expr-check.rs:19:12
|
LL | return enum;

View file

@ -2,7 +2,7 @@ error: expected `|`, found `}`
--> $DIR/issue-43196.rs:3:1
|
LL | |
| - expected `|` here
| - expected `|`
LL | }
| ^ unexpected token

View file

@ -2,7 +2,7 @@ error: expected `fn`, found `::`
--> $DIR/keyword-extern-as-identifier-type.rs:1:16
|
LL | type A = extern::foo::bar;
| ^^ expected `fn` here
| ^^ expected `fn`
error: aborting due to previous error

View file

@ -1,8 +1,8 @@
error: expected one of `crate`, `fn`, `pub`, `static`, or `type`, found `let`
error: expected one of `crate`, `fn`, `pub`, `static`, or `type`, found keyword `let`
--> $DIR/issue-54441.rs:3:9
|
LL | let
| ^^^ unexpected token
| ^^^ expected one of `crate`, `fn`, `pub`, `static`, or `type` here
...
LL | m!();
| ----- in this macro invocation

View file

@ -1,4 +1,4 @@
error: expected one of `async`, `const`, `extern`, `fn`, `type`, or `unsafe`, found `pub`
error: expected one of `async`, `const`, `extern`, `fn`, `type`, or `unsafe`, found keyword `pub`
--> $DIR/default.rs:22:13
|
LL | default pub fn foo<T: Default>() -> T { T::default() }

View file

@ -1,4 +1,4 @@
error: expected one of `(`, `fn`, `static`, or `type`, found `pub`
error: expected one of `(`, `fn`, `static`, or `type`, found keyword `pub`
--> $DIR/duplicate-visibility.rs:3:9
|
LL | pub pub fn foo();

View file

@ -1,4 +1,4 @@
// Verifies that the expected token errors for `extern crate` are
// raised
extern "C" mod foo; //~ERROR expected one of `fn` or `{`, found `mod`
extern "C" mod foo; //~ERROR expected one of `fn` or `{`, found keyword `mod`

View file

@ -1,4 +1,4 @@
error: expected one of `fn` or `{`, found `mod`
error: expected one of `fn` or `{`, found keyword `mod`
--> $DIR/extern-expected-fn-or-brace.rs:4:12
|
LL | extern "C" mod foo;

View file

@ -26,7 +26,7 @@ error: expected `impl`, found `FAIL`
--> $DIR/impl-parsing.rs:11:16
|
LL | default unsafe FAIL
| ^^^^ expected `impl` here
| ^^^^ expected `impl`
error: aborting due to 5 previous errors

View file

@ -11,7 +11,7 @@ fn main(){
}
//~^ NOTE expected one of `.`, `=>`, `?`, or an operator here
_ => {}
//~^ ERROR expected one of `.`, `=>`, `?`, or an operator, found `_`
//~^ ERROR expected one of `.`, `=>`, `?`, or an operator, found reserved identifier `_`
//~| NOTE unexpected token
}
}

View file

@ -12,7 +12,7 @@ help: you can escape reserved keywords to use them as identifiers
LL | r#return
|
error: expected one of `.`, `=>`, `?`, or an operator, found `_`
error: expected one of `.`, `=>`, `?`, or an operator, found reserved identifier `_`
--> $DIR/issue-15980.rs:13:9
|
LL | }

View file

@ -1,5 +1,5 @@
trait T {
extern "Rust" unsafe fn foo(); //~ ERROR expected `fn`, found `unsafe`
extern "Rust" unsafe fn foo(); //~ ERROR expected `fn`, found keyword `unsafe`
}
fn main() {}

View file

@ -1,8 +1,8 @@
error: expected `fn`, found `unsafe`
error: expected `fn`, found keyword `unsafe`
--> $DIR/issue-19398.rs:2:19
|
LL | extern "Rust" unsafe fn foo();
| ^^^^^^ expected `fn` here
| ^^^^^^ expected `fn`
error: aborting due to previous error

View file

@ -2,5 +2,5 @@
fn main()
{
let x = 3
} //~ ERROR: expected one of `.`, `;`, `?`, or an operator, found `}`
let x = 3 //~ ERROR: expected `;`
}

View file

@ -1,10 +1,10 @@
error: expected one of `.`, `;`, `?`, or an operator, found `}`
--> $DIR/issue-3036.rs:6:1
error: expected `;`, found ``}``
--> $DIR/issue-3036.rs:5:14
|
LL | let x = 3
| - expected one of `.`, `;`, `?`, or an operator here
| ^ help: add `;` here
LL | }
| ^ unexpected token
| - unexpected token
error: aborting due to previous error

View file

@ -2,7 +2,7 @@ error: expected one of `async`, `const`, `extern`, `fn`, `type`, or `unsafe`, fo
--> $DIR/trait-non-item-macros.rs:2:19
|
LL | ($a:expr) => ($a)
| ^^ unexpected token
| ^^ expected one of `async`, `const`, `extern`, `fn`, `type`, or `unsafe` here
...
LL | bah!(2);
| -------- in this macro invocation

View file

@ -1,5 +1,5 @@
fn test_if() {
r#if true { } //~ ERROR found `true`
r#if true { } //~ ERROR found keyword `true`
}
fn test_struct() {

View file

@ -1,4 +1,4 @@
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found `true`
error: expected one of `!`, `.`, `::`, `;`, `?`, `{`, `}`, or an operator, found keyword `true`
--> $DIR/raw-literal-keywords.rs:2:10
|
LL | r#if true { }

View file

@ -8,7 +8,7 @@ fn main() {
let vec = vec![1, 2, 3];
for ( elem in vec ) {
//~^ ERROR expected one of `)`, `,`, `@`, or `|`, found `in`
//~^ ERROR expected one of `)`, `,`, `@`, or `|`, found keyword `in`
//~| ERROR unexpected closing `)`
const RECOVERY_WITNESS: () = 0; //~ ERROR mismatched types
}

View file

@ -1,4 +1,4 @@
error: expected one of `)`, `,`, `@`, or `|`, found `in`
error: expected one of `)`, `,`, `@`, or `|`, found keyword `in`
--> $DIR/recover-for-loop-parens-around-head.rs:10:16
|
LL | for ( elem in vec ) {

View file

@ -1,13 +1,13 @@
fn main() {
let _: usize = ()
//~^ ERROR mismatched types
//~| ERROR expected `;`
let _ = 3;
//~^ ERROR expected one of
}
fn foo() -> usize {
let _: usize = ()
//~^ ERROR mismatched types
//~| ERROR expected `;`
return 3;
//~^ ERROR expected one of
}

View file

@ -1,20 +1,20 @@
error: expected one of `.`, `;`, `?`, or an operator, found `let`
--> $DIR/recover-missing-semi.rs:4:5
error: expected `;`, found `keyword `let``
--> $DIR/recover-missing-semi.rs:2:22
|
LL | let _: usize = ()
| - help: a semicolon may be missing here
LL |
| ^ help: add `;` here
...
LL | let _ = 3;
| ^^^
| --- unexpected token
error: expected one of `.`, `;`, `?`, or an operator, found `return`
--> $DIR/recover-missing-semi.rs:11:5
error: expected `;`, found `keyword `return``
--> $DIR/recover-missing-semi.rs:9:22
|
LL | let _: usize = ()
| - help: a semicolon may be missing here
LL |
| ^ help: add `;` here
...
LL | return 3;
| ^^^^^^
| ------ unexpected token
error[E0308]: mismatched types
--> $DIR/recover-missing-semi.rs:2:20

View file

@ -1,4 +1,4 @@
error: expected one of `async`, `const`, `crate`, `default`, `extern`, `fn`, `pub`, `type`, `unsafe`, or `}`, found `static`
error: expected one of `async`, `const`, `crate`, `default`, `extern`, `fn`, `pub`, `type`, `unsafe`, or `}`, found keyword `static`
--> $DIR/removed-syntax-static-fn.rs:4:5
|
LL | impl S {

View file

@ -1 +1,2 @@
type mut_box = Box<mut isize>; //~ ERROR expected one of `>`, const, lifetime, or type, found `mut`
type mut_box = Box<mut isize>;
//~^ ERROR expected one of `>`, const, lifetime, or type, found keyword `mut`

View file

@ -1,4 +1,4 @@
error: expected one of `>`, const, lifetime, or type, found `mut`
error: expected one of `>`, const, lifetime, or type, found keyword `mut`
--> $DIR/removed-syntax-uniq-mut-ty.rs:1:20
|
LL | type mut_box = Box<mut isize>;

View file

@ -25,6 +25,6 @@ use _ as g; //~ ERROR expected identifier, found reserved identifier `_`
trait _ {} //~ ERROR expected identifier, found reserved identifier `_`
trait _ = Copy; //~ ERROR expected identifier, found reserved identifier `_`
macro_rules! _ { () => {} } //~ ERROR expected identifier, found reserved identifier `_`
union _ { f: u8 } //~ ERROR expected one of `!` or `::`, found `_`
union _ { f: u8 } //~ ERROR expected one of `!` or `::`, found reserved identifier `_`
fn main() {}

View file

@ -82,7 +82,7 @@ error: expected identifier, found reserved identifier `_`
LL | macro_rules! _ { () => {} }
| ^ expected identifier, found reserved identifier
error: expected one of `!` or `::`, found `_`
error: expected one of `!` or `::`, found reserved identifier `_`
--> $DIR/underscore_item_not_const.rs:28:7
|
LL | union _ { f: u8 }