Skip to content

Commit 62c068f

Browse files
committed
Auto merge of #127636 - nnethercote:fix-Parser-look_ahead, r=oli-obk
Fix `Parser::look_ahead` `Parser::look_ahead` has a slow but simple general case, and a fast special case that is hit most of the time. But the special case is buggy and behaves differently to the general case. There are also no unit tests. This PR fixes all of this, resulting in a `Parser::look_ahead` that is equally fast, slightly simpler, more correct, and better tested. r? `@davidtwco`
2 parents 5d76a13 + 100f3fd commit 62c068f

File tree

2 files changed

+147
-30
lines changed

2 files changed

+147
-30
lines changed

compiler/rustc_parse/src/parser/mod.rs

+26-30
Original file line numberDiff line numberDiff line change
@@ -1118,41 +1118,37 @@ impl<'a> Parser<'a> {
11181118
return looker(&self.token);
11191119
}
11201120

1121-
if let Some(&(_, span, _, delim)) = self.token_cursor.stack.last()
1122-
&& delim != Delimiter::Invisible
1123-
{
1124-
// We are not in the outermost token stream, and the token stream
1125-
// we are in has non-skipped delimiters. Look for skipped
1126-
// delimiters in the lookahead range.
1127-
let tree_cursor = &self.token_cursor.tree_cursor;
1128-
let all_normal = (0..dist).all(|i| {
1129-
let token = tree_cursor.look_ahead(i);
1130-
!matches!(token, Some(TokenTree::Delimited(.., Delimiter::Invisible, _)))
1131-
});
1132-
if all_normal {
1133-
// There were no skipped delimiters. Do lookahead by plain indexing.
1134-
return match tree_cursor.look_ahead(dist - 1) {
1135-
Some(tree) => {
1136-
// Indexing stayed within the current token stream.
1137-
match tree {
1138-
TokenTree::Token(token, _) => looker(token),
1139-
TokenTree::Delimited(dspan, _, delim, _) => {
1140-
looker(&Token::new(token::OpenDelim(*delim), dspan.open))
1141-
}
1121+
// Typically around 98% of the `dist > 0` cases have `dist == 1`, so we
1122+
// have a fast special case for that.
1123+
if dist == 1 {
1124+
// The index is zero because the tree cursor's index always points
1125+
// to the next token to be gotten.
1126+
match self.token_cursor.tree_cursor.look_ahead(0) {
1127+
Some(tree) => {
1128+
// Indexing stayed within the current token tree.
1129+
return match tree {
1130+
TokenTree::Token(token, _) => looker(token),
1131+
TokenTree::Delimited(dspan, _, delim, _) => {
1132+
looker(&Token::new(token::OpenDelim(*delim), dspan.open))
11421133
}
1134+
};
1135+
}
1136+
None => {
1137+
// The tree cursor lookahead went (one) past the end of the
1138+
// current token tree. Try to return a close delimiter.
1139+
if let Some(&(_, span, _, delim)) = self.token_cursor.stack.last()
1140+
&& delim != Delimiter::Invisible
1141+
{
1142+
// We are not in the outermost token stream, so we have
1143+
// delimiters. Also, those delimiters are not skipped.
1144+
return looker(&Token::new(token::CloseDelim(delim), span.close));
11431145
}
1144-
None => {
1145-
// Indexing went past the end of the current token
1146-
// stream. Use the close delimiter, no matter how far
1147-
// ahead `dist` went.
1148-
looker(&Token::new(token::CloseDelim(delim), span.close))
1149-
}
1150-
};
1146+
}
11511147
}
11521148
}
11531149

1154-
// We are in a more complex case. Just clone the token cursor and use
1155-
// `next`, skipping delimiters as necessary. Slow but simple.
1150+
// Just clone the token cursor and use `next`, skipping delimiters as
1151+
// necessary. Slow but simple.
11561152
let mut cursor = self.token_cursor.clone();
11571153
let mut i = 0;
11581154
let mut token = Token::dummy();

compiler/rustc_parse/src/parser/tests.rs

+121
Original file line numberDiff line numberDiff line change
@@ -1376,6 +1376,127 @@ fn ttdelim_span() {
13761376
});
13771377
}
13781378

1379+
// Uses a macro rather than a function so that failure messages mention the
1380+
// correct line in the test function.
1381+
macro_rules! look {
1382+
($p:ident, $dist:literal, $kind:expr) => {
1383+
$p.look_ahead($dist, |tok| assert_eq!($kind, tok.kind));
1384+
};
1385+
}
1386+
1387+
#[test]
1388+
fn look_ahead() {
1389+
create_default_session_globals_then(|| {
1390+
let sym_f = Symbol::intern("f");
1391+
let sym_x = Symbol::intern("x");
1392+
#[allow(non_snake_case)]
1393+
let sym_S = Symbol::intern("S");
1394+
let raw_no = IdentIsRaw::No;
1395+
1396+
let psess = psess();
1397+
let mut p = string_to_parser(&psess, "fn f(x: u32) { x } struct S;".to_string());
1398+
1399+
// Current position is the `fn`.
1400+
look!(p, 0, token::Ident(kw::Fn, raw_no));
1401+
look!(p, 1, token::Ident(sym_f, raw_no));
1402+
look!(p, 2, token::OpenDelim(Delimiter::Parenthesis));
1403+
look!(p, 3, token::Ident(sym_x, raw_no));
1404+
look!(p, 4, token::Colon);
1405+
look!(p, 5, token::Ident(sym::u32, raw_no));
1406+
look!(p, 6, token::CloseDelim(Delimiter::Parenthesis));
1407+
look!(p, 7, token::OpenDelim(Delimiter::Brace));
1408+
look!(p, 8, token::Ident(sym_x, raw_no));
1409+
look!(p, 9, token::CloseDelim(Delimiter::Brace));
1410+
look!(p, 10, token::Ident(kw::Struct, raw_no));
1411+
look!(p, 11, token::Ident(sym_S, raw_no));
1412+
look!(p, 12, token::Semi);
1413+
// Any lookahead past the end of the token stream returns `Eof`.
1414+
look!(p, 13, token::Eof);
1415+
look!(p, 14, token::Eof);
1416+
look!(p, 15, token::Eof);
1417+
look!(p, 100, token::Eof);
1418+
1419+
// Move forward to the first `x`.
1420+
for _ in 0..3 {
1421+
p.bump();
1422+
}
1423+
look!(p, 0, token::Ident(sym_x, raw_no));
1424+
look!(p, 1, token::Colon);
1425+
look!(p, 2, token::Ident(sym::u32, raw_no));
1426+
look!(p, 3, token::CloseDelim(Delimiter::Parenthesis));
1427+
look!(p, 4, token::OpenDelim(Delimiter::Brace));
1428+
look!(p, 5, token::Ident(sym_x, raw_no));
1429+
look!(p, 6, token::CloseDelim(Delimiter::Brace));
1430+
look!(p, 7, token::Ident(kw::Struct, raw_no));
1431+
look!(p, 8, token::Ident(sym_S, raw_no));
1432+
look!(p, 9, token::Semi);
1433+
look!(p, 10, token::Eof);
1434+
look!(p, 11, token::Eof);
1435+
look!(p, 100, token::Eof);
1436+
1437+
// Move forward to the `;`.
1438+
for _ in 0..9 {
1439+
p.bump();
1440+
}
1441+
look!(p, 0, token::Semi);
1442+
// Any lookahead past the end of the token stream returns `Eof`.
1443+
look!(p, 1, token::Eof);
1444+
look!(p, 100, token::Eof);
1445+
1446+
// Move one past the `;`, i.e. past the end of the token stream.
1447+
p.bump();
1448+
look!(p, 0, token::Eof);
1449+
look!(p, 1, token::Eof);
1450+
look!(p, 100, token::Eof);
1451+
1452+
// Bumping after Eof is idempotent.
1453+
p.bump();
1454+
look!(p, 0, token::Eof);
1455+
look!(p, 1, token::Eof);
1456+
look!(p, 100, token::Eof);
1457+
});
1458+
}
1459+
1460+
/// There used to be some buggy behaviour when using `look_ahead` not within
1461+
/// the outermost token stream, which this test covers.
1462+
#[test]
1463+
fn look_ahead_non_outermost_stream() {
1464+
create_default_session_globals_then(|| {
1465+
let sym_f = Symbol::intern("f");
1466+
let sym_x = Symbol::intern("x");
1467+
#[allow(non_snake_case)]
1468+
let sym_S = Symbol::intern("S");
1469+
let raw_no = IdentIsRaw::No;
1470+
1471+
let psess = psess();
1472+
let mut p = string_to_parser(&psess, "mod m { fn f(x: u32) { x } struct S; }".to_string());
1473+
1474+
// Move forward to the `fn`, which is not within the outermost token
1475+
// stream (because it's inside the `mod { ... }`).
1476+
for _ in 0..3 {
1477+
p.bump();
1478+
}
1479+
look!(p, 0, token::Ident(kw::Fn, raw_no));
1480+
look!(p, 1, token::Ident(sym_f, raw_no));
1481+
look!(p, 2, token::OpenDelim(Delimiter::Parenthesis));
1482+
look!(p, 3, token::Ident(sym_x, raw_no));
1483+
look!(p, 4, token::Colon);
1484+
look!(p, 5, token::Ident(sym::u32, raw_no));
1485+
look!(p, 6, token::CloseDelim(Delimiter::Parenthesis));
1486+
look!(p, 7, token::OpenDelim(Delimiter::Brace));
1487+
look!(p, 8, token::Ident(sym_x, raw_no));
1488+
look!(p, 9, token::CloseDelim(Delimiter::Brace));
1489+
look!(p, 10, token::Ident(kw::Struct, raw_no));
1490+
look!(p, 11, token::Ident(sym_S, raw_no));
1491+
look!(p, 12, token::Semi);
1492+
look!(p, 13, token::CloseDelim(Delimiter::Brace));
1493+
// Any lookahead past the end of the token stream returns `Eof`.
1494+
look!(p, 14, token::Eof);
1495+
look!(p, 15, token::Eof);
1496+
look!(p, 100, token::Eof);
1497+
});
1498+
}
1499+
13791500
// This tests that when parsing a string (rather than a file) we don't try
13801501
// and read in a file for a module declaration and just parse a stub.
13811502
// See `recurse_into_file_modules` in the parser.

0 commit comments

Comments
 (0)