diff --git a/crates/red_knot_python_semantic/src/types/string_annotation.rs b/crates/red_knot_python_semantic/src/types/string_annotation.rs index a3c4dbd956d28..d900db4c7e4c7 100644 --- a/crates/red_knot_python_semantic/src/types/string_annotation.rs +++ b/crates/red_knot_python_semantic/src/types/string_annotation.rs @@ -153,9 +153,7 @@ pub(crate) fn parse_string_annotation( } else if raw_contents(node_text) .is_some_and(|raw_contents| raw_contents == string_literal.as_str()) { - let parsed = - ruff_python_parser::parse_string_annotation(source.as_str(), string_literal); - match parsed { + match ruff_python_parser::parse_string_annotation(source.as_str(), string_literal) { Ok(parsed) => return Some(parsed), Err(parse_error) => context.report_lint( &INVALID_SYNTAX_IN_FORWARD_ANNOTATION, diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index c78ab5ff48967..e456481f2cbcd 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -49,7 +49,7 @@ use ruff_python_ast::{helpers, str, visitor, PySourceType}; use ruff_python_codegen::{Generator, Stylist}; use ruff_python_index::Indexer; use ruff_python_parser::typing::{parse_type_annotation, AnnotationKind, ParsedAnnotation}; -use ruff_python_parser::{Parsed, Tokens}; +use ruff_python_parser::{ParseError, Parsed, Tokens}; use ruff_python_semantic::all::{DunderAllDefinition, DunderAllFlags}; use ruff_python_semantic::analyze::{imports, typing}; use ruff_python_semantic::{ @@ -234,7 +234,7 @@ impl<'a> Checker<'a> { #[allow(clippy::too_many_arguments)] pub(crate) fn new( parsed: &'a Parsed<ModModule>, - parsed_annotations_arena: &'a typed_arena::Arena<ParsedAnnotation>, + parsed_annotations_arena: &'a typed_arena::Arena<Result<ParsedAnnotation, ParseError>>, settings: &'a LinterSettings, noqa_line_for: &'a NoqaMapping, noqa: flags::Noqa, @@ -425,7 +425,7 @@ impl<'a> Checker<'a> { pub(crate) fn parse_type_annotation( &self, annotation: &ast::ExprStringLiteral, - ) -> Option<&'a ParsedAnnotation> { + ) -> Result<&'a ParsedAnnotation, &'a ParseError> { self.parsed_annotations_cache .lookup_or_parse(annotation, self.locator.contents()) } @@ -441,7 +441,7 @@ impl<'a> Checker<'a> { match_fn: impl FnOnce(&ast::Expr) -> bool, ) -> bool { if let ast::Expr::StringLiteral(string_annotation) = expr { - let Some(parsed_annotation) = self.parse_type_annotation(string_annotation) else { + let Some(parsed_annotation) = self.parse_type_annotation(string_annotation).ok() else { return false; }; match_fn(parsed_annotation.expression()) @@ -2318,58 +2318,66 @@ impl<'a> Checker<'a> { while !self.visit.string_type_definitions.is_empty() { let type_definitions = std::mem::take(&mut self.visit.string_type_definitions); for (string_expr, snapshot) in type_definitions { - let annotation_parse_result = self.parse_type_annotation(string_expr); - if let Some(parsed_annotation) = annotation_parse_result { - self.parsed_type_annotation = Some(parsed_annotation); + match self.parse_type_annotation(string_expr) { + Ok(parsed_annotation) => { + self.parsed_type_annotation = Some(parsed_annotation); - let annotation = string_expr.value.to_str(); - let range = string_expr.range(); + let annotation = string_expr.value.to_str(); + let range = string_expr.range(); - self.semantic.restore(snapshot); + self.semantic.restore(snapshot); - if self.semantic.in_annotation() && self.semantic.in_typing_only_annotation() { - if self.enabled(Rule::QuotedAnnotation) { - pyupgrade::rules::quoted_annotation(self, annotation, range); + if self.semantic.in_annotation() + && self.semantic.in_typing_only_annotation() + { + if self.enabled(Rule::QuotedAnnotation) { + pyupgrade::rules::quoted_annotation(self, annotation, range); + } } - } - if self.source_type.is_stub() { - if self.enabled(Rule::QuotedAnnotationInStub) { - flake8_pyi::rules::quoted_annotation_in_stub(self, annotation, range); + if self.source_type.is_stub() { + if self.enabled(Rule::QuotedAnnotationInStub) { + flake8_pyi::rules::quoted_annotation_in_stub( + self, annotation, range, + ); + } } - } - let type_definition_flag = match parsed_annotation.kind() { - AnnotationKind::Simple => SemanticModelFlags::SIMPLE_STRING_TYPE_DEFINITION, - AnnotationKind::Complex => { - SemanticModelFlags::COMPLEX_STRING_TYPE_DEFINITION - } - }; - - self.semantic.flags |= - SemanticModelFlags::TYPE_DEFINITION | type_definition_flag; - let parsed_expr = parsed_annotation.expression(); - self.visit_expr(parsed_expr); - if self.semantic.in_type_alias_value() { - // stub files are covered by PYI020 - if !self.source_type.is_stub() && self.enabled(Rule::QuotedTypeAlias) { - flake8_type_checking::rules::quoted_type_alias( - self, - parsed_expr, - string_expr, - ); + let type_definition_flag = match parsed_annotation.kind() { + AnnotationKind::Simple => { + SemanticModelFlags::SIMPLE_STRING_TYPE_DEFINITION + } + AnnotationKind::Complex => { + SemanticModelFlags::COMPLEX_STRING_TYPE_DEFINITION + } + }; + + self.semantic.flags |= + SemanticModelFlags::TYPE_DEFINITION | type_definition_flag; + let parsed_expr = parsed_annotation.expression(); + self.visit_expr(parsed_expr); + if self.semantic.in_type_alias_value() { + // stub files are covered by PYI020 + if !self.source_type.is_stub() && self.enabled(Rule::QuotedTypeAlias) { + flake8_type_checking::rules::quoted_type_alias( + self, + parsed_expr, + string_expr, + ); + } } + self.parsed_type_annotation = None; } - self.parsed_type_annotation = None; - } else { - self.semantic.restore(snapshot); - - if self.enabled(Rule::ForwardAnnotationSyntaxError) { - self.push_type_diagnostic(Diagnostic::new( - pyflakes::rules::ForwardAnnotationSyntaxError { - body: string_expr.value.to_string(), - }, - string_expr.range(), - )); + Err(parse_error) => { + self.semantic.restore(snapshot); + + if self.enabled(Rule::ForwardAnnotationSyntaxError) { + self.push_type_diagnostic(Diagnostic::new( + pyflakes::rules::ForwardAnnotationSyntaxError { + parse_error: parse_error.error.to_string(), + }, + string_expr.range(), + )); + } } } } @@ -2541,12 +2549,12 @@ impl<'a> Checker<'a> { } struct ParsedAnnotationsCache<'a> { - arena: &'a typed_arena::Arena<ParsedAnnotation>, - by_offset: RefCell<FxHashMap<TextSize, Option<&'a ParsedAnnotation>>>, + arena: &'a typed_arena::Arena<Result<ParsedAnnotation, ParseError>>, + by_offset: RefCell<FxHashMap<TextSize, Result<&'a ParsedAnnotation, &'a ParseError>>>, } impl<'a> ParsedAnnotationsCache<'a> { - fn new(arena: &'a typed_arena::Arena<ParsedAnnotation>) -> Self { + fn new(arena: &'a typed_arena::Arena<Result<ParsedAnnotation, ParseError>>) -> Self { Self { arena, by_offset: RefCell::default(), @@ -2557,17 +2565,15 @@ impl<'a> ParsedAnnotationsCache<'a> { &self, annotation: &ast::ExprStringLiteral, source: &str, - ) -> Option<&'a ParsedAnnotation> { + ) -> Result<&'a ParsedAnnotation, &'a ParseError> { *self .by_offset .borrow_mut() .entry(annotation.start()) .or_insert_with(|| { - if let Ok(annotation) = parse_type_annotation(annotation, source) { - Some(self.arena.alloc(annotation)) - } else { - None - } + self.arena + .alloc(parse_type_annotation(annotation, source)) + .as_ref() }) } diff --git a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs index cad9679a559aa..b1ff9f4f14a28 100644 --- a/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs +++ b/crates/ruff_linter/src/rules/flake8_annotations/rules/definition.rs @@ -519,7 +519,7 @@ fn check_dynamically_typed<F>( { if let Expr::StringLiteral(string_expr) = annotation { // Quoted annotations - if let Some(parsed_annotation) = checker.parse_type_annotation(string_expr) { + if let Ok(parsed_annotation) = checker.parse_type_annotation(string_expr) { if type_hint_resolves_to_any( parsed_annotation.expression(), checker, diff --git a/crates/ruff_linter/src/rules/pyflakes/rules/forward_annotation_syntax_error.rs b/crates/ruff_linter/src/rules/pyflakes/rules/forward_annotation_syntax_error.rs index 18852477563b8..dd4a2a59a5ce6 100644 --- a/crates/ruff_linter/src/rules/pyflakes/rules/forward_annotation_syntax_error.rs +++ b/crates/ruff_linter/src/rules/pyflakes/rules/forward_annotation_syntax_error.rs @@ -24,13 +24,13 @@ use ruff_macros::{derive_message_formats, ViolationMetadata}; /// - [PEP 563 – Postponed Evaluation of Annotations](https://peps.python.org/pep-0563/) #[derive(ViolationMetadata)] pub(crate) struct ForwardAnnotationSyntaxError { - pub body: String, + pub parse_error: String, } impl Violation for ForwardAnnotationSyntaxError { #[derive_message_formats] fn message(&self) -> String { - let ForwardAnnotationSyntaxError { body } = self; - format!("Syntax error in forward annotation: `{body}`") + let ForwardAnnotationSyntaxError { parse_error } = self; + format!("Syntax error in forward annotation: {parse_error}") } } 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 05de73ad5ca72..54646f4ff37d1 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 @@ -1,15 +1,14 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs -snapshot_kind: text --- -F722.py:9:12: F722 Syntax error in forward annotation: `///` +F722.py:9:12: F722 Syntax error in forward annotation: Expected an expression | 9 | def g() -> "///": | ^^^^^ F722 10 | pass | -F722.py:13:4: F722 Syntax error in forward annotation: `List[int]☃` +F722.py:13:4: F722 Syntax error in forward annotation: Got unexpected token ☃ | 13 | X: """List[int]"""'☃' = [] | ^^^^^^^^^^^^^^^^^^ F722 @@ -17,10 +16,7 @@ F722.py:13:4: F722 Syntax error in forward annotation: `List[int]☃` 15 | # Type annotations with triple quotes can contain newlines and indentation | -F722.py:30:11: F722 Syntax error in forward annotation: ` - int | -str) -` +F722.py:30:11: F722 Syntax error in forward annotation: Unexpected token at the end of an expression | 28 | """ 29 | @@ -34,10 +30,7 @@ str) 35 | invalid2: """ | -F722.py:35:11: F722 Syntax error in forward annotation: ` - int) | -str -` +F722.py:35:11: F722 Syntax error in forward annotation: Unexpected token at the end of an expression | 33 | """ 34 | @@ -51,9 +44,7 @@ str 40 | ((int) | -F722.py:39:11: F722 Syntax error in forward annotation: ` - ((int) -` +F722.py:39:11: F722 Syntax error in forward annotation: unexpected EOF while parsing | 37 | str 38 | """ @@ -66,9 +57,7 @@ F722.py:39:11: F722 Syntax error in forward annotation: ` 43 | (int | -F722.py:42:11: F722 Syntax error in forward annotation: ` - (int -` +F722.py:42:11: F722 Syntax error in forward annotation: unexpected EOF while parsing | 40 | ((int) 41 | """ diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722_1.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722_1.py.snap index c7c943161e0b3..1fd4f43abd0a5 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722_1.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722_1.py.snap @@ -1,8 +1,7 @@ --- source: crates/ruff_linter/src/rules/pyflakes/mod.rs -snapshot_kind: text --- -F722_1.py:8:22: F722 Syntax error in forward annotation: `this isn't python` +F722_1.py:8:22: F722 Syntax error in forward annotation: Unexpected token at the end of an expression | 6 | @no_type_check 7 | class C: @@ -11,7 +10,7 @@ F722_1.py:8:22: F722 Syntax error in forward annotation: `this isn't python` 9 | x: "this also isn't python" = 1 | -F722_1.py:8:46: F722 Syntax error in forward annotation: `this isn't python either` +F722_1.py:8:46: F722 Syntax error in forward annotation: Unexpected token at the end of an expression | 6 | @no_type_check 7 | class C: @@ -20,7 +19,7 @@ F722_1.py:8:46: F722 Syntax error in forward annotation: `this isn't python eith 9 | x: "this also isn't python" = 1 | -F722_1.py:9:12: F722 Syntax error in forward annotation: `this also isn't python` +F722_1.py:9:12: F722 Syntax error in forward annotation: Unexpected token at the end of an expression | 7 | class C: 8 | def f(self, arg: "this isn't python") -> "this isn't python either": diff --git a/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs b/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs index 718a39d4093b1..69af7d4bc1922 100644 --- a/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs +++ b/crates/ruff_linter/src/rules/ruff/rules/implicit_optional.rs @@ -179,7 +179,7 @@ pub(crate) fn implicit_optional(checker: &mut Checker, parameters: &Parameters) if let Expr::StringLiteral(string_expr) = annotation.as_ref() { // Quoted annotation. - if let Some(parsed_annotation) = checker.parse_type_annotation(string_expr) { + if let Ok(parsed_annotation) = checker.parse_type_annotation(string_expr) { let Some(expr) = type_hint_explicitly_allows_none( parsed_annotation.expression(), checker, diff --git a/crates/ruff_linter/src/rules/ruff/typing.rs b/crates/ruff_linter/src/rules/ruff/typing.rs index f7ed900719e0e..3faea2fa86bec 100644 --- a/crates/ruff_linter/src/rules/ruff/typing.rs +++ b/crates/ruff_linter/src/rules/ruff/typing.rs @@ -109,7 +109,7 @@ impl<'a> TypingTarget<'a> { Expr::NoneLiteral(_) => Some(TypingTarget::None), Expr::StringLiteral(string_expr) => checker .parse_type_annotation(string_expr) - .as_ref() + .ok() .map(|parsed_annotation| { TypingTarget::ForwardReference(parsed_annotation.expression()) }),