mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 20:09:22 +00:00
[pyflakes] Avoid false positives in @no_type_check
contexts (F821, F722) (#14615)
This commit is contained in:
parent
b94d6cf567
commit
9f446faa6c
9 changed files with 136 additions and 3 deletions
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py
vendored
Normal file
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F722_1.py
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"""Regression test for #13824.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@no_type_check
|
||||||
|
def f(arg: "this isn't python") -> "this isn't python either":
|
||||||
|
x: "this also isn't python" = 0
|
||||||
|
|
||||||
|
|
||||||
|
@no_type_check
|
||||||
|
class C:
|
||||||
|
def f(arg: "this isn't python") -> "this isn't python either":
|
||||||
|
x: "this also isn't python" = 1
|
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py
vendored
Normal file
21
crates/ruff_linter/resources/test/fixtures/pyflakes/F821_30.py
vendored
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
"""Regression test for #13824.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
@typing.no_type_check
|
||||||
|
def f(arg: "A") -> "R":
|
||||||
|
x: "A" = 1
|
||||||
|
|
||||||
|
|
||||||
|
@typing.no_type_check
|
||||||
|
class C:
|
||||||
|
def f(self, arg: "B") -> "S":
|
||||||
|
x: "B" = 1
|
|
@ -723,6 +723,12 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
// Visit the decorators and arguments, but avoid the body, which will be
|
// Visit the decorators and arguments, but avoid the body, which will be
|
||||||
// deferred.
|
// deferred.
|
||||||
for decorator in decorator_list {
|
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);
|
self.visit_decorator(decorator);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1851,6 +1857,9 @@ impl<'a> Checker<'a> {
|
||||||
|
|
||||||
/// Visit an [`Expr`], and treat it as a type definition.
|
/// Visit an [`Expr`], and treat it as a type definition.
|
||||||
fn visit_type_definition(&mut self, expr: &'a Expr) {
|
fn visit_type_definition(&mut self, expr: &'a Expr) {
|
||||||
|
if self.semantic.in_no_type_check() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
let snapshot = self.semantic.flags;
|
let snapshot = self.semantic.flags;
|
||||||
self.semantic.flags |= SemanticModelFlags::TYPE_DEFINITION;
|
self.semantic.flags |= SemanticModelFlags::TYPE_DEFINITION;
|
||||||
self.visit_expr(expr);
|
self.visit_expr(expr);
|
||||||
|
|
|
@ -94,7 +94,8 @@ mod tests {
|
||||||
#[test_case(Rule::YieldOutsideFunction, Path::new("F704.py"))]
|
#[test_case(Rule::YieldOutsideFunction, Path::new("F704.py"))]
|
||||||
#[test_case(Rule::ReturnOutsideFunction, Path::new("F706.py"))]
|
#[test_case(Rule::ReturnOutsideFunction, Path::new("F706.py"))]
|
||||||
#[test_case(Rule::DefaultExceptNotLast, Path::new("F707.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_0.py"))]
|
||||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_1.py"))]
|
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_1.py"))]
|
||||||
#[test_case(Rule::RedefinedWhileUnused, Path::new("F811_2.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_26.pyi"))]
|
||||||
#[test_case(Rule::UndefinedName, Path::new("F821_27.py"))]
|
#[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_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.py"))]
|
||||||
#[test_case(Rule::UndefinedExport, Path::new("F822_0.pyi"))]
|
#[test_case(Rule::UndefinedExport, Path::new("F822_0.pyi"))]
|
||||||
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
|
#[test_case(Rule::UndefinedExport, Path::new("F822_1.py"))]
|
||||||
|
|
|
@ -2,14 +2,14 @@
|
||||||
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
source: crates/ruff_linter/src/rules/pyflakes/mod.rs
|
||||||
snapshot_kind: text
|
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() -> "///":
|
9 | def g() -> "///":
|
||||||
| ^^^^^ F722
|
| ^^^^^ F722
|
||||||
10 | pass
|
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]"""'☃' = []
|
13 | X: """List[int]"""'☃' = []
|
||||||
| ^^^^^^^^^^^^^^^^^^ F722
|
| ^^^^^^^^^^^^^^^^^^ F722
|
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
---
|
||||||
|
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
|
||||||
|
|
|
|
@ -1584,6 +1584,11 @@ impl<'a> SemanticModel<'a> {
|
||||||
self.flags.intersects(SemanticModelFlags::ANNOTATION)
|
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.
|
/// Return `true` if the model is in a typing-only type annotation.
|
||||||
pub const fn in_typing_only_annotation(&self) -> bool {
|
pub const fn in_typing_only_annotation(&self) -> bool {
|
||||||
self.flags
|
self.flags
|
||||||
|
@ -2222,6 +2227,23 @@ bitflags! {
|
||||||
/// [PEP 257]: https://peps.python.org/pep-0257/#what-is-a-docstring
|
/// [PEP 257]: https://peps.python.org/pep-0257/#what-is-a-docstring
|
||||||
const ATTRIBUTE_DOCSTRING = 1 << 25;
|
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.
|
/// The context is in any type annotation.
|
||||||
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();
|
const ANNOTATION = Self::TYPING_ONLY_ANNOTATION.bits() | Self::RUNTIME_EVALUATED_ANNOTATION.bits() | Self::RUNTIME_REQUIRED_ANNOTATION.bits();
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue