From efbd25e68871671f88435c76c8c4600bfc47a3d4 Mon Sep 17 00:00:00 2001 From: Glyphack Date: Fri, 10 Jan 2025 00:26:14 +0100 Subject: [PATCH] Parse Quoted annotations as if parenthesized --- .../resources/test/fixtures/pyflakes/F722.py | 15 +++++++++++++++ ...er__rules__pyflakes__tests__F722_F722.py.snap | 2 ++ crates/ruff_python_parser/src/lexer.rs | 16 ++++++++++++++-- crates/ruff_python_parser/src/lib.rs | 12 ++++++++++++ crates/ruff_python_parser/src/parser/mod.rs | 4 +++- crates/ruff_python_parser/src/typing.rs | 4 ++-- 6 files changed, 48 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722.py index 35231d60af0fa..6ee240927d406 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722.py @@ -11,3 +11,18 @@ def g() -> "///": X: """List[int]"""'☃' = [] + +y: """ + + int | + str + +""" + +z: """( + + int | + str + +) +""" diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722.py.snap index 3a41c4a06203c..44141349315c9 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722.py.snap @@ -13,4 +13,6 @@ F722.py:13:4: F722 Syntax error in forward annotation: `List[int]☃` | 13 | X: """List[int]"""'☃' = [] | ^^^^^^^^^^^^^^^^^^ F722 +14 | +15 | y: """ | diff --git a/crates/ruff_python_parser/src/lexer.rs b/crates/ruff_python_parser/src/lexer.rs index 4bc4bb43194ac..742a841262fab 100644 --- a/crates/ruff_python_parser/src/lexer.rs +++ b/crates/ruff_python_parser/src/lexer.rs @@ -84,15 +84,22 @@ impl<'src> Lexer<'src> { "Lexer only supports files with a size up to 4GB" ); + let nesting = u32::from(mode == Mode::ParenthesizedExpression); + let state = if mode == Mode::ParenthesizedExpression { + State::Other + } else { + State::AfterNewline + }; + let mut lexer = Lexer { source, cursor: Cursor::new(source), - state: State::AfterNewline, + state, current_kind: TokenKind::EndOfFile, current_range: TextRange::empty(start_offset), current_value: TokenValue::None, current_flags: TokenFlags::empty(), - nesting: 0, + nesting, indentations: Indentations::default(), pending_indentation: None, mode, @@ -1307,6 +1314,11 @@ impl<'src> Lexer<'src> { } fn consume_end(&mut self) -> TokenKind { + // For Mode::ParenthesizedExpression we start with nesting level 1. + // So remove that extra nesting before checks. + if self.mode == Mode::ParenthesizedExpression { + self.nesting = self.nesting.saturating_sub(1); + } // We reached end of file. // First of all, we need all nestings to be finished. if self.nesting > 0 { diff --git a/crates/ruff_python_parser/src/lib.rs b/crates/ruff_python_parser/src/lib.rs index df11678118000..48a2a380d41a8 100644 --- a/crates/ruff_python_parser/src/lib.rs +++ b/crates/ruff_python_parser/src/lib.rs @@ -166,6 +166,16 @@ pub fn parse_expression_range( .into_result() } +pub fn parse_parenthesized_expression_range( + source: &str, + range: TextRange, +) -> Result, ParseError> { + let source = &source[..range.end().to_usize()]; + let parsed = + Parser::new_starts_at(source, Mode::ParenthesizedExpression, range.start()).parse(); + parsed.try_into_expression().unwrap().into_result() +} + /// Parse the given Python source code using the specified [`Mode`]. /// /// This function is the most general function to parse Python code. Based on the [`Mode`] supplied, @@ -599,6 +609,8 @@ pub enum Mode { /// [System shell access]: https://ipython.readthedocs.io/en/stable/interactive/reference.html#system-shell-access /// [Automatic parentheses and quotes]: https://ipython.readthedocs.io/en/stable/interactive/reference.html#automatic-parentheses-and-quotes Ipython, + + ParenthesizedExpression, } impl std::str::FromStr for Mode { diff --git a/crates/ruff_python_parser/src/parser/mod.rs b/crates/ruff_python_parser/src/parser/mod.rs index 08c85f7a07148..d4528c8c3c4a0 100644 --- a/crates/ruff_python_parser/src/parser/mod.rs +++ b/crates/ruff_python_parser/src/parser/mod.rs @@ -74,7 +74,9 @@ impl<'src> Parser<'src> { /// Consumes the [`Parser`] and returns the parsed [`Parsed`]. pub(crate) fn parse(mut self) -> Parsed { let syntax = match self.mode { - Mode::Expression => Mod::Expression(self.parse_single_expression()), + Mode::Expression | Mode::ParenthesizedExpression => { + Mod::Expression(self.parse_single_expression()) + } Mode::Module | Mode::Ipython => Mod::Module(self.parse_module()), }; diff --git a/crates/ruff_python_parser/src/typing.rs b/crates/ruff_python_parser/src/typing.rs index 76e25ac880bd2..210662a8d7a9e 100644 --- a/crates/ruff_python_parser/src/typing.rs +++ b/crates/ruff_python_parser/src/typing.rs @@ -5,7 +5,7 @@ use ruff_python_ast::str::raw_contents; use ruff_python_ast::{Expr, ExprStringLiteral, ModExpression, StringFlags, StringLiteral}; use ruff_text_size::Ranged; -use crate::{parse_expression, parse_expression_range, ParseError, Parsed}; +use crate::{parse_expression, parse_parenthesized_expression_range, ParseError, Parsed}; type AnnotationParseResult = Result; @@ -86,7 +86,7 @@ fn parse_simple_type_annotation( .add_start(string_literal.flags.opener_len()) .sub_end(string_literal.flags.closer_len()); Ok(ParsedAnnotation { - parsed: parse_expression_range(source, range_excluding_quotes)?, + parsed: parse_parenthesized_expression_range(source, range_excluding_quotes)?, kind: AnnotationKind::Simple, }) }