Skip to content

Commit 2fe6f22

Browse files
authored
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).
2 parents eec3a9c + e8016c2 commit 2fe6f22

36 files changed

+175
-157
lines changed

src/libsyntax/parse/parser/diagnostics.rs

+82-68
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use crate::ast::{
66
self, Param, BinOpKind, BindingMode, BlockCheckMode, Expr, ExprKind, Ident, Item, ItemKind,
77
Mutability, Pat, PatKind, PathSegment, QSelf, Ty, TyKind,
88
};
9-
use crate::parse::token::{self, TokenKind};
9+
use crate::parse::token::{self, TokenKind, token_can_begin_expr};
1010
use crate::print::pprust;
1111
use crate::ptr::P;
1212
use crate::symbol::{kw, sym};
@@ -274,23 +274,23 @@ impl<'a> Parser<'a> {
274274
expected.sort_by_cached_key(|x| x.to_string());
275275
expected.dedup();
276276
let expect = tokens_to_string(&expected[..]);
277-
let actual = self.this_token_to_string();
277+
let actual = self.this_token_descr();
278278
let (msg_exp, (label_sp, label_exp)) = if expected.len() > 1 {
279279
let short_expect = if expected.len() > 6 {
280280
format!("{} possible tokens", expected.len())
281281
} else {
282282
expect.clone()
283283
};
284-
(format!("expected one of {}, found `{}`", expect, actual),
284+
(format!("expected one of {}, found {}", expect, actual),
285285
(self.sess.source_map().next_point(self.prev_span),
286286
format!("expected one of {} here", short_expect)))
287287
} else if expected.is_empty() {
288-
(format!("unexpected token: `{}`", actual),
288+
(format!("unexpected token: {}", actual),
289289
(self.prev_span, "unexpected token after this".to_string()))
290290
} else {
291-
(format!("expected {}, found `{}`", expect, actual),
291+
(format!("expected {}, found {}", expect, actual),
292292
(self.sess.source_map().next_point(self.prev_span),
293-
format!("expected {} here", expect)))
293+
format!("expected {}", expect)))
294294
};
295295
self.last_unexpected_token_span = Some(self.token.span);
296296
let mut err = self.fatal(&msg_exp);
@@ -326,58 +326,28 @@ impl<'a> Parser<'a> {
326326
}
327327
}
328328

329-
let is_semi_suggestable = expected.iter().any(|t| match t {
330-
TokenType::Token(token::Semi) => true, // We expect a `;` here.
331-
_ => false,
332-
}) && ( // A `;` would be expected before the current keyword.
333-
self.token.is_keyword(kw::Break) ||
334-
self.token.is_keyword(kw::Continue) ||
335-
self.token.is_keyword(kw::For) ||
336-
self.token.is_keyword(kw::If) ||
337-
self.token.is_keyword(kw::Let) ||
338-
self.token.is_keyword(kw::Loop) ||
339-
self.token.is_keyword(kw::Match) ||
340-
self.token.is_keyword(kw::Return) ||
341-
self.token.is_keyword(kw::While)
342-
);
343329
let sm = self.sess.source_map();
344-
match (sm.lookup_line(self.token.span.lo()), sm.lookup_line(sp.lo())) {
345-
(Ok(ref a), Ok(ref b)) if a.line != b.line && is_semi_suggestable => {
346-
// The spans are in different lines, expected `;` and found `let` or `return`.
347-
// High likelihood that it is only a missing `;`.
348-
err.span_suggestion_short(
349-
label_sp,
350-
"a semicolon may be missing here",
351-
";".to_string(),
352-
Applicability::MaybeIncorrect,
353-
);
354-
err.emit();
355-
return Ok(true);
356-
}
357-
(Ok(ref a), Ok(ref b)) if a.line == b.line => {
358-
// When the spans are in the same line, it means that the only content between
359-
// them is whitespace, point at the found token in that case:
360-
//
361-
// X | () => { syntax error };
362-
// | ^^^^^ expected one of 8 possible tokens here
363-
//
364-
// instead of having:
365-
//
366-
// X | () => { syntax error };
367-
// | -^^^^^ unexpected token
368-
// | |
369-
// | expected one of 8 possible tokens here
370-
err.span_label(self.token.span, label_exp);
371-
}
372-
_ if self.prev_span == syntax_pos::DUMMY_SP => {
373-
// Account for macro context where the previous span might not be
374-
// available to avoid incorrect output (#54841).
375-
err.span_label(self.token.span, "unexpected token");
376-
}
377-
_ => {
378-
err.span_label(sp, label_exp);
379-
err.span_label(self.token.span, "unexpected token");
380-
}
330+
if self.prev_span == DUMMY_SP {
331+
// Account for macro context where the previous span might not be
332+
// available to avoid incorrect output (#54841).
333+
err.span_label(self.token.span, label_exp);
334+
} else if !sm.is_multiline(self.token.span.shrink_to_hi().until(sp.shrink_to_lo())) {
335+
// When the spans are in the same line, it means that the only content between
336+
// them is whitespace, point at the found token in that case:
337+
//
338+
// X | () => { syntax error };
339+
// | ^^^^^ expected one of 8 possible tokens here
340+
//
341+
// instead of having:
342+
//
343+
// X | () => { syntax error };
344+
// | -^^^^^ unexpected token
345+
// | |
346+
// | expected one of 8 possible tokens here
347+
err.span_label(self.token.span, label_exp);
348+
} else {
349+
err.span_label(sp, label_exp);
350+
err.span_label(self.token.span, "unexpected token");
381351
}
382352
self.maybe_annotate_with_ascription(&mut err, false);
383353
Err(err)
@@ -902,20 +872,64 @@ impl<'a> Parser<'a> {
902872
}
903873
}
904874
let sm = self.sess.source_map();
905-
match (sm.lookup_line(prev_sp.lo()), sm.lookup_line(sp.lo())) {
906-
(Ok(ref a), Ok(ref b)) if a.line == b.line => {
907-
// When the spans are in the same line, it means that the only content
908-
// between them is whitespace, point only at the found token.
909-
err.span_label(sp, label_exp);
910-
}
911-
_ => {
912-
err.span_label(prev_sp, label_exp);
913-
err.span_label(sp, "unexpected token");
914-
}
875+
if !sm.is_multiline(prev_sp.until(sp)) {
876+
// When the spans are in the same line, it means that the only content
877+
// between them is whitespace, point only at the found token.
878+
err.span_label(sp, label_exp);
879+
} else {
880+
err.span_label(prev_sp, label_exp);
881+
err.span_label(sp, "unexpected token");
915882
}
916883
Err(err)
917884
}
918885

886+
pub(super) fn expect_semi(&mut self) -> PResult<'a, ()> {
887+
if self.eat(&token::Semi) {
888+
return Ok(());
889+
}
890+
let sm = self.sess.source_map();
891+
let msg = format!("expected `;`, found `{}`", self.this_token_descr());
892+
let appl = Applicability::MachineApplicable;
893+
if self.token.span == DUMMY_SP || self.prev_span == DUMMY_SP {
894+
// Likely inside a macro, can't provide meaninful suggestions.
895+
return self.expect(&token::Semi).map(|_| ());
896+
} else if !sm.is_multiline(self.prev_span.until(self.token.span)) {
897+
// The current token is in the same line as the prior token, not recoverable.
898+
} else if self.look_ahead(1, |t| t == &token::CloseDelim(token::Brace)
899+
|| token_can_begin_expr(t) && t.kind != token::Colon
900+
) && [token::Comma, token::Colon].contains(&self.token.kind) {
901+
// Likely typo: `,` → `;` or `:` → `;`. This is triggered if the current token is
902+
// either `,` or `:`, and the next token could either start a new statement or is a
903+
// block close. For example:
904+
//
905+
// let x = 32:
906+
// let y = 42;
907+
self.bump();
908+
let sp = self.prev_span;
909+
self.struct_span_err(sp, &msg)
910+
.span_suggestion(sp, "change this to `;`", ";".to_string(), appl)
911+
.emit();
912+
return Ok(())
913+
} else if self.look_ahead(0, |t| t == &token::CloseDelim(token::Brace) || (
914+
token_can_begin_expr(t)
915+
&& t != &token::Semi
916+
&& t != &token::Pound // Avoid triggering with too many trailing `#` in raw string.
917+
)) {
918+
// Missing semicolon typo. This is triggered if the next token could either start a
919+
// new statement or is a block close. For example:
920+
//
921+
// let x = 32
922+
// let y = 42;
923+
let sp = self.prev_span.shrink_to_hi();
924+
self.struct_span_err(sp, &msg)
925+
.span_label(self.token.span, "unexpected token")
926+
.span_suggestion_short(sp, "add `;` here", ";".to_string(), appl)
927+
.emit();
928+
return Ok(())
929+
}
930+
self.expect(&token::Semi).map(|_| ()) // Error unconditionally
931+
}
932+
919933
pub(super) fn parse_semi_or_incorrect_foreign_fn_body(
920934
&mut self,
921935
ident: &Ident,
@@ -943,7 +957,7 @@ impl<'a> Parser<'a> {
943957
Err(mut err) => {
944958
err.cancel();
945959
mem::replace(self, parser_snapshot);
946-
self.expect(&token::Semi)?;
960+
self.expect_semi()?;
947961
}
948962
}
949963
} else {

src/libsyntax/parse/parser/item.rs

+12-12
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ impl<'a> Parser<'a> {
9898
if self.eat_keyword(kw::Use) {
9999
// USE ITEM
100100
let item_ = ItemKind::Use(P(self.parse_use_tree()?));
101-
self.expect(&token::Semi)?;
101+
self.expect_semi()?;
102102

103103
let span = lo.to(self.prev_span);
104104
let item = self.mk_item(span, Ident::invalid(), item_, vis, attrs);
@@ -526,7 +526,7 @@ impl<'a> Parser<'a> {
526526
// eat a matched-delimiter token tree:
527527
let (delim, tts) = self.expect_delimited_token_tree()?;
528528
if delim != MacDelimiter::Brace {
529-
self.expect(&token::Semi)?;
529+
self.expect_semi()?;
530530
}
531531

532532
Ok(Some(Mac {
@@ -776,7 +776,7 @@ impl<'a> Parser<'a> {
776776
let typ = self.parse_ty()?;
777777
self.expect(&token::Eq)?;
778778
let expr = self.parse_expr()?;
779-
self.expect(&token::Semi)?;
779+
self.expect_semi()?;
780780
Ok((name, ImplItemKind::Const(typ, expr), Generics::default()))
781781
}
782782

@@ -813,7 +813,7 @@ impl<'a> Parser<'a> {
813813

814814
let bounds = self.parse_generic_bounds(None)?;
815815
tps.where_clause = self.parse_where_clause()?;
816-
self.expect(&token::Semi)?;
816+
self.expect_semi()?;
817817

818818
let whole_span = lo.to(self.prev_span);
819819
if is_auto == IsAuto::Yes {
@@ -927,7 +927,7 @@ impl<'a> Parser<'a> {
927927
} else {
928928
None
929929
};
930-
self.expect(&token::Semi)?;
930+
self.expect_semi()?;
931931
Ok((ident, TraitItemKind::Const(ty, default), Generics::default()))
932932
}
933933

@@ -951,7 +951,7 @@ impl<'a> Parser<'a> {
951951
} else {
952952
None
953953
};
954-
self.expect(&token::Semi)?;
954+
self.expect_semi()?;
955955

956956
Ok((ident, TraitItemKind::Type(bounds, default), generics))
957957
}
@@ -1054,7 +1054,7 @@ impl<'a> Parser<'a> {
10541054
} else {
10551055
(orig_name, None)
10561056
};
1057-
self.expect(&token::Semi)?;
1057+
self.expect_semi()?;
10581058

10591059
let span = lo.to(self.prev_span);
10601060
Ok(self.mk_item(span, item_name, ItemKind::ExternCrate(orig_name), visibility, attrs))
@@ -1217,7 +1217,7 @@ impl<'a> Parser<'a> {
12171217
self.expect(&token::Colon)?;
12181218
let ty = self.parse_ty()?;
12191219
let hi = self.token.span;
1220-
self.expect(&token::Semi)?;
1220+
self.expect_semi()?;
12211221
Ok(ForeignItem {
12221222
ident,
12231223
attrs,
@@ -1235,7 +1235,7 @@ impl<'a> Parser<'a> {
12351235

12361236
let ident = self.parse_ident()?;
12371237
let hi = self.token.span;
1238-
self.expect(&token::Semi)?;
1238+
self.expect_semi()?;
12391239
Ok(ast::ForeignItem {
12401240
ident,
12411241
attrs,
@@ -1282,7 +1282,7 @@ impl<'a> Parser<'a> {
12821282

12831283
self.expect(&token::Eq)?;
12841284
let e = self.parse_expr()?;
1285-
self.expect(&token::Semi)?;
1285+
self.expect_semi()?;
12861286
let item = match m {
12871287
Some(m) => ItemKind::Static(ty, m, e),
12881288
None => ItemKind::Const(ty, e),
@@ -1344,7 +1344,7 @@ impl<'a> Parser<'a> {
13441344
let ty = self.parse_ty()?;
13451345
AliasKind::Weak(ty)
13461346
};
1347-
self.expect(&token::Semi)?;
1347+
self.expect_semi()?;
13481348
Ok((ident, alias, tps))
13491349
}
13501350

@@ -1468,7 +1468,7 @@ impl<'a> Parser<'a> {
14681468
} else if self.token == token::OpenDelim(token::Paren) {
14691469
let body = VariantData::Tuple(self.parse_tuple_struct_body()?, DUMMY_NODE_ID);
14701470
generics.where_clause = self.parse_where_clause()?;
1471-
self.expect(&token::Semi)?;
1471+
self.expect_semi()?;
14721472
body
14731473
} else {
14741474
let token_str = self.this_token_descr();

src/libsyntax/parse/parser/stmt.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,7 @@ impl<'a> Parser<'a> {
432432
None => return Ok(None),
433433
};
434434

435+
let mut eat_semi = true;
435436
match stmt.kind {
436437
StmtKind::Expr(ref expr) if self.token != token::Eof => {
437438
// expression without semicolon
@@ -453,13 +454,14 @@ impl<'a> Parser<'a> {
453454
if macro_legacy_warnings && self.token != token::Semi {
454455
self.warn_missing_semicolon();
455456
} else {
456-
self.expect_one_of(&[], &[token::Semi])?;
457+
self.expect_semi()?;
458+
eat_semi = false;
457459
}
458460
}
459461
_ => {}
460462
}
461463

462-
if self.eat(&token::Semi) {
464+
if eat_semi && self.eat(&token::Semi) {
463465
stmt = stmt.add_trailing_semicolon();
464466
}
465467
stmt.span = stmt.span.to(self.prev_span);

src/libsyntax/parse/token.rs

+26-25
Original file line numberDiff line numberDiff line change
@@ -143,34 +143,35 @@ impl Lit {
143143

144144
pub(crate) fn ident_can_begin_expr(name: ast::Name, span: Span, is_raw: bool) -> bool {
145145
let ident_token = Token::new(Ident(name, is_raw), span);
146+
token_can_begin_expr(&ident_token)
147+
}
146148

149+
pub(crate) fn token_can_begin_expr(ident_token: &Token) -> bool {
147150
!ident_token.is_reserved_ident() ||
148151
ident_token.is_path_segment_keyword() ||
149-
[
150-
kw::Async,
151-
152-
// FIXME: remove when `await!(..)` syntax is removed
153-
// https://github.com/rust-lang/rust/issues/60610
154-
kw::Await,
155-
156-
kw::Do,
157-
kw::Box,
158-
kw::Break,
159-
kw::Continue,
160-
kw::False,
161-
kw::For,
162-
kw::If,
163-
kw::Let,
164-
kw::Loop,
165-
kw::Match,
166-
kw::Move,
167-
kw::Return,
168-
kw::True,
169-
kw::Unsafe,
170-
kw::While,
171-
kw::Yield,
172-
kw::Static,
173-
].contains(&name)
152+
match ident_token.kind {
153+
TokenKind::Ident(ident, _) => [
154+
kw::Async,
155+
kw::Do,
156+
kw::Box,
157+
kw::Break,
158+
kw::Continue,
159+
kw::False,
160+
kw::For,
161+
kw::If,
162+
kw::Let,
163+
kw::Loop,
164+
kw::Match,
165+
kw::Move,
166+
kw::Return,
167+
kw::True,
168+
kw::Unsafe,
169+
kw::While,
170+
kw::Yield,
171+
kw::Static,
172+
].contains(&ident),
173+
_=> false,
174+
}
174175
}
175176

176177
fn ident_can_begin_type(name: ast::Name, span: Span, is_raw: bool) -> bool {

0 commit comments

Comments
 (0)