From 9aa49139392f2db134adc9cfe15852f55c30b38c Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 10:50:04 -0500 Subject: [PATCH 01/13] add test case from #13824 --- crates/ruff_linter/src/rules/pyflakes/mod.rs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index c01397206f2be..519ef04036d69 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -4221,4 +4221,22 @@ lambda: fu &[], ); } + + #[test] + fn no_type_check_function() { + flakes( + r#" + from typing import no_type_check + + @no_type_check + def f821(arg: "A") -> "R": + pass + + @no_type_check + def f722(arg: "this isn't python") -> "this isn't python either": + pass + "#, + &[], + ); + } } From 3c5ceab687196acbaf4b1c08bd3ede1ba5a186db Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 10:41:11 -0500 Subject: [PATCH 02/13] skip visit_type_definition in a @no_type_check context --- crates/ruff_linter/src/checkers/ast/mod.rs | 10 ++++++++++ crates/ruff_python_semantic/src/model.rs | 2 ++ 2 files changed, 12 insertions(+) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 1f02f66f3dccb..773ee3de3381b 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -723,6 +723,13 @@ impl<'a> Visitor<'a> for Checker<'a> { // Visit the decorators and arguments, but avoid the body, which will be // deferred. for decorator in decorator_list { + if decorator + .expression + .as_name_expr() + .is_some_and(|name| name.id.as_str() == "no_type_check") + { + self.semantic.flags |= SemanticModelFlags::NO_TYPE_CHECK; + } self.visit_decorator(decorator); } @@ -1851,6 +1858,9 @@ impl<'a> Checker<'a> { /// Visit an [`Expr`], and treat it as a type definition. fn visit_type_definition(&mut self, expr: &'a Expr) { + if !(self.semantic.flags & SemanticModelFlags::NO_TYPE_CHECK).is_empty() { + return; + } let snapshot = self.semantic.flags; self.semantic.flags |= SemanticModelFlags::TYPE_DEFINITION; self.visit_expr(expr); diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 8f3235d7b0715..fe04f10e8b49a 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -2222,6 +2222,8 @@ bitflags! { /// [PEP 257]: https://peps.python.org/pep-0257/#what-is-a-docstring const ATTRIBUTE_DOCSTRING = 1 << 25; + const NO_TYPE_CHECK = 1 << 26; + /// The context is in any type annotation. const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits(); From 1b3ae3628983fe7258a7fb5c5f2eae021a1e3448 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 11:31:49 -0500 Subject: [PATCH 03/13] add docs to new flag --- crates/ruff_python_semantic/src/model.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index fe04f10e8b49a..0eabf141d562d 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -2222,6 +2222,21 @@ bitflags! { /// [PEP 257]: https://peps.python.org/pep-0257/#what-is-a-docstring const ATTRIBUTE_DOCSTRING = 1 << 25; + /// The model is in a [no_type_check] context. + /// + /// This is used to skip type checking when the `@no_type_check` decorator is found. + /// + /// For example (adapted from [#13824]): + /// ```python + /// from typing import no_type_check + /// + /// @no_type_check + /// def fn(arg: "A") -> "R": + /// pass + /// ``` + /// + /// [no_type_check]: https://docs.python.org/3/library/typing.html#typing.no_type_check + /// [#13824]: https://github.com/astral-sh/ruff/issues/13824 const NO_TYPE_CHECK = 1 << 26; /// The context is in any type annotation. From 530ddd2c10a6c3487f46d4b79d6338fdfb01dfca Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 11:32:04 -0500 Subject: [PATCH 04/13] use contains instead of manual check --- crates/ruff_linter/src/checkers/ast/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 773ee3de3381b..f927cbc7fc691 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -1858,7 +1858,11 @@ impl<'a> Checker<'a> { /// Visit an [`Expr`], and treat it as a type definition. fn visit_type_definition(&mut self, expr: &'a Expr) { - if !(self.semantic.flags & SemanticModelFlags::NO_TYPE_CHECK).is_empty() { + if self + .semantic + .flags + .contains(SemanticModelFlags::NO_TYPE_CHECK) + { return; } let snapshot = self.semantic.flags; From 10b2cacecdd22dc9c714dc6922fb28032ea0ec17 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 11:45:53 -0500 Subject: [PATCH 05/13] switch to snapshot test --- .../test/fixtures/pyflakes/{F722.py => F722_0.py} | 0 .../resources/test/fixtures/pyflakes/F722_1.py | 12 ++++++++++++ .../resources/test/fixtures/pyflakes/F821_30.py | 12 ++++++++++++ crates/ruff_linter/src/rules/pyflakes/mod.rs | 4 +++- ...ter__rules__pyflakes__tests__F722_F722_0.py.snap} | 4 ++-- ...nter__rules__pyflakes__tests__F722_F722_1.py.snap | 5 +++++ ...ter__rules__pyflakes__tests__F821_F821_30.py.snap | 5 +++++ 7 files changed, 39 insertions(+), 3 deletions(-) rename crates/ruff_linter/resources/test/fixtures/pyflakes/{F722.py => F722_0.py} (100%) create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py create mode 100644 crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py rename crates/ruff_linter/src/rules/pyflakes/snapshots/{ruff_linter__rules__pyflakes__tests__F722_F722.py.snap => ruff_linter__rules__pyflakes__tests__F722_F722_0.py.snap} (63%) create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722_1.py.snap create mode 100644 crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_30.py.snap diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_0.py similarity index 100% rename from crates/ruff_linter/resources/test/fixtures/pyflakes/F722.py rename to crates/ruff_linter/resources/test/fixtures/pyflakes/F722_0.py diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py new file mode 100644 index 0000000000000..88e2f8a585eaa --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py @@ -0,0 +1,12 @@ +"""Regression test for #13824. + +Don't report an error when the function being annotated has the +`@no_type_check` decorator. +""" + +from typing import no_type_check + + +@no_type_check +def f(arg: "this isn't python") -> "this isn't python either": + pass diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py new file mode 100644 index 0000000000000..260181fccbe17 --- /dev/null +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py @@ -0,0 +1,12 @@ +"""Regression test for #13824. + +Don't report an error when the function being annotated has the +`@no_type_check` decorator. +""" + +from typing import no_type_check + + +@no_type_check +def f(arg: "A") -> "R": + pass diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index 519ef04036d69..e7de2c43a7b97 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -94,7 +94,8 @@ mod tests { #[test_case(Rule::YieldOutsideFunction, Path::new("F704.py"))] #[test_case(Rule::ReturnOutsideFunction, Path::new("F706.py"))] #[test_case(Rule::DefaultExceptNotLast, Path::new("F707.py"))] - #[test_case(Rule::ForwardAnnotationSyntaxError, Path::new("F722.py"))] + #[test_case(Rule::ForwardAnnotationSyntaxError, Path::new("F722_0.py"))] + #[test_case(Rule::ForwardAnnotationSyntaxError, Path::new("F722_1.py"))] #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_0.py"))] #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_1.py"))] #[test_case(Rule::RedefinedWhileUnused, Path::new("F811_2.py"))] @@ -159,6 +160,7 @@ mod tests { #[test_case(Rule::UndefinedName, Path::new("F821_26.pyi"))] #[test_case(Rule::UndefinedName, Path::new("F821_27.py"))] #[test_case(Rule::UndefinedName, Path::new("F821_28.py"))] + #[test_case(Rule::UndefinedName, Path::new("F821_30.py"))] #[test_case(Rule::UndefinedExport, Path::new("F822_0.py"))] #[test_case(Rule::UndefinedExport, Path::new("F822_0.pyi"))] #[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))] 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_0.py.snap similarity index 63% rename from crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722.py.snap rename to crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722_0.py.snap index 3a41c4a06203c..0e45ac706c607 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_0.py.snap @@ -2,14 +2,14 @@ source: crates/ruff_linter/src/rules/pyflakes/mod.rs snapshot_kind: text --- -F722.py:9:12: F722 Syntax error in forward annotation: `///` +F722_0.py:9:12: F722 Syntax error in forward annotation: `///` | 9 | def g() -> "///": | ^^^^^ F722 10 | pass | -F722.py:13:4: F722 Syntax error in forward annotation: `List[int]☃` +F722_0.py:13:4: F722 Syntax error in forward annotation: `List[int]☃` | 13 | X: """List[int]"""'☃' = [] | ^^^^^^^^^^^^^^^^^^ F722 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 new file mode 100644 index 0000000000000..a487e4ddb80cf --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F722_F722_1.py.snap @@ -0,0 +1,5 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +snapshot_kind: text +--- + diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_30.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_30.py.snap new file mode 100644 index 0000000000000..a487e4ddb80cf --- /dev/null +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_30.py.snap @@ -0,0 +1,5 @@ +--- +source: crates/ruff_linter/src/rules/pyflakes/mod.rs +snapshot_kind: text +--- + From 3fac5c5442378f01e79208bae079f09ed1b6065f Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 11:50:12 -0500 Subject: [PATCH 06/13] Revert "add test case from #13824" This reverts commit 9aa49139392f2db134adc9cfe15852f55c30b38c. --- crates/ruff_linter/src/rules/pyflakes/mod.rs | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/crates/ruff_linter/src/rules/pyflakes/mod.rs b/crates/ruff_linter/src/rules/pyflakes/mod.rs index e7de2c43a7b97..7fb0f32673a8d 100644 --- a/crates/ruff_linter/src/rules/pyflakes/mod.rs +++ b/crates/ruff_linter/src/rules/pyflakes/mod.rs @@ -4223,22 +4223,4 @@ lambda: fu &[], ); } - - #[test] - fn no_type_check_function() { - flakes( - r#" - from typing import no_type_check - - @no_type_check - def f821(arg: "A") -> "R": - pass - - @no_type_check - def f722(arg: "this isn't python") -> "this isn't python either": - pass - "#, - &[], - ); - } } From 01f29a7054cb3a4f53c24ffae617d824b6ac7854 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 12:04:29 -0500 Subject: [PATCH 07/13] add failing tests for classes where no_type_check is also valid --- .../ruff_linter/resources/test/fixtures/pyflakes/F722_1.py | 6 ++++++ .../ruff_linter/resources/test/fixtures/pyflakes/F821_30.py | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py index 88e2f8a585eaa..c093290e5d328 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py @@ -10,3 +10,9 @@ @no_type_check def f(arg: "this isn't python") -> "this isn't python either": pass + + +@no_type_check +class C: + def f(arg: "this isn't python") -> "this isn't python either": + pass diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py index 260181fccbe17..7315b043c3da3 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py @@ -10,3 +10,9 @@ @no_type_check def f(arg: "A") -> "R": pass + + +@no_type_check +class C: + def f(self, arg: "B") -> "S": + pass From 06c1655c8f7ff9eab3895f65d92b6f39389918de Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 12:07:35 -0500 Subject: [PATCH 08/13] move no_type_check check into visit_decorator --- crates/ruff_linter/src/checkers/ast/mod.rs | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index f927cbc7fc691..01b2440d7d4b1 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -723,13 +723,6 @@ impl<'a> Visitor<'a> for Checker<'a> { // Visit the decorators and arguments, but avoid the body, which will be // deferred. for decorator in decorator_list { - if decorator - .expression - .as_name_expr() - .is_some_and(|name| name.id.as_str() == "no_type_check") - { - self.semantic.flags |= SemanticModelFlags::NO_TYPE_CHECK; - } self.visit_decorator(decorator); } @@ -1067,6 +1060,17 @@ impl<'a> Visitor<'a> for Checker<'a> { self.semantic.flags = flags_snapshot; } + fn visit_decorator(&mut self, decorator: &'a ruff_python_ast::Decorator) { + if decorator + .expression + .as_name_expr() + .is_some_and(|name| name.id.as_str() == "no_type_check") + { + self.semantic.flags |= SemanticModelFlags::NO_TYPE_CHECK; + } + visitor::walk_decorator(self, decorator); + } + fn visit_expr(&mut self, expr: &'a Expr) { // Step 0: Pre-processing if self.source_type.is_stub() From b367082642fb881515042b62d3de316830c3fa0b Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 12:16:34 -0500 Subject: [PATCH 09/13] add and use SemanticModel::in_no_type_check --- crates/ruff_linter/src/checkers/ast/mod.rs | 6 +----- crates/ruff_python_semantic/src/model.rs | 5 +++++ 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 01b2440d7d4b1..077261d203b88 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -1862,11 +1862,7 @@ impl<'a> Checker<'a> { /// Visit an [`Expr`], and treat it as a type definition. fn visit_type_definition(&mut self, expr: &'a Expr) { - if self - .semantic - .flags - .contains(SemanticModelFlags::NO_TYPE_CHECK) - { + if self.semantic.in_no_type_check() { return; } let snapshot = self.semantic.flags; diff --git a/crates/ruff_python_semantic/src/model.rs b/crates/ruff_python_semantic/src/model.rs index 0eabf141d562d..d2d4907881d81 100644 --- a/crates/ruff_python_semantic/src/model.rs +++ b/crates/ruff_python_semantic/src/model.rs @@ -1584,6 +1584,11 @@ impl<'a> SemanticModel<'a> { self.flags.intersects(SemanticModelFlags::ANNOTATION) } + /// Return `true` if the model is in a `@no_type_check` context. + pub const fn in_no_type_check(&self) -> bool { + self.flags.intersects(SemanticModelFlags::NO_TYPE_CHECK) + } + /// Return `true` if the model is in a typing-only type annotation. pub const fn in_typing_only_annotation(&self) -> bool { self.flags From 615b4b8732db8aaf89452e374b1cb6db165875ec Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 12:42:13 -0500 Subject: [PATCH 10/13] ensure no_type_check propagates into function bodies --- crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py | 4 ++-- .../ruff_linter/resources/test/fixtures/pyflakes/F821_30.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py index c093290e5d328..5d0ca18cbc634 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py @@ -9,10 +9,10 @@ @no_type_check def f(arg: "this isn't python") -> "this isn't python either": - pass + x: "this also isn't python" = 0 @no_type_check class C: def f(arg: "this isn't python") -> "this isn't python either": - pass + x: "this also isn't python" = 1 diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py index 7315b043c3da3..fffb87b6a33cb 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py @@ -9,10 +9,10 @@ @no_type_check def f(arg: "A") -> "R": - pass + x: "A" = 1 @no_type_check class C: def f(self, arg: "B") -> "S": - pass + x: "B" = 1 From a60b747c76b325784ac1fac29598fe5c01e97300 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 12:58:33 -0500 Subject: [PATCH 11/13] make sure typing.no_type_check works too --- .../ruff_linter/resources/test/fixtures/pyflakes/F821_30.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py index fffb87b6a33cb..d0e19594f8330 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py @@ -4,15 +4,15 @@ `@no_type_check` decorator. """ -from typing import no_type_check +import typing -@no_type_check +@typing.no_type_check def f(arg: "A") -> "R": x: "A" = 1 -@no_type_check +@typing.no_type_check class C: def f(self, arg: "B") -> "S": x: "B" = 1 From 350f6e3121ccea05b1396a8434b35025eb995327 Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 12:55:18 -0500 Subject: [PATCH 12/13] use `match_typing_expr` --- crates/ruff_linter/src/checkers/ast/mod.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index 077261d203b88..b33e0fcfaf55b 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -1061,10 +1061,9 @@ impl<'a> Visitor<'a> for Checker<'a> { } fn visit_decorator(&mut self, decorator: &'a ruff_python_ast::Decorator) { - if decorator - .expression - .as_name_expr() - .is_some_and(|name| name.id.as_str() == "no_type_check") + if self + .semantic + .match_typing_expr(&decorator.expression, "no_type_check") { self.semantic.flags |= SemanticModelFlags::NO_TYPE_CHECK; } From 1d5ee646f6ad0aaf352c0518f870f7504e12b5df Mon Sep 17 00:00:00 2001 From: Brent Westbrook Date: Tue, 26 Nov 2024 13:45:30 -0500 Subject: [PATCH 13/13] ignore `@no_type_check` for classes --- .../test/fixtures/pyflakes/F722_1.py | 3 +++ .../test/fixtures/pyflakes/F821_30.py | 3 +++ crates/ruff_linter/src/checkers/ast/mod.rs | 16 +++++-------- ...ules__pyflakes__tests__F722_F722_1.py.snap | 24 +++++++++++++++++++ ...les__pyflakes__tests__F821_F821_30.py.snap | 24 +++++++++++++++++++ 5 files changed, 60 insertions(+), 10 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py index 5d0ca18cbc634..203aa2b3ef269 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py @@ -2,6 +2,9 @@ Don't report an error when the function being annotated has the `@no_type_check` decorator. + +However, we still want to ignore this annotation on classes. See +https://github.com/python/typing/pull/1615/files and the discussion on #14615. """ from typing import no_type_check diff --git a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py index d0e19594f8330..d0c1b812b460e 100644 --- a/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py +++ b/crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py @@ -2,6 +2,9 @@ Don't report an error when the function being annotated has the `@no_type_check` decorator. + +However, we still want to ignore this annotation on classes. See +https://github.com/python/typing/pull/1615/files and the discussion on #14615. """ import typing diff --git a/crates/ruff_linter/src/checkers/ast/mod.rs b/crates/ruff_linter/src/checkers/ast/mod.rs index b33e0fcfaf55b..97154f4fccee5 100644 --- a/crates/ruff_linter/src/checkers/ast/mod.rs +++ b/crates/ruff_linter/src/checkers/ast/mod.rs @@ -723,6 +723,12 @@ impl<'a> Visitor<'a> for Checker<'a> { // Visit the decorators and arguments, but avoid the body, which will be // deferred. for decorator in decorator_list { + if self + .semantic + .match_typing_expr(&decorator.expression, "no_type_check") + { + self.semantic.flags |= SemanticModelFlags::NO_TYPE_CHECK; + } self.visit_decorator(decorator); } @@ -1060,16 +1066,6 @@ impl<'a> Visitor<'a> for Checker<'a> { self.semantic.flags = flags_snapshot; } - fn visit_decorator(&mut self, decorator: &'a ruff_python_ast::Decorator) { - if self - .semantic - .match_typing_expr(&decorator.expression, "no_type_check") - { - self.semantic.flags |= SemanticModelFlags::NO_TYPE_CHECK; - } - visitor::walk_decorator(self, decorator); - } - fn visit_expr(&mut self, expr: &'a Expr) { // Step 0: Pre-processing if self.source_type.is_stub() 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 a487e4ddb80cf..f79147a188710 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 @@ -2,4 +2,28 @@ source: crates/ruff_linter/src/rules/pyflakes/mod.rs snapshot_kind: text --- +F722_1.py:20:16: F722 Syntax error in forward annotation: `this isn't python` + | +18 | @no_type_check +19 | class C: +20 | def f(arg: "this isn't python") -> "this isn't python either": + | ^^^^^^^^^^^^^^^^^^^ F722 +21 | x: "this also isn't python" = 1 + | +F722_1.py:20:40: F722 Syntax error in forward annotation: `this isn't python either` + | +18 | @no_type_check +19 | class C: +20 | def f(arg: "this isn't python") -> "this isn't python either": + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ F722 +21 | x: "this also isn't python" = 1 + | + +F722_1.py:21:12: F722 Syntax error in forward annotation: `this also isn't python` + | +19 | class C: +20 | def f(arg: "this isn't python") -> "this isn't python either": +21 | x: "this also isn't python" = 1 + | ^^^^^^^^^^^^^^^^^^^^^^^^ F722 + | diff --git a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_30.py.snap b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_30.py.snap index a487e4ddb80cf..65b4a6cb293c7 100644 --- a/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_30.py.snap +++ b/crates/ruff_linter/src/rules/pyflakes/snapshots/ruff_linter__rules__pyflakes__tests__F821_F821_30.py.snap @@ -2,4 +2,28 @@ source: crates/ruff_linter/src/rules/pyflakes/mod.rs snapshot_kind: text --- +F821_30.py:20:23: F821 Undefined name `B` + | +18 | @typing.no_type_check +19 | class C: +20 | def f(self, arg: "B") -> "S": + | ^ F821 +21 | x: "B" = 1 + | +F821_30.py:20:31: F821 Undefined name `S` + | +18 | @typing.no_type_check +19 | class C: +20 | def f(self, arg: "B") -> "S": + | ^ F821 +21 | x: "B" = 1 + | + +F821_30.py:21:13: F821 Undefined name `B` + | +19 | class C: +20 | def f(self, arg: "B") -> "S": +21 | x: "B" = 1 + | ^ F821 + |