From f0d43dafcfa8932f072c4d8563b0bd722fba51cd Mon Sep 17 00:00:00 2001 From: Charlie Marsh Date: Thu, 4 Jan 2024 01:00:55 -0400 Subject: [PATCH] Ignore trailing quotes for unclosed l-brace errors (#9388) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Given: ```python F"{"ڤ ``` We try to locate the "unclosed left brace" error by subtracting the quote size from the lexer offset -- so we subtract 1 from the end of the source, which puts us in the middle of a Unicode character. I don't think we should try to adjust the offset in this way, since there can be content _after_ the quote. For example, with the advent of PEP 701, this string could reasonably be fixed as: ```python F"{"ڤ"}" ```` Closes https://github.com/astral-sh/ruff/issues/9379. --- crates/ruff_python_parser/src/lexer.rs | 45 ++++++++++++++----- .../ruff_python_parser/src/lexer/fstring.rs | 11 ----- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/crates/ruff_python_parser/src/lexer.rs b/crates/ruff_python_parser/src/lexer.rs index eb2a1f73f1..154a9a26bf 100644 --- a/crates/ruff_python_parser/src/lexer.rs +++ b/crates/ruff_python_parser/src/lexer.rs @@ -700,7 +700,7 @@ impl<'source> Lexer<'source> { } Some('\r' | '\n') if !triple_quoted => { if let Some(fstring) = self.fstrings.current() { - // When we are in an f-string, check whether does the initial quote + // When we are in an f-string, check whether the initial quote // matches with f-strings quotes and if it is, then this must be a // missing '}' token so raise the proper error. if fstring.quote_char() == quote && !fstring.is_triple_quoted() { @@ -708,7 +708,7 @@ impl<'source> Lexer<'source> { error: LexicalErrorType::FStringError( FStringErrorType::UnclosedLbrace, ), - location: self.offset() - fstring.quote_size(), + location: self.offset() - TextSize::new(1), }); } } @@ -732,7 +732,7 @@ impl<'source> Lexer<'source> { Some(_) => {} None => { if let Some(fstring) = self.fstrings.current() { - // When we are in an f-string, check whether does the initial quote + // When we are in an f-string, check whether the initial quote // matches with f-strings quotes and if it is, then this must be a // missing '}' token so raise the proper error. if fstring.quote_char() == quote @@ -742,7 +742,7 @@ impl<'source> Lexer<'source> { error: LexicalErrorType::FStringError( FStringErrorType::UnclosedLbrace, ), - location: self.offset() - fstring.quote_size(), + location: self.offset(), }); } } @@ -2195,13 +2195,17 @@ f"{(lambda x:{x})}" assert_debug_snapshot!(lex_jupyter_source(source)); } + fn lex_error(source: &str) -> LexicalError { + match lex(source, Mode::Module).find_map(Result::err) { + Some(err) => err, + _ => panic!("Expected at least one error"), + } + } + fn lex_fstring_error(source: &str) -> FStringErrorType { - match lex(source, Mode::Module).find_map(std::result::Result::err) { - Some(err) => match err.error { - LexicalErrorType::FStringError(error) => error, - _ => panic!("Expected FStringError: {err:?}"), - }, - _ => panic!("Expected atleast one FStringError"), + match lex_error(source).error { + LexicalErrorType::FStringError(error) => error, + err => panic!("Expected FStringError: {err:?}"), } } @@ -2246,4 +2250,25 @@ f"{(lambda x:{x})}" UnterminatedTripleQuotedString ); } + + #[test] + fn test_fstring_error_location() { + assert_debug_snapshot!(lex_error("f'{'"), @r###" + LexicalError { + error: FStringError( + UnclosedLbrace, + ), + location: 4, + } + "###); + + assert_debug_snapshot!(lex_error("f'{'α"), @r###" + LexicalError { + error: FStringError( + UnclosedLbrace, + ), + location: 6, + } + "###); + } } diff --git a/crates/ruff_python_parser/src/lexer/fstring.rs b/crates/ruff_python_parser/src/lexer/fstring.rs index 8acf00d52c..83a2965e43 100644 --- a/crates/ruff_python_parser/src/lexer/fstring.rs +++ b/crates/ruff_python_parser/src/lexer/fstring.rs @@ -1,7 +1,5 @@ use bitflags::bitflags; -use ruff_text_size::TextSize; - bitflags! { #[derive(Debug)] pub(crate) struct FStringContextFlags: u8 { @@ -58,15 +56,6 @@ impl FStringContext { } } - /// Returns the number of quotes for the current f-string. - pub(crate) const fn quote_size(&self) -> TextSize { - if self.is_triple_quoted() { - TextSize::new(3) - } else { - TextSize::new(1) - } - } - /// Returns the triple quotes for the current f-string if it is a triple-quoted /// f-string, `None` otherwise. pub(crate) const fn triple_quotes(&self) -> Option<&'static str> {