Skip to content

Commit

Permalink
When encountering unclosed delimiters during parsing, check for diff …
Browse files Browse the repository at this point in the history
…markers

Fix #116252.
  • Loading branch information
estebank committed Oct 13, 2023
1 parent df4379b commit 1a69b6c
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 35 deletions.
38 changes: 22 additions & 16 deletions compiler/rustc_parse/src/lexer/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,28 +64,34 @@ pub(crate) fn parse_token_trees<'a>(
override_span,
nbsp_is_whitespace: false,
};
let (token_trees, unmatched_delims) =
let (stream, res, unmatched_delims) =
tokentrees::TokenTreesReader::parse_all_token_trees(string_reader);
match token_trees {
Ok(stream) if unmatched_delims.is_empty() => Ok(stream),
_ => {
// Return error if there are unmatched delimiters or unclosed delimiters.
// We emit delimiter mismatch errors first, then emit the unclosing delimiter mismatch
// because the delimiter mismatch is more likely to be the root cause of error

let mut buffer = Vec::with_capacity(1);
for unmatched in unmatched_delims {
if let Some(err) = make_unclosed_delims_error(unmatched, &sess) {
err.buffer(&mut buffer);
}
if !unmatched_delims.is_empty() || res.is_err() {
// Return error if there are unmatched delimiters or unclosed delimiters.
// We emit delimiter mismatch errors first, then emit the unclosing delimiter mismatch
// because the delimiter mismatch is more likely to be the root cause of error

let mut buffer = Vec::with_capacity(1);
let mut parser = crate::stream_to_parser(sess, stream.clone(), None);
while parser.token != token::Eof {
// There were unmatched delims, see if there were diff markers that caused it.
if let Err(err) = parser.err_diff_marker() {
err.buffer(&mut buffer);
}
if let Err(err) = token_trees {
// Add unclosing delimiter error
parser.bump();
}
for unmatched in unmatched_delims {
if let Some(err) = make_unclosed_delims_error(unmatched, &sess) {
err.buffer(&mut buffer);
}
Err(buffer)
}
if let Err(err) = res {
// Add unclosing delimiter error
err.buffer(&mut buffer);
}
return Err(buffer);
}
Ok(stream)
}

struct StringReader<'a> {
Expand Down
48 changes: 32 additions & 16 deletions compiler/rustc_parse/src/lexer/tokentrees.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,36 +18,39 @@ pub(super) struct TokenTreesReader<'a> {
impl<'a> TokenTreesReader<'a> {
pub(super) fn parse_all_token_trees(
string_reader: StringReader<'a>,
) -> (PResult<'a, TokenStream>, Vec<UnmatchedDelim>) {
) -> (TokenStream, PResult<'a, ()>, Vec<UnmatchedDelim>) {
let mut tt_reader = TokenTreesReader {
string_reader,
token: Token::dummy(),
diag_info: TokenTreeDiagInfo::default(),
};
let res = tt_reader.parse_token_trees(/* is_delimited */ false);
(res, tt_reader.diag_info.unmatched_delims)
let (stream, res) = tt_reader.parse_token_trees(/* is_delimited */ false);
(stream, res, tt_reader.diag_info.unmatched_delims)
}

// Parse a stream of tokens into a list of `TokenTree`s.
fn parse_token_trees(&mut self, is_delimited: bool) -> PResult<'a, TokenStream> {
fn parse_token_trees(&mut self, is_delimited: bool) -> (TokenStream, PResult<'a, ()>) {
self.token = self.string_reader.next_token().0;
let mut buf = Vec::new();
loop {
match self.token.kind {
token::OpenDelim(delim) => buf.push(self.parse_token_tree_open_delim(delim)?),
token::OpenDelim(delim) => {
buf.push(match self.parse_token_tree_open_delim(delim) {
Ok(val) => val,
Err(err) => return (TokenStream::new(buf), Err(err)),
})
}
token::CloseDelim(delim) => {
return if is_delimited {
Ok(TokenStream::new(buf))
} else {
Err(self.close_delim_err(delim))
};
return (
TokenStream::new(buf),
if is_delimited { Ok(()) } else { Err(self.close_delim_err(delim)) },
);
}
token::Eof => {
return if is_delimited {
Err(self.eof_err())
} else {
Ok(TokenStream::new(buf))
};
return (
TokenStream::new(buf),
if is_delimited { Err(self.eof_err()) } else { Ok(()) },
);
}
_ => {
// Get the next normal token. This might require getting multiple adjacent
Expand Down Expand Up @@ -106,7 +109,20 @@ impl<'a> TokenTreesReader<'a> {
// Parse the token trees within the delimiters.
// We stop at any delimiter so we can try to recover if the user
// uses an incorrect delimiter.
let tts = self.parse_token_trees(/* is_delimited */ true)?;
let (tts, res) = self.parse_token_trees(/* is_delimited */ true);
if let Err(mut err) = res {
// If there are unclosed delims, see if there are diff markers and if so, point them
// out instead of complaining about the unclosed delims.
let mut parser = crate::stream_to_parser(self.string_reader.sess, tts, None);
while parser.token != token::Eof {
if let Err(mut diff_err) = parser.err_diff_marker() {
diff_err.emit();
err.delay_as_bug();
}
parser.bump();
}
return Err(err);
}

// Expand to cover the entire delimited token tree
let delim_span = DelimSpan::from_pair(pre_span, self.token.span);
Expand Down
12 changes: 9 additions & 3 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2721,8 +2721,15 @@ impl<'a> Parser<'a> {
}

pub fn recover_diff_marker(&mut self) {
if let Err(mut err) = self.err_diff_marker() {
err.emit();
FatalError.raise();
}
}

pub fn err_diff_marker(&mut self) -> PResult<'a, ()> {
let Some(start) = self.diff_marker(&TokenKind::BinOp(token::Shl), &TokenKind::Lt) else {
return;
return Ok(());
};
let mut spans = Vec::with_capacity(3);
spans.push(start);
Expand Down Expand Up @@ -2769,8 +2776,7 @@ impl<'a> Parser<'a> {
"for an explanation on these markers from the `git` documentation, visit \
<https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging#_checking_out_conflicts>",
);
err.emit();
FatalError.raise()
Err(err)
}

/// Parse and throw away a parenthesized comma separated
Expand Down
14 changes: 14 additions & 0 deletions tests/ui/parser/diff-markers/unclosed-delims.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
mod tests {
#[test]
<<<<<<< HEAD
//~^ ERROR encountered diff marker
//~| NOTE after this is the code before the merge
//~| NOTE for an explanation on these markers
fn test1() {
=======
//~^ NOTE
fn test2() {
>>>>>>> 7a4f13c blah blah blah
//~^ NOTE above this are the incoming code changes
}
}
18 changes: 18 additions & 0 deletions tests/ui/parser/diff-markers/unclosed-delims.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
error: encountered diff marker
--> $DIR/unclosed-delims.rs:3:1
|
LL | <<<<<<< HEAD
| ^^^^^^^ after this is the code before the merge
...
LL | =======
| -------
...
LL | >>>>>>> 7a4f13c blah blah blah
| ^^^^^^^ above this are the incoming code changes
|
= help: if you're having merge conflicts after pulling new code, the top section is the code you already had and the bottom section is the remote code
= help: if you're in the middle of a rebase, the top section is the code being rebased onto and the bottom section is the code coming from the current commit being rebased
= note: for an explanation on these markers from the `git` documentation, visit <https://git-scm.com/book/en/v2/Git-Tools-Advanced-Merging#_checking_out_conflicts>

error: aborting due to previous error

0 comments on commit 1a69b6c

Please sign in to comment.