Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Exit when there are unmatched delims to avoid noisy diagnostics #108297

Merged
merged 5 commits into from
Mar 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion compiler/rustc_expand/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ pub(crate) fn string_to_stream(source_str: String) -> TokenStream {
ps.source_map().new_source_file(PathBuf::from("bogofile").into(), source_str),
None,
)
.0
}

/// Parses a string, returns a crate.
Expand Down
10 changes: 5 additions & 5 deletions compiler/rustc_parse/src/lexer/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use super::UnmatchedBrace;
use super::UnmatchedDelim;
use rustc_ast::token::Delimiter;
use rustc_errors::Diagnostic;
use rustc_span::source_map::SourceMap;
Expand All @@ -8,7 +8,7 @@ use rustc_span::Span;
pub struct TokenTreeDiagInfo {
/// Stack of open delimiters and their spans. Used for error message.
pub open_braces: Vec<(Delimiter, Span)>,
pub unmatched_braces: Vec<UnmatchedBrace>,
pub unmatched_delims: Vec<UnmatchedDelim>,

/// Used only for error recovery when arriving to EOF with mismatched braces.
pub last_unclosed_found_span: Option<Span>,
Expand All @@ -32,10 +32,10 @@ pub fn same_identation_level(sm: &SourceMap, open_sp: Span, close_sp: Span) -> b
// it's more friendly compared to report `unmatched error` in later phase
pub fn report_missing_open_delim(
err: &mut Diagnostic,
unmatched_braces: &[UnmatchedBrace],
unmatched_delims: &[UnmatchedDelim],
) -> bool {
let mut reported_missing_open = false;
for unmatch_brace in unmatched_braces.iter() {
for unmatch_brace in unmatched_delims.iter() {
if let Some(delim) = unmatch_brace.found_delim
&& matches!(delim, Delimiter::Parenthesis | Delimiter::Bracket)
{
Expand All @@ -60,7 +60,7 @@ pub fn report_suspicious_mismatch_block(
sm: &SourceMap,
delim: Delimiter,
) {
if report_missing_open_delim(err, &diag_info.unmatched_braces) {
if report_missing_open_delim(err, &diag_info.unmatched_delims) {
return;
}

Expand Down
31 changes: 27 additions & 4 deletions compiler/rustc_parse/src/lexer/mod.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::errors;
use crate::lexer::unicode_chars::UNICODE_ARRAY;
use crate::make_unclosed_delims_error;
use rustc_ast::ast::{self, AttrStyle};
use rustc_ast::token::{self, CommentKind, Delimiter, Token, TokenKind};
use rustc_ast::tokenstream::TokenStream;
use rustc_ast::util::unicode::contains_text_flow_control_chars;
use rustc_errors::{error_code, Applicability, DiagnosticBuilder, PResult, StashKey};
use rustc_errors::{error_code, Applicability, Diagnostic, DiagnosticBuilder, StashKey};
use rustc_lexer::unescape::{self, Mode};
use rustc_lexer::Cursor;
use rustc_lexer::{Base, DocStyle, RawStrError};
Expand All @@ -31,7 +32,7 @@ use unescape_error_reporting::{emit_unescape_error, escaped_char};
rustc_data_structures::static_assert_size!(rustc_lexer::Token, 12);

#[derive(Clone, Debug)]
pub struct UnmatchedBrace {
pub struct UnmatchedDelim {
pub expected_delim: Delimiter,
pub found_delim: Option<Delimiter>,
pub found_span: Span,
Expand All @@ -44,7 +45,7 @@ pub(crate) fn parse_token_trees<'a>(
mut src: &'a str,
mut start_pos: BytePos,
override_span: Option<Span>,
) -> (PResult<'a, TokenStream>, Vec<UnmatchedBrace>) {
) -> Result<TokenStream, Vec<Diagnostic>> {
// Skip `#!`, if present.
if let Some(shebang_len) = rustc_lexer::strip_shebang(src) {
src = &src[shebang_len..];
Expand All @@ -61,7 +62,29 @@ pub(crate) fn parse_token_trees<'a>(
override_span,
nbsp_is_whitespace: false,
};
tokentrees::TokenTreesReader::parse_all_token_trees(string_reader)
let (token_trees, 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 unclosng 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);
// Not using `emit_unclosed_delims` to use `db.buffer`
for unmatched in unmatched_delims {
if let Some(err) = make_unclosed_delims_error(unmatched, &sess) {
err.buffer(&mut buffer);
}
}
if let Err(err) = token_trees {
// Add unclosing delimiter error
err.buffer(&mut buffer);
}
Err(buffer)
}
}
}

struct StringReader<'a> {
Expand Down
27 changes: 14 additions & 13 deletions compiler/rustc_parse/src/lexer/tokentrees.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use super::diagnostics::report_suspicious_mismatch_block;
use super::diagnostics::same_identation_level;
use super::diagnostics::TokenTreeDiagInfo;
use super::{StringReader, UnmatchedBrace};
use super::{StringReader, UnmatchedDelim};
use rustc_ast::token::{self, Delimiter, Token};
use rustc_ast::tokenstream::{DelimSpan, Spacing, TokenStream, TokenTree};
use rustc_ast_pretty::pprust::token_to_string;
Expand All @@ -18,14 +18,14 @@ pub(super) struct TokenTreesReader<'a> {
impl<'a> TokenTreesReader<'a> {
pub(super) fn parse_all_token_trees(
string_reader: StringReader<'a>,
) -> (PResult<'a, TokenStream>, Vec<UnmatchedBrace>) {
) -> (PResult<'a, TokenStream>, 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_braces)
(res, tt_reader.diag_info.unmatched_delims)
}

// Parse a stream of tokens into a list of `TokenTree`s.
Expand All @@ -34,7 +34,7 @@ impl<'a> TokenTreesReader<'a> {
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(self.parse_token_tree_open_delim(delim)?),
token::CloseDelim(delim) => {
return if is_delimited {
Ok(TokenStream::new(buf))
Expand All @@ -43,10 +43,11 @@ impl<'a> TokenTreesReader<'a> {
};
}
token::Eof => {
if is_delimited {
petrochenkov marked this conversation as resolved.
Show resolved Hide resolved
self.eof_err().emit();
}
return Ok(TokenStream::new(buf));
return if is_delimited {
Err(self.eof_err())
} else {
Ok(TokenStream::new(buf))
};
}
_ => {
// Get the next normal token. This might require getting multiple adjacent
Expand Down Expand Up @@ -78,7 +79,7 @@ impl<'a> TokenTreesReader<'a> {
let mut err = self.string_reader.sess.span_diagnostic.struct_span_err(self.token.span, msg);
for &(_, sp) in &self.diag_info.open_braces {
err.span_label(sp, "unclosed delimiter");
self.diag_info.unmatched_braces.push(UnmatchedBrace {
self.diag_info.unmatched_delims.push(UnmatchedDelim {
expected_delim: Delimiter::Brace,
found_delim: None,
found_span: self.token.span,
Expand All @@ -98,7 +99,7 @@ impl<'a> TokenTreesReader<'a> {
err
}

fn parse_token_tree_open_delim(&mut self, open_delim: Delimiter) -> TokenTree {
fn parse_token_tree_open_delim(&mut self, open_delim: Delimiter) -> PResult<'a, TokenTree> {
chenyukang marked this conversation as resolved.
Show resolved Hide resolved
// The span for beginning of the delimited section
let pre_span = self.token.span;

Expand All @@ -107,7 +108,7 @@ 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).unwrap();
let tts = self.parse_token_trees(/* is_delimited */ true)?;

// Expand to cover the entire delimited token tree
let delim_span = DelimSpan::from_pair(pre_span, self.token.span);
Expand Down Expand Up @@ -160,7 +161,7 @@ impl<'a> TokenTreesReader<'a> {
}
}
let (tok, _) = self.diag_info.open_braces.pop().unwrap();
self.diag_info.unmatched_braces.push(UnmatchedBrace {
self.diag_info.unmatched_delims.push(UnmatchedDelim {
expected_delim: tok,
found_delim: Some(close_delim),
found_span: self.token.span,
Expand Down Expand Up @@ -190,7 +191,7 @@ impl<'a> TokenTreesReader<'a> {
_ => unreachable!(),
}

TokenTree::Delimited(delim_span, open_delim, tts)
Ok(TokenTree::Delimited(delim_span, open_delim, tts))
}

fn close_delim_err(&mut self, delim: Delimiter) -> PErr<'a> {
Expand Down
32 changes: 6 additions & 26 deletions compiler/rustc_parse/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ pub const MACRO_ARGUMENTS: Option<&str> = Some("macro arguments");

#[macro_use]
pub mod parser;
use parser::{emit_unclosed_delims, make_unclosed_delims_error, Parser};
use parser::{make_unclosed_delims_error, Parser};
pub mod lexer;
pub mod validate_attr;

Expand Down Expand Up @@ -96,10 +96,7 @@ pub fn parse_stream_from_source_str(
sess: &ParseSess,
override_span: Option<Span>,
) -> TokenStream {
let (stream, mut errors) =
source_file_to_stream(sess, sess.source_map().new_source_file(name, source), override_span);
emit_unclosed_delims(&mut errors, &sess);
stream
source_file_to_stream(sess, sess.source_map().new_source_file(name, source), override_span)
}

/// Creates a new parser from a source string.
Expand Down Expand Up @@ -135,9 +132,8 @@ fn maybe_source_file_to_parser(
source_file: Lrc<SourceFile>,
) -> Result<Parser<'_>, Vec<Diagnostic>> {
let end_pos = source_file.end_pos;
let (stream, unclosed_delims) = maybe_file_to_stream(sess, source_file, None)?;
let stream = maybe_file_to_stream(sess, source_file, None)?;
let mut parser = stream_to_parser(sess, stream, None);
parser.unclosed_delims = unclosed_delims;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is Parser::unclosed_delims field ever populated after this change?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, but this field is used in parser itself, I found that we could have more cleanup on Parser::unclosed_delims, maybe it's better to do it in another Pr.

if parser.token == token::Eof {
parser.token.span = Span::new(end_pos, end_pos, parser.token.span.ctxt(), None);
}
Expand Down Expand Up @@ -182,7 +178,7 @@ pub fn source_file_to_stream(
sess: &ParseSess,
source_file: Lrc<SourceFile>,
override_span: Option<Span>,
) -> (TokenStream, Vec<lexer::UnmatchedBrace>) {
) -> TokenStream {
panictry_buffer!(&sess.span_diagnostic, maybe_file_to_stream(sess, source_file, override_span))
}

Expand All @@ -192,31 +188,15 @@ pub fn maybe_file_to_stream(
sess: &ParseSess,
source_file: Lrc<SourceFile>,
override_span: Option<Span>,
) -> Result<(TokenStream, Vec<lexer::UnmatchedBrace>), Vec<Diagnostic>> {
) -> Result<TokenStream, Vec<Diagnostic>> {
let src = source_file.src.as_ref().unwrap_or_else(|| {
sess.span_diagnostic.bug(&format!(
"cannot lex `source_file` without source: {}",
sess.source_map().filename_for_diagnostics(&source_file.name)
));
});

let (token_trees, unmatched_braces) =
lexer::parse_token_trees(sess, src.as_str(), source_file.start_pos, override_span);

match token_trees {
Ok(stream) => Ok((stream, unmatched_braces)),
Err(err) => {
let mut buffer = Vec::with_capacity(1);
err.buffer(&mut buffer);
// Not using `emit_unclosed_delims` to use `db.buffer`
for unmatched in unmatched_braces {
if let Some(err) = make_unclosed_delims_error(unmatched, &sess) {
err.buffer(&mut buffer);
}
}
Err(buffer)
}
}
lexer::parse_token_trees(sess, src.as_str(), source_file.start_pos, override_span)
}

/// Given a stream and the `ParseSess`, produces a parser.
Expand Down
6 changes: 3 additions & 3 deletions compiler/rustc_parse/src/parser/diagnostics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use crate::errors::{
};

use crate::fluent_generated as fluent;
use crate::lexer::UnmatchedBrace;
use crate::lexer::UnmatchedDelim;
use crate::parser;
use rustc_ast as ast;
use rustc_ast::ptr::P;
Expand Down Expand Up @@ -222,7 +222,7 @@ impl MultiSugg {
/// is dropped.
pub struct SnapshotParser<'a> {
parser: Parser<'a>,
unclosed_delims: Vec<UnmatchedBrace>,
unclosed_delims: Vec<UnmatchedDelim>,
}

impl<'a> Deref for SnapshotParser<'a> {
Expand Down Expand Up @@ -264,7 +264,7 @@ impl<'a> Parser<'a> {
self.unclosed_delims.extend(snapshot.unclosed_delims);
}

pub fn unclosed_delims(&self) -> &[UnmatchedBrace] {
pub fn unclosed_delims(&self) -> &[UnmatchedDelim] {
&self.unclosed_delims
}

Expand Down
10 changes: 5 additions & 5 deletions compiler/rustc_parse/src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod path;
mod stmt;
mod ty;

use crate::lexer::UnmatchedBrace;
use crate::lexer::UnmatchedDelim;
pub use attr_wrapper::AttrWrapper;
pub use diagnostics::AttemptLocalParseRecovery;
pub(crate) use item::FnParseMode;
Expand Down Expand Up @@ -149,7 +149,7 @@ pub struct Parser<'a> {
/// A list of all unclosed delimiters found by the lexer. If an entry is used for error recovery
/// it gets removed from here. Every entry left at the end gets emitted as an independent
/// error.
pub(super) unclosed_delims: Vec<UnmatchedBrace>,
pub(super) unclosed_delims: Vec<UnmatchedDelim>,
last_unexpected_token_span: Option<Span>,
/// Span pointing at the `:` for the last type ascription the parser has seen, and whether it
/// looked like it could have been a mistyped path or literal `Option:Some(42)`).
Expand Down Expand Up @@ -1521,11 +1521,11 @@ impl<'a> Parser<'a> {
}

pub(crate) fn make_unclosed_delims_error(
unmatched: UnmatchedBrace,
unmatched: UnmatchedDelim,
sess: &ParseSess,
) -> Option<DiagnosticBuilder<'_, ErrorGuaranteed>> {
// `None` here means an `Eof` was found. We already emit those errors elsewhere, we add them to
// `unmatched_braces` only for error recovery in the `Parser`.
// `unmatched_delims` only for error recovery in the `Parser`.
let found_delim = unmatched.found_delim?;
let mut spans = vec![unmatched.found_span];
if let Some(sp) = unmatched.unclosed_span {
Expand All @@ -1542,7 +1542,7 @@ pub(crate) fn make_unclosed_delims_error(
Some(err)
}

pub fn emit_unclosed_delims(unclosed_delims: &mut Vec<UnmatchedBrace>, sess: &ParseSess) {
pub fn emit_unclosed_delims(unclosed_delims: &mut Vec<UnmatchedDelim>, sess: &ParseSess) {
*sess.reached_eof.borrow_mut() |=
unclosed_delims.iter().any(|unmatched_delim| unmatched_delim.found_delim.is_none());
for unmatched in unclosed_delims.drain(..) {
Expand Down
14 changes: 3 additions & 11 deletions src/librustdoc/doctest.rs
Original file line number Diff line number Diff line change
Expand Up @@ -769,24 +769,16 @@ fn check_if_attr_is_complete(source: &str, edition: Edition) -> bool {
match maybe_new_parser_from_source_str(&sess, filename, source.to_owned()) {
Ok(p) => p,
Err(_) => {
debug!("Cannot build a parser to check mod attr so skipping...");
return true;
// If there is an unclosed delimiter, an error will be returned by the tokentrees.
return false;
}
};
// If a parsing error happened, it's very likely that the attribute is incomplete.
if let Err(e) = parser.parse_attribute(InnerAttrPolicy::Permitted) {
e.cancel();
return false;
}
// We now check if there is an unclosed delimiter for the attribute. To do so, we look at
// the `unclosed_delims` and see if the opening square bracket was closed.
parser
.unclosed_delims()
.get(0)
.map(|unclosed| {
unclosed.unclosed_span.map(|s| s.lo()).unwrap_or(BytePos(0)) != BytePos(2)
})
.unwrap_or(true)
true
})
})
.unwrap_or(false)
Expand Down
1 change: 0 additions & 1 deletion tests/ui/lint/issue-104897.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
// error-pattern: this file contains an unclosed delimiter
// error-pattern: this file contains an unclosed delimiter
// error-pattern: this file contains an unclosed delimiter
// error-pattern: format argument must be a string literal

fn f(){(print!(á
Loading