diff --git a/crates/ruff_python_parser/resources/invalid/expressions/unclosed_fstring_lbrace.py b/crates/ruff_python_parser/resources/invalid/expressions/unclosed_fstring_lbrace.py new file mode 100644 index 0000000000..338d16e549 --- /dev/null +++ b/crates/ruff_python_parser/resources/invalid/expressions/unclosed_fstring_lbrace.py @@ -0,0 +1,8 @@ +# TODO(dhruvmanila): Remove the dummy test case and uncomment the others when this is fixed. See PR #10372 +x + + +# f'{' +# f'{foo!r' +# f'{foo=' +# f"{" +# f"""{""" diff --git a/crates/ruff_python_parser/src/lexer.rs b/crates/ruff_python_parser/src/lexer.rs index 5078858c6d..f5b15d53ec 100644 --- a/crates/ruff_python_parser/src/lexer.rs +++ b/crates/ruff_python_parser/src/lexer.rs @@ -723,19 +723,6 @@ impl<'source> Lexer<'source> { let Some(index) = memchr::memchr(quote_byte, self.cursor.rest().as_bytes()) else { self.cursor.skip_to_end(); - if let Some(fstring) = self.fstrings.current() { - // 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() == kind.is_triple_quoted() - { - return Err(LexicalError::new( - LexicalErrorType::FStringError(FStringErrorType::UnclosedLbrace), - self.token_range(), - )); - } - } return Err(LexicalError::new( LexicalErrorType::UnclosedStringError, self.token_range(), @@ -772,19 +759,6 @@ impl<'source> Lexer<'source> { else { self.cursor.skip_to_end(); - if let Some(fstring) = self.fstrings.current() { - // 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() == kind.is_triple_quoted() - { - return Err(LexicalError::new( - LexicalErrorType::FStringError(FStringErrorType::UnclosedLbrace), - self.token_range(), - )); - } - } return Err(LexicalError::new( LexicalErrorType::StringError, self.token_range(), @@ -813,19 +787,6 @@ impl<'source> Lexer<'source> { match ch { Some('\r' | '\n') => { - if let Some(fstring) = self.fstrings.current() { - // 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() { - return Err(LexicalError::new( - LexicalErrorType::FStringError( - FStringErrorType::UnclosedLbrace, - ), - self.token_range(), - )); - } - } return Err(LexicalError::new( LexicalErrorType::UnclosedStringError, self.token_range(), @@ -2319,9 +2280,7 @@ f"{(lambda x:{x})}" #[test] fn test_fstring_error() { - use FStringErrorType::{ - SingleRbrace, UnclosedLbrace, UnterminatedString, UnterminatedTripleQuotedString, - }; + use FStringErrorType::{SingleRbrace, UnterminatedString, UnterminatedTripleQuotedString}; assert_eq!(lex_fstring_error("f'}'"), SingleRbrace); assert_eq!(lex_fstring_error("f'{{}'"), SingleRbrace); @@ -2331,17 +2290,6 @@ f"{(lambda x:{x})}" assert_eq!(lex_fstring_error("f'{a:b}}'"), SingleRbrace); assert_eq!(lex_fstring_error("f'{3:}}>10}'"), SingleRbrace); assert_eq!(lex_fstring_error(r"f'\{foo}\}'"), SingleRbrace); - assert_eq!(lex_fstring_error("f'{'"), UnclosedLbrace); - assert_eq!(lex_fstring_error("f'{foo!r'"), UnclosedLbrace); - assert_eq!(lex_fstring_error("f'{foo='"), UnclosedLbrace); - assert_eq!( - lex_fstring_error( - r#"f"{" -"# - ), - UnclosedLbrace - ); - assert_eq!(lex_fstring_error(r#"f"""{""""#), UnclosedLbrace); assert_eq!(lex_fstring_error(r#"f""#), UnterminatedString); assert_eq!(lex_fstring_error(r"f'"), UnterminatedString); @@ -2357,25 +2305,4 @@ f"{(lambda x:{x})}" UnterminatedTripleQuotedString ); } - - #[test] - fn test_fstring_error_location() { - assert_debug_snapshot!(lex_error("f'{'"), @r###" - LexicalError { - error: FStringError( - UnclosedLbrace, - ), - location: 3..4, - } - "###); - - assert_debug_snapshot!(lex_error("f'{'α"), @r###" - LexicalError { - error: FStringError( - UnclosedLbrace, - ), - location: 3..6, - } - "###); - } } diff --git a/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unclosed_fstring_lbrace.py.snap b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unclosed_fstring_lbrace.py.snap new file mode 100644 index 0000000000..f608aa9dbe --- /dev/null +++ b/crates/ruff_python_parser/tests/snapshots/invalid_syntax@expressions__unclosed_fstring_lbrace.py.snap @@ -0,0 +1,50 @@ +--- +source: crates/ruff_python_parser/tests/fixtures.rs +input_file: crates/ruff_python_parser/resources/invalid/expressions/unclosed_fstring_lbrace.py +--- +## AST + + ``` +Module( + ModModule { + range: 0..160, + body: [ + Expr( + StmtExpr { + range: 107..110, + value: BinOp( + ExprBinOp { + range: 107..110, + left: Name( + ExprName { + range: 107..108, + id: "x", + ctx: Load, + }, + ), + op: Add, + right: Name( + ExprName { + range: 110..110, + id: "", + ctx: Load, + }, + ), + }, + ), + }, + ), + ], + }, +) +``` +## Errors + + | +1 | # TODO(dhruvmanila): Remove the dummy test case and uncomment the others when this is fixed. See PR #10372 +2 | x + + | ^ Syntax Error: Expression expected. +3 | +4 | # f'{' +5 | # f'{foo!r' + |