Skip to content

Commit

Permalink
Implement runtime-import-in-type-checking-block (TYP004) (#2146)
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Jan 25, 2023
1 parent deff503 commit 0758049
Show file tree
Hide file tree
Showing 29 changed files with 364 additions and 40 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1202,6 +1202,7 @@ For more, see [flake8-type-checking](https://pypi.org/project/flake8-type-checki

| Code | Name | Message | Fix |
| ---- | ---- | ------- | --- |
| TYP004 | runtime-import-in-type-checking-block | Move import `{}` out of type-checking block. Import is used for more than type hinting. | |
| TYP005 | empty-type-checking-block | Found empty type-checking block | |

### tryceratops (TRY)
Expand Down
5 changes: 5 additions & 0 deletions resources/test/fixtures/flake8_type_checking/TYP004_1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from datetime import datetime
x = datetime
8 changes: 8 additions & 0 deletions resources/test/fixtures/flake8_type_checking/TYP004_2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from datetime import date


def example():
return date()
6 changes: 6 additions & 0 deletions resources/test/fixtures/flake8_type_checking/TYP004_3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Any

CustomType = Any
11 changes: 11 additions & 0 deletions resources/test/fixtures/flake8_type_checking/TYP004_4.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import TYPE_CHECKING, Type

if TYPE_CHECKING:
from typing import Any


def example(*args: Any, **kwargs: Any):
return


my_type: Type[Any] | Any
8 changes: 8 additions & 0 deletions resources/test/fixtures/flake8_type_checking/TYP004_5.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import List, Sequence, Set


def example(a: List[int], /, b: Sequence[int], *, c: Set[int]):
return
10 changes: 10 additions & 0 deletions resources/test/fixtures/flake8_type_checking/TYP004_6.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from pandas import DataFrame


def example():
from pandas import DataFrame

x = DataFrame
10 changes: 10 additions & 0 deletions resources/test/fixtures/flake8_type_checking/TYP004_7.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import AsyncIterator, List


class Example:
async def example(self) -> AsyncIterator[List[str]]:
yield 0
7 changes: 7 additions & 0 deletions resources/test/fixtures/flake8_type_checking/TYP004_8.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from typing import TYPE_CHECKING
from weakref import WeakKeyDictionary

if TYPE_CHECKING:
from typing import Any

d = WeakKeyDictionary["Any", "Any"]()
1 change: 1 addition & 0 deletions ruff.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -1784,6 +1784,7 @@
"TYP",
"TYP0",
"TYP00",
"TYP004",
"TYP005",
"UP",
"UP0",
Expand Down
35 changes: 33 additions & 2 deletions src/ast/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ impl Range {
pub fn from_located<T>(located: &Located<T>) -> Self {
Range::new(located.location, located.end_location.unwrap())
}

pub fn contains(&self, other: &Range) -> bool {
self.location <= other.location && self.end_location >= other.end_location
}
}

#[derive(Debug)]
Expand Down Expand Up @@ -130,8 +134,22 @@ pub struct Binding<'a> {
/// The statement in which the [`Binding`] was defined.
pub source: Option<RefEquality<'a, Stmt>>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used.
pub used: Option<(usize, Range)>,
/// the binding was last used in a runtime context.
pub runtime_usage: Option<(usize, Range)>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a typing-time context.
pub typing_usage: Option<(usize, Range)>,
/// Tuple of (scope index, range) indicating the scope and range at which
/// the binding was last used in a synthetic context. This is used for
/// (e.g.) `__future__` imports, explicit re-exports, and other bindings
/// that should be considered used even if they're never referenced.
pub synthetic_usage: Option<(usize, Range)>,
}

#[derive(Copy, Clone)]
pub enum UsageContext {
Runtime,
Typing,
}

// Pyflakes defines the following binding hierarchy (via inheritance):
Expand All @@ -152,6 +170,19 @@ pub struct Binding<'a> {
// FutureImportation

impl<'a> Binding<'a> {
pub fn mark_used(&mut self, scope: usize, range: Range, context: UsageContext) {
match context {
UsageContext::Runtime => self.runtime_usage = Some((scope, range)),
UsageContext::Typing => self.typing_usage = Some((scope, range)),
}
}

pub fn used(&self) -> bool {
self.runtime_usage.is_some()
|| self.synthetic_usage.is_some()
|| self.typing_usage.is_some()
}

pub fn is_definition(&self) -> bool {
matches!(
self.kind,
Expand Down

0 comments on commit 0758049

Please sign in to comment.