Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement runtime-import-in-type-checking-block (TYP004) #2146

Merged
merged 1 commit into from
Jan 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
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