mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-15 16:10:17 +00:00
[flake8-type-checking
] Adds implementation for TC007 and TC008 (#12927)
Co-authored-by: Simon Brugman <sbrugman@users.noreply.github.com> Co-authored-by: Carl Meyer <carl@oddbird.net>
This commit is contained in:
parent
e0f3eaf1dd
commit
6fd10e2fe7
22 changed files with 1483 additions and 10 deletions
31
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC007.py
vendored
Normal file
31
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC007.py
vendored
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import Dict, TypeAlias, TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
from foo import Foo
|
||||||
|
|
||||||
|
OptStr: TypeAlias = str | None
|
||||||
|
Bar: TypeAlias = Foo[int]
|
||||||
|
|
||||||
|
a: TypeAlias = int # OK
|
||||||
|
b: TypeAlias = Dict # OK
|
||||||
|
c: TypeAlias = Foo # TC007
|
||||||
|
d: TypeAlias = Foo | None # TC007
|
||||||
|
e: TypeAlias = OptStr # TC007
|
||||||
|
f: TypeAlias = Bar # TC007
|
||||||
|
g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
h: TypeAlias = Foo[str] # TC007
|
||||||
|
i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
Bar)
|
||||||
|
|
||||||
|
type C = Foo # OK
|
||||||
|
type D = Foo | None # OK
|
||||||
|
type E = OptStr # OK
|
||||||
|
type F = Bar # OK
|
||||||
|
type G = Foo | Bar # OK
|
||||||
|
type H = Foo[str] # OK
|
||||||
|
type I = (Foo | # OK
|
||||||
|
Bar)
|
52
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC008.py
vendored
Normal file
52
crates/ruff_linter/resources/test/fixtures/flake8_type_checking/TC008.py
vendored
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TypeAlias, TYPE_CHECKING
|
||||||
|
|
||||||
|
from foo import Foo
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
|
OptStr: TypeAlias = str | None
|
||||||
|
Bar: TypeAlias = Foo[int]
|
||||||
|
else:
|
||||||
|
Bar = Foo
|
||||||
|
|
||||||
|
a: TypeAlias = 'int' # TC008
|
||||||
|
b: TypeAlias = 'Dict' # OK
|
||||||
|
c: TypeAlias = 'Foo' # TC008
|
||||||
|
d: TypeAlias = 'Foo[str]' # OK
|
||||||
|
e: TypeAlias = 'Foo.bar' # OK
|
||||||
|
f: TypeAlias = 'Foo | None' # TC008
|
||||||
|
g: TypeAlias = 'OptStr' # OK
|
||||||
|
h: TypeAlias = 'Bar' # TC008
|
||||||
|
i: TypeAlias = Foo['str'] # TC008
|
||||||
|
j: TypeAlias = 'Baz' # OK
|
||||||
|
k: TypeAlias = 'k | None' # OK
|
||||||
|
l: TypeAlias = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
m: TypeAlias = ('int' # TC008
|
||||||
|
| None)
|
||||||
|
n: TypeAlias = ('int' # TC008 (fix removes comment currently)
|
||||||
|
' | None')
|
||||||
|
|
||||||
|
type B = 'Dict' # TC008
|
||||||
|
type D = 'Foo[str]' # TC008
|
||||||
|
type E = 'Foo.bar' # TC008
|
||||||
|
type G = 'OptStr' # TC008
|
||||||
|
type I = Foo['str'] # TC008
|
||||||
|
type J = 'Baz' # TC008
|
||||||
|
type K = 'K | None' # TC008
|
||||||
|
type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
type M = ('int' # TC008
|
||||||
|
| None)
|
||||||
|
type N = ('int' # TC008 (fix removes comment currently)
|
||||||
|
' | None')
|
||||||
|
|
||||||
|
|
||||||
|
class Baz:
|
||||||
|
a: TypeAlias = 'Baz' # OK
|
||||||
|
type A = 'Baz' # TC008
|
||||||
|
|
||||||
|
class Nested:
|
||||||
|
a: TypeAlias = 'Baz' # OK
|
||||||
|
type A = 'Baz' # TC008
|
|
@ -101,3 +101,12 @@ def f():
|
||||||
|
|
||||||
def test_annotated_non_typing_reference(user: Annotated[str, Depends(get_foo)]):
|
def test_annotated_non_typing_reference(user: Annotated[str, Depends(get_foo)]):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def f():
|
||||||
|
from typing import TypeAlias, TYPE_CHECKING
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from pandas import DataFrame
|
||||||
|
|
||||||
|
x: TypeAlias = DataFrame | None
|
||||||
|
|
|
@ -3,7 +3,9 @@ use ruff_text_size::Ranged;
|
||||||
|
|
||||||
use crate::checkers::ast::Checker;
|
use crate::checkers::ast::Checker;
|
||||||
use crate::codes::Rule;
|
use crate::codes::Rule;
|
||||||
use crate::rules::{flake8_import_conventions, flake8_pyi, pyflakes, pylint, ruff};
|
use crate::rules::{
|
||||||
|
flake8_import_conventions, flake8_pyi, flake8_type_checking, pyflakes, pylint, ruff,
|
||||||
|
};
|
||||||
|
|
||||||
/// Run lint rules over the [`Binding`]s.
|
/// Run lint rules over the [`Binding`]s.
|
||||||
pub(crate) fn bindings(checker: &mut Checker) {
|
pub(crate) fn bindings(checker: &mut Checker) {
|
||||||
|
@ -15,6 +17,7 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||||
Rule::UnconventionalImportAlias,
|
Rule::UnconventionalImportAlias,
|
||||||
Rule::UnsortedDunderSlots,
|
Rule::UnsortedDunderSlots,
|
||||||
Rule::UnusedVariable,
|
Rule::UnusedVariable,
|
||||||
|
Rule::UnquotedTypeAlias,
|
||||||
]) {
|
]) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -72,6 +75,13 @@ pub(crate) fn bindings(checker: &mut Checker) {
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if checker.enabled(Rule::UnquotedTypeAlias) {
|
||||||
|
if let Some(diagnostics) =
|
||||||
|
flake8_type_checking::rules::unquoted_type_alias(checker, binding)
|
||||||
|
{
|
||||||
|
checker.diagnostics.extend(diagnostics);
|
||||||
|
}
|
||||||
|
}
|
||||||
if checker.enabled(Rule::UnsortedDunderSlots) {
|
if checker.enabled(Rule::UnsortedDunderSlots) {
|
||||||
if let Some(diagnostic) = ruff::rules::sort_dunder_slots(checker, binding) {
|
if let Some(diagnostic) = ruff::rules::sort_dunder_slots(checker, binding) {
|
||||||
checker.diagnostics.push(diagnostic);
|
checker.diagnostics.push(diagnostic);
|
||||||
|
|
|
@ -52,6 +52,8 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
// Identify any valid runtime imports. If a module is imported at runtime, and
|
// Identify any valid runtime imports. If a module is imported at runtime, and
|
||||||
// used at runtime, then by default, we avoid flagging any other
|
// used at runtime, then by default, we avoid flagging any other
|
||||||
// imports from that model as typing-only.
|
// imports from that model as typing-only.
|
||||||
|
// FIXME: This does not seem quite right, if only TC004 is enabled
|
||||||
|
// then we don't need to collect the runtime imports
|
||||||
let enforce_typing_imports = !checker.source_type.is_stub()
|
let enforce_typing_imports = !checker.source_type.is_stub()
|
||||||
&& checker.any_enabled(&[
|
&& checker.any_enabled(&[
|
||||||
Rule::RuntimeImportInTypeCheckingBlock,
|
Rule::RuntimeImportInTypeCheckingBlock,
|
||||||
|
@ -375,6 +377,8 @@ pub(crate) fn deferred_scopes(checker: &mut Checker) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
|
if matches!(scope.kind, ScopeKind::Function(_) | ScopeKind::Module) {
|
||||||
|
// FIXME: This does not seem quite right, if only TC004 is enabled
|
||||||
|
// then we don't need to collect the runtime imports
|
||||||
if enforce_typing_imports {
|
if enforce_typing_imports {
|
||||||
let runtime_imports: Vec<&Binding> = checker
|
let runtime_imports: Vec<&Binding> = checker
|
||||||
.semantic
|
.semantic
|
||||||
|
|
|
@ -888,9 +888,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
if let Some(type_params) = type_params {
|
if let Some(type_params) = type_params {
|
||||||
self.visit_type_params(type_params);
|
self.visit_type_params(type_params);
|
||||||
}
|
}
|
||||||
self.visit
|
self.visit_deferred_type_alias_value(value);
|
||||||
.type_param_definitions
|
|
||||||
.push((value, self.semantic.snapshot()));
|
|
||||||
self.semantic.pop_scope();
|
self.semantic.pop_scope();
|
||||||
self.visit_expr(name);
|
self.visit_expr(name);
|
||||||
}
|
}
|
||||||
|
@ -961,7 +959,7 @@ impl<'a> Visitor<'a> for Checker<'a> {
|
||||||
|
|
||||||
if let Some(expr) = value {
|
if let Some(expr) = value {
|
||||||
if self.semantic.match_typing_expr(annotation, "TypeAlias") {
|
if self.semantic.match_typing_expr(annotation, "TypeAlias") {
|
||||||
self.visit_type_definition(expr);
|
self.visit_annotated_type_alias_value(expr);
|
||||||
} else {
|
} else {
|
||||||
self.visit_expr(expr);
|
self.visit_expr(expr);
|
||||||
}
|
}
|
||||||
|
@ -1855,6 +1853,45 @@ impl<'a> Checker<'a> {
|
||||||
self.semantic.flags = snapshot;
|
self.semantic.flags = snapshot;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Visit an [`Expr`], and treat it as the value expression
|
||||||
|
/// of a [PEP 613] type alias.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```python
|
||||||
|
/// from typing import TypeAlias
|
||||||
|
///
|
||||||
|
/// OptStr: TypeAlias = str | None # We're visiting the RHS
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [PEP 613]: https://peps.python.org/pep-0613/
|
||||||
|
fn visit_annotated_type_alias_value(&mut self, expr: &'a Expr) {
|
||||||
|
let snapshot = self.semantic.flags;
|
||||||
|
self.semantic.flags |= SemanticModelFlags::ANNOTATED_TYPE_ALIAS;
|
||||||
|
self.visit_type_definition(expr);
|
||||||
|
self.semantic.flags = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Visit an [`Expr`], and treat it as the value expression
|
||||||
|
/// of a [PEP 695] type alias.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```python
|
||||||
|
/// type OptStr = str | None # We're visiting the RHS
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [PEP 695]: https://peps.python.org/pep-0695/#generic-type-alias
|
||||||
|
fn visit_deferred_type_alias_value(&mut self, expr: &'a Expr) {
|
||||||
|
let snapshot = self.semantic.flags;
|
||||||
|
// even though we don't visit these nodes immediately we need to
|
||||||
|
// modify the semantic flags before we push the expression and its
|
||||||
|
// corresponding semantic snapshot
|
||||||
|
self.semantic.flags |= SemanticModelFlags::DEFERRED_TYPE_ALIAS;
|
||||||
|
self.visit
|
||||||
|
.type_param_definitions
|
||||||
|
.push((expr, self.semantic.snapshot()));
|
||||||
|
self.semantic.flags = snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
/// 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() {
|
if self.semantic.in_no_type_check() {
|
||||||
|
@ -2017,6 +2054,21 @@ impl<'a> Checker<'a> {
|
||||||
flags.insert(BindingFlags::UNPACKED_ASSIGNMENT);
|
flags.insert(BindingFlags::UNPACKED_ASSIGNMENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
match parent {
|
||||||
|
Stmt::TypeAlias(_) => flags.insert(BindingFlags::DEFERRED_TYPE_ALIAS),
|
||||||
|
Stmt::AnnAssign(ast::StmtAnnAssign { annotation, .. }) => {
|
||||||
|
// TODO: It is a bit unfortunate that we do this check twice
|
||||||
|
// maybe we should change how we visit this statement
|
||||||
|
// so the semantic flag for the type alias sticks around
|
||||||
|
// until after we've handled this store, so we can check
|
||||||
|
// the flag instead of duplicating this check
|
||||||
|
if self.semantic.match_typing_expr(annotation, "TypeAlias") {
|
||||||
|
flags.insert(BindingFlags::ANNOTATED_TYPE_ALIAS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
|
||||||
let scope = self.semantic.current_scope();
|
let scope = self.semantic.current_scope();
|
||||||
|
|
||||||
if scope.kind.is_module()
|
if scope.kind.is_module()
|
||||||
|
@ -2272,7 +2324,17 @@ impl<'a> Checker<'a> {
|
||||||
|
|
||||||
self.semantic.flags |=
|
self.semantic.flags |=
|
||||||
SemanticModelFlags::TYPE_DEFINITION | type_definition_flag;
|
SemanticModelFlags::TYPE_DEFINITION | type_definition_flag;
|
||||||
self.visit_expr(parsed_annotation.expression());
|
let parsed_expr = parsed_annotation.expression();
|
||||||
|
self.visit_expr(parsed_expr);
|
||||||
|
if self.semantic.in_type_alias_value() {
|
||||||
|
if 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 {
|
} else {
|
||||||
if self.enabled(Rule::ForwardAnnotationSyntaxError) {
|
if self.enabled(Rule::ForwardAnnotationSyntaxError) {
|
||||||
|
|
|
@ -858,6 +858,8 @@ pub fn code_to_rule(linter: Linter, code: &str) -> Option<(RuleGroup, Rule)> {
|
||||||
(Flake8TypeChecking, "004") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock),
|
(Flake8TypeChecking, "004") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeImportInTypeCheckingBlock),
|
||||||
(Flake8TypeChecking, "005") => (RuleGroup::Stable, rules::flake8_type_checking::rules::EmptyTypeCheckingBlock),
|
(Flake8TypeChecking, "005") => (RuleGroup::Stable, rules::flake8_type_checking::rules::EmptyTypeCheckingBlock),
|
||||||
(Flake8TypeChecking, "006") => (RuleGroup::Preview, rules::flake8_type_checking::rules::RuntimeCastValue),
|
(Flake8TypeChecking, "006") => (RuleGroup::Preview, rules::flake8_type_checking::rules::RuntimeCastValue),
|
||||||
|
(Flake8TypeChecking, "007") => (RuleGroup::Preview, rules::flake8_type_checking::rules::UnquotedTypeAlias),
|
||||||
|
(Flake8TypeChecking, "008") => (RuleGroup::Preview, rules::flake8_type_checking::rules::QuotedTypeAlias),
|
||||||
(Flake8TypeChecking, "010") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeStringUnion),
|
(Flake8TypeChecking, "010") => (RuleGroup::Stable, rules::flake8_type_checking::rules::RuntimeStringUnion),
|
||||||
|
|
||||||
// tryceratops
|
// tryceratops
|
||||||
|
|
|
@ -63,6 +63,23 @@ mod tests {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// we test these rules as a pair, since they're opposites of one another
|
||||||
|
// so we want to make sure their fixes are not going around in circles.
|
||||||
|
#[test_case(Rule::UnquotedTypeAlias, Path::new("TC007.py"))]
|
||||||
|
#[test_case(Rule::QuotedTypeAlias, Path::new("TC008.py"))]
|
||||||
|
fn type_alias_rules(rule_code: Rule, path: &Path) -> Result<()> {
|
||||||
|
let snapshot = format!("{}_{}", rule_code.as_ref(), path.to_string_lossy());
|
||||||
|
let diagnostics = test_path(
|
||||||
|
Path::new("flake8_type_checking").join(path).as_path(),
|
||||||
|
&settings::LinterSettings::for_rules(vec![
|
||||||
|
Rule::UnquotedTypeAlias,
|
||||||
|
Rule::QuotedTypeAlias,
|
||||||
|
]),
|
||||||
|
)?;
|
||||||
|
assert_messages!(snapshot, diagnostics);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("quote.py"))]
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("quote.py"))]
|
||||||
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("quote.py"))]
|
#[test_case(Rule::TypingOnlyThirdPartyImport, Path::new("quote.py"))]
|
||||||
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("quote2.py"))]
|
#[test_case(Rule::RuntimeImportInTypeCheckingBlock, Path::new("quote2.py"))]
|
||||||
|
@ -431,6 +448,29 @@ mod tests {
|
||||||
",
|
",
|
||||||
"multiple_modules_different_types"
|
"multiple_modules_different_types"
|
||||||
)]
|
)]
|
||||||
|
#[test_case(
|
||||||
|
r"
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TYPE_CHECKING, TypeAlias
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from foo import Foo # TC004
|
||||||
|
|
||||||
|
a: TypeAlias = Foo | None # OK
|
||||||
|
",
|
||||||
|
"tc004_precedence_over_tc007"
|
||||||
|
)]
|
||||||
|
#[test_case(
|
||||||
|
r"
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from typing import TypeAlias
|
||||||
|
|
||||||
|
a: TypeAlias = 'int | None' # TC008
|
||||||
|
b: TypeAlias = 'int' | None # TC010
|
||||||
|
",
|
||||||
|
"tc010_precedence_over_tc008"
|
||||||
|
)]
|
||||||
fn contents(contents: &str, snapshot: &str) {
|
fn contents(contents: &str, snapshot: &str) {
|
||||||
let diagnostics = test_snippet(
|
let diagnostics = test_snippet(
|
||||||
contents,
|
contents,
|
||||||
|
|
|
@ -2,10 +2,12 @@ pub(crate) use empty_type_checking_block::*;
|
||||||
pub(crate) use runtime_cast_value::*;
|
pub(crate) use runtime_cast_value::*;
|
||||||
pub(crate) use runtime_import_in_type_checking_block::*;
|
pub(crate) use runtime_import_in_type_checking_block::*;
|
||||||
pub(crate) use runtime_string_union::*;
|
pub(crate) use runtime_string_union::*;
|
||||||
|
pub(crate) use type_alias_quotes::*;
|
||||||
pub(crate) use typing_only_runtime_import::*;
|
pub(crate) use typing_only_runtime_import::*;
|
||||||
|
|
||||||
mod empty_type_checking_block;
|
mod empty_type_checking_block;
|
||||||
mod runtime_cast_value;
|
mod runtime_cast_value;
|
||||||
mod runtime_import_in_type_checking_block;
|
mod runtime_import_in_type_checking_block;
|
||||||
mod runtime_string_union;
|
mod runtime_string_union;
|
||||||
|
mod type_alias_quotes;
|
||||||
mod typing_only_runtime_import;
|
mod typing_only_runtime_import;
|
||||||
|
|
|
@ -151,6 +151,14 @@ pub(crate) fn runtime_import_in_type_checking_block(
|
||||||
} else {
|
} else {
|
||||||
// Determine whether the member should be fixed by moving the import out of the
|
// Determine whether the member should be fixed by moving the import out of the
|
||||||
// type-checking block, or by quoting its references.
|
// type-checking block, or by quoting its references.
|
||||||
|
// TODO: We should check `reference.in_annotated_type_alias()`
|
||||||
|
// as well to match the behavior of the flake8 plugin
|
||||||
|
// although maybe the best way forward is to add an
|
||||||
|
// additional setting to configure whether quoting
|
||||||
|
// or moving the import is preferred for type aliases
|
||||||
|
// since some people will consistently use their
|
||||||
|
// type aliases at runtimes, while others won't, so
|
||||||
|
// the best solution is unclear.
|
||||||
if checker.settings.flake8_type_checking.quote_annotations
|
if checker.settings.flake8_type_checking.quote_annotations
|
||||||
&& binding.references().all(|reference_id| {
|
&& binding.references().all(|reference_id| {
|
||||||
let reference = checker.semantic().reference(reference_id);
|
let reference = checker.semantic().reference(reference_id);
|
||||||
|
|
|
@ -51,7 +51,7 @@ impl Violation for RuntimeStringUnion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// TC006
|
/// TC010
|
||||||
pub(crate) fn runtime_string_union(checker: &mut Checker, expr: &Expr) {
|
pub(crate) fn runtime_string_union(checker: &mut Checker, expr: &Expr) {
|
||||||
if !checker.semantic().in_type_definition() {
|
if !checker.semantic().in_type_definition() {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -0,0 +1,309 @@
|
||||||
|
use crate::registry::Rule;
|
||||||
|
use ast::{ExprContext, Operator};
|
||||||
|
use ruff_diagnostics::{AlwaysFixableViolation, Diagnostic, Edit, Fix, FixAvailability, Violation};
|
||||||
|
use ruff_macros::{derive_message_formats, violation};
|
||||||
|
use ruff_python_ast as ast;
|
||||||
|
use ruff_python_ast::{Expr, Stmt};
|
||||||
|
use ruff_python_semantic::{Binding, SemanticModel};
|
||||||
|
use ruff_python_stdlib::typing::{is_pep_593_generic_type, is_standard_library_literal};
|
||||||
|
use ruff_text_size::Ranged;
|
||||||
|
|
||||||
|
use crate::checkers::ast::Checker;
|
||||||
|
use crate::rules::flake8_type_checking::helpers::quote_type_expression;
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks if [PEP 613] explicit type aliases contain references to
|
||||||
|
/// symbols that are not available at runtime.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Referencing type-checking only symbols results in a `NameError` at runtime.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// ```python
|
||||||
|
/// from typing import TYPE_CHECKING, TypeAlias
|
||||||
|
///
|
||||||
|
/// if TYPE_CHECKING:
|
||||||
|
/// from foo import Foo
|
||||||
|
/// OptFoo: TypeAlias = Foo | None
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// from typing import TYPE_CHECKING, TypeAlias
|
||||||
|
///
|
||||||
|
/// if TYPE_CHECKING:
|
||||||
|
/// from foo import Foo
|
||||||
|
/// OptFoo: TypeAlias = "Foo | None"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Fix safety
|
||||||
|
/// This rule's fix is currently always marked as unsafe, since runtime
|
||||||
|
/// typing libraries may try to access/resolve the type alias in a way
|
||||||
|
/// that we can't statically determine during analysis and relies on the
|
||||||
|
/// type alias not containing any forward references.
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [PEP 613 – Explicit Type Aliases](https://peps.python.org/pep-0613/)
|
||||||
|
///
|
||||||
|
/// [PEP 613]: https://peps.python.org/pep-0613/
|
||||||
|
#[violation]
|
||||||
|
pub struct UnquotedTypeAlias;
|
||||||
|
|
||||||
|
impl Violation for UnquotedTypeAlias {
|
||||||
|
const FIX_AVAILABILITY: FixAvailability = FixAvailability::Sometimes;
|
||||||
|
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
"Add quotes to type alias".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_title(&self) -> Option<String> {
|
||||||
|
Some("Add quotes".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ## What it does
|
||||||
|
/// Checks for unnecessary quotes in [PEP 613] explicit type aliases
|
||||||
|
/// and [PEP 695] type statements.
|
||||||
|
///
|
||||||
|
/// ## Why is this bad?
|
||||||
|
/// Unnecessary string forward references can lead to additional overhead
|
||||||
|
/// in runtime libraries making use of type hints, as well as lead to bad
|
||||||
|
/// interactions with other runtime uses like [PEP 604] type unions.
|
||||||
|
///
|
||||||
|
/// For explicit type aliases the quotes are only considered redundant
|
||||||
|
/// if the type expression contains no subscripts or attribute accesses
|
||||||
|
/// this is because of stubs packages. Some types will only be subscriptable
|
||||||
|
/// at type checking time, similarly there may be some module-level
|
||||||
|
/// attributes like type aliases that are only available in the stubs.
|
||||||
|
///
|
||||||
|
/// ## Example
|
||||||
|
/// Given:
|
||||||
|
/// ```python
|
||||||
|
/// OptInt: TypeAlias = "int | None"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// OptInt: TypeAlias = int | None
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Given:
|
||||||
|
/// ```python
|
||||||
|
/// type OptInt = "int | None"
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Use instead:
|
||||||
|
/// ```python
|
||||||
|
/// type OptInt = int | None
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// ## Fix safety
|
||||||
|
/// This rule's fix is marked as safe, unless the type annotation contains comments.
|
||||||
|
///
|
||||||
|
/// ## References
|
||||||
|
/// - [PEP 613 – Explicit Type Aliases](https://peps.python.org/pep-0613/)
|
||||||
|
/// - [PEP 695: Generic Type Alias](https://peps.python.org/pep-0695/#generic-type-alias)
|
||||||
|
/// - [PEP 604 – Allow writing union types as `X | Y`](https://peps.python.org/pep-0604/)
|
||||||
|
///
|
||||||
|
/// [PEP 604]: https://peps.python.org/pep-0604/
|
||||||
|
/// [PEP 613]: https://peps.python.org/pep-0613/
|
||||||
|
/// [PEP 695]: https://peps.python.org/pep-0695/#generic-type-alias
|
||||||
|
#[violation]
|
||||||
|
pub struct QuotedTypeAlias;
|
||||||
|
|
||||||
|
impl AlwaysFixableViolation for QuotedTypeAlias {
|
||||||
|
#[derive_message_formats]
|
||||||
|
fn message(&self) -> String {
|
||||||
|
"Remove quotes from type alias".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fix_title(&self) -> String {
|
||||||
|
"Remove quotes".to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TC007
|
||||||
|
pub(crate) fn unquoted_type_alias(checker: &Checker, binding: &Binding) -> Option<Vec<Diagnostic>> {
|
||||||
|
if binding.context.is_typing() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !binding.is_annotated_type_alias() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(Stmt::AnnAssign(ast::StmtAnnAssign {
|
||||||
|
value: Some(expr), ..
|
||||||
|
})) = binding.statement(checker.semantic())
|
||||||
|
else {
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut names = Vec::new();
|
||||||
|
collect_typing_references(checker, expr, &mut names);
|
||||||
|
if names.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We generate a diagnostic for every name that needs to be quoted
|
||||||
|
// but we currently emit a single shared fix that quotes the entire
|
||||||
|
// expression.
|
||||||
|
//
|
||||||
|
// Eventually we may try to be more clever and come up with the
|
||||||
|
// minimal set of subexpressions that need to be quoted.
|
||||||
|
let parent = expr.range().start();
|
||||||
|
let edit = quote_type_expression(expr, checker.semantic(), checker.stylist());
|
||||||
|
let mut diagnostics = Vec::with_capacity(names.len());
|
||||||
|
for name in names {
|
||||||
|
let mut diagnostic = Diagnostic::new(UnquotedTypeAlias, name.range());
|
||||||
|
diagnostic.set_parent(parent);
|
||||||
|
if let Ok(edit) = edit.as_ref() {
|
||||||
|
diagnostic.set_fix(Fix::unsafe_edit(edit.clone()));
|
||||||
|
}
|
||||||
|
diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
Some(diagnostics)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverses the type expression and collects `[Expr::Name]` nodes that are
|
||||||
|
/// not available at runtime and thus need to be quoted, unless they would
|
||||||
|
/// become available through `[Rule::RuntimeImportInTypeCheckingBlock]`.
|
||||||
|
fn collect_typing_references<'a>(
|
||||||
|
checker: &Checker,
|
||||||
|
expr: &'a Expr,
|
||||||
|
names: &mut Vec<&'a ast::ExprName>,
|
||||||
|
) {
|
||||||
|
match expr {
|
||||||
|
Expr::BinOp(ast::ExprBinOp { left, right, .. }) => {
|
||||||
|
collect_typing_references(checker, left, names);
|
||||||
|
collect_typing_references(checker, right, names);
|
||||||
|
}
|
||||||
|
Expr::Starred(ast::ExprStarred {
|
||||||
|
value,
|
||||||
|
ctx: ExprContext::Load,
|
||||||
|
..
|
||||||
|
})
|
||||||
|
| Expr::Attribute(ast::ExprAttribute { value, .. }) => {
|
||||||
|
collect_typing_references(checker, value, names);
|
||||||
|
}
|
||||||
|
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||||
|
collect_typing_references(checker, value, names);
|
||||||
|
if let Some(qualified_name) = checker.semantic().resolve_qualified_name(value) {
|
||||||
|
if is_standard_library_literal(qualified_name.segments()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if is_pep_593_generic_type(qualified_name.segments()) {
|
||||||
|
// First argument is a type (including forward references); the
|
||||||
|
// rest are arbitrary Python objects.
|
||||||
|
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||||
|
let mut iter = elts.iter();
|
||||||
|
if let Some(expr) = iter.next() {
|
||||||
|
collect_typing_references(checker, expr, names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collect_typing_references(checker, slice, names);
|
||||||
|
}
|
||||||
|
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||||
|
for elt in elts {
|
||||||
|
collect_typing_references(checker, elt, names);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Expr::Name(name) => {
|
||||||
|
let Some(binding_id) = checker.semantic().resolve_name(name) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if checker.semantic().simulate_runtime_load(name).is_some() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if TC004 is enabled we shouldn't emit a TC007 for a reference to
|
||||||
|
// a binding that would emit a TC004, otherwise the fixes will never
|
||||||
|
// stabilize and keep going in circles
|
||||||
|
if checker.enabled(Rule::RuntimeImportInTypeCheckingBlock)
|
||||||
|
&& checker
|
||||||
|
.semantic()
|
||||||
|
.binding(binding_id)
|
||||||
|
.references()
|
||||||
|
.any(|id| checker.semantic().reference(id).in_runtime_context())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
names.push(name);
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// TC008
|
||||||
|
pub(crate) fn quoted_type_alias(
|
||||||
|
checker: &mut Checker,
|
||||||
|
expr: &Expr,
|
||||||
|
annotation_expr: &ast::ExprStringLiteral,
|
||||||
|
) {
|
||||||
|
if checker.enabled(Rule::RuntimeStringUnion) {
|
||||||
|
// this should return a TC010 error instead
|
||||||
|
if let Some(Expr::BinOp(ast::ExprBinOp {
|
||||||
|
op: Operator::BitOr,
|
||||||
|
..
|
||||||
|
})) = checker.semantic().current_expression_parent()
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// explicit type aliases require some additional checks to avoid false positives
|
||||||
|
if checker.semantic().in_annotated_type_alias_value()
|
||||||
|
&& quotes_are_unremovable(checker.semantic(), expr)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let range = annotation_expr.range();
|
||||||
|
let mut diagnostic = Diagnostic::new(QuotedTypeAlias, range);
|
||||||
|
let edit = Edit::range_replacement(annotation_expr.value.to_string(), range);
|
||||||
|
if checker
|
||||||
|
.comment_ranges()
|
||||||
|
.has_comments(expr, checker.source())
|
||||||
|
{
|
||||||
|
diagnostic.set_fix(Fix::unsafe_edit(edit));
|
||||||
|
} else {
|
||||||
|
diagnostic.set_fix(Fix::safe_edit(edit));
|
||||||
|
}
|
||||||
|
checker.diagnostics.push(diagnostic);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traverses the type expression and checks if the expression can safely
|
||||||
|
/// be unquoted
|
||||||
|
fn quotes_are_unremovable(semantic: &SemanticModel, expr: &Expr) -> bool {
|
||||||
|
match expr {
|
||||||
|
Expr::BinOp(ast::ExprBinOp { left, right, .. }) => {
|
||||||
|
quotes_are_unremovable(semantic, left) || quotes_are_unremovable(semantic, right)
|
||||||
|
}
|
||||||
|
Expr::Starred(ast::ExprStarred {
|
||||||
|
value,
|
||||||
|
ctx: ExprContext::Load,
|
||||||
|
..
|
||||||
|
}) => quotes_are_unremovable(semantic, value),
|
||||||
|
// for subscripts and attributes we don't know whether it's safe
|
||||||
|
// to do at runtime, since the operation may only be available at
|
||||||
|
// type checking time. E.g. stubs only generics. Or stubs only
|
||||||
|
// type aliases.
|
||||||
|
Expr::Subscript(_) | Expr::Attribute(_) => true,
|
||||||
|
Expr::List(ast::ExprList { elts, .. }) | Expr::Tuple(ast::ExprTuple { elts, .. }) => {
|
||||||
|
for elt in elts {
|
||||||
|
if quotes_are_unremovable(semantic, elt) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
Expr::Name(name) => {
|
||||||
|
semantic.resolve_name(name).is_some() && semantic.simulate_runtime_load(name).is_none()
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
quote.py:57:28: TC004 [*] Quote references to `pandas.DataFrame`. Import is in a type-checking block.
|
quote.py:57:28: TC004 [*] Quote references to `pandas.DataFrame`. Import is in a type-checking block.
|
||||||
|
|
|
|
||||||
|
@ -20,4 +19,28 @@ quote.py:57:28: TC004 [*] Quote references to `pandas.DataFrame`. Import is in a
|
||||||
59 |+ def func(value: "DataFrame"):
|
59 |+ def func(value: "DataFrame"):
|
||||||
60 60 | ...
|
60 60 | ...
|
||||||
61 61 |
|
61 61 |
|
||||||
62 62 |
|
62 62 |
|
||||||
|
|
||||||
|
quote.py:110:28: TC004 [*] Move import `pandas.DataFrame` out of type-checking block. Import is used for more than type hinting.
|
||||||
|
|
|
||||||
|
109 | if TYPE_CHECKING:
|
||||||
|
110 | from pandas import DataFrame
|
||||||
|
| ^^^^^^^^^ TC004
|
||||||
|
111 |
|
||||||
|
112 | x: TypeAlias = DataFrame | None
|
||||||
|
|
|
||||||
|
= help: Move out of type-checking block
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
1 |+from pandas import DataFrame
|
||||||
|
1 2 | def f():
|
||||||
|
2 3 | from pandas import DataFrame
|
||||||
|
3 4 |
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
107 108 | from typing import TypeAlias, TYPE_CHECKING
|
||||||
|
108 109 |
|
||||||
|
109 110 | if TYPE_CHECKING:
|
||||||
|
110 |- from pandas import DataFrame
|
||||||
|
111 |+ pass
|
||||||
|
111 112 |
|
||||||
|
112 113 | x: TypeAlias = DataFrame | None
|
||||||
|
|
|
@ -0,0 +1,420 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||||
|
---
|
||||||
|
TC008.py:15:16: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
13 | Bar = Foo
|
||||||
|
14 |
|
||||||
|
15 | a: TypeAlias = 'int' # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
16 | b: TypeAlias = 'Dict' # OK
|
||||||
|
17 | c: TypeAlias = 'Foo' # TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
12 12 | else:
|
||||||
|
13 13 | Bar = Foo
|
||||||
|
14 14 |
|
||||||
|
15 |-a: TypeAlias = 'int' # TC008
|
||||||
|
15 |+a: TypeAlias = int # TC008
|
||||||
|
16 16 | b: TypeAlias = 'Dict' # OK
|
||||||
|
17 17 | c: TypeAlias = 'Foo' # TC008
|
||||||
|
18 18 | d: TypeAlias = 'Foo[str]' # OK
|
||||||
|
|
||||||
|
TC008.py:17:16: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
15 | a: TypeAlias = 'int' # TC008
|
||||||
|
16 | b: TypeAlias = 'Dict' # OK
|
||||||
|
17 | c: TypeAlias = 'Foo' # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
18 | d: TypeAlias = 'Foo[str]' # OK
|
||||||
|
19 | e: TypeAlias = 'Foo.bar' # OK
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
14 14 |
|
||||||
|
15 15 | a: TypeAlias = 'int' # TC008
|
||||||
|
16 16 | b: TypeAlias = 'Dict' # OK
|
||||||
|
17 |-c: TypeAlias = 'Foo' # TC008
|
||||||
|
17 |+c: TypeAlias = Foo # TC008
|
||||||
|
18 18 | d: TypeAlias = 'Foo[str]' # OK
|
||||||
|
19 19 | e: TypeAlias = 'Foo.bar' # OK
|
||||||
|
20 20 | f: TypeAlias = 'Foo | None' # TC008
|
||||||
|
|
||||||
|
TC008.py:20:16: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
18 | d: TypeAlias = 'Foo[str]' # OK
|
||||||
|
19 | e: TypeAlias = 'Foo.bar' # OK
|
||||||
|
20 | f: TypeAlias = 'Foo | None' # TC008
|
||||||
|
| ^^^^^^^^^^^^ TC008
|
||||||
|
21 | g: TypeAlias = 'OptStr' # OK
|
||||||
|
22 | h: TypeAlias = 'Bar' # TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
17 17 | c: TypeAlias = 'Foo' # TC008
|
||||||
|
18 18 | d: TypeAlias = 'Foo[str]' # OK
|
||||||
|
19 19 | e: TypeAlias = 'Foo.bar' # OK
|
||||||
|
20 |-f: TypeAlias = 'Foo | None' # TC008
|
||||||
|
20 |+f: TypeAlias = Foo | None # TC008
|
||||||
|
21 21 | g: TypeAlias = 'OptStr' # OK
|
||||||
|
22 22 | h: TypeAlias = 'Bar' # TC008
|
||||||
|
23 23 | i: TypeAlias = Foo['str'] # TC008
|
||||||
|
|
||||||
|
TC008.py:22:16: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
20 | f: TypeAlias = 'Foo | None' # TC008
|
||||||
|
21 | g: TypeAlias = 'OptStr' # OK
|
||||||
|
22 | h: TypeAlias = 'Bar' # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
23 | i: TypeAlias = Foo['str'] # TC008
|
||||||
|
24 | j: TypeAlias = 'Baz' # OK
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
19 19 | e: TypeAlias = 'Foo.bar' # OK
|
||||||
|
20 20 | f: TypeAlias = 'Foo | None' # TC008
|
||||||
|
21 21 | g: TypeAlias = 'OptStr' # OK
|
||||||
|
22 |-h: TypeAlias = 'Bar' # TC008
|
||||||
|
22 |+h: TypeAlias = Bar # TC008
|
||||||
|
23 23 | i: TypeAlias = Foo['str'] # TC008
|
||||||
|
24 24 | j: TypeAlias = 'Baz' # OK
|
||||||
|
25 25 | k: TypeAlias = 'k | None' # OK
|
||||||
|
|
||||||
|
TC008.py:23:20: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
21 | g: TypeAlias = 'OptStr' # OK
|
||||||
|
22 | h: TypeAlias = 'Bar' # TC008
|
||||||
|
23 | i: TypeAlias = Foo['str'] # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
24 | j: TypeAlias = 'Baz' # OK
|
||||||
|
25 | k: TypeAlias = 'k | None' # OK
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
20 20 | f: TypeAlias = 'Foo | None' # TC008
|
||||||
|
21 21 | g: TypeAlias = 'OptStr' # OK
|
||||||
|
22 22 | h: TypeAlias = 'Bar' # TC008
|
||||||
|
23 |-i: TypeAlias = Foo['str'] # TC008
|
||||||
|
23 |+i: TypeAlias = Foo[str] # TC008
|
||||||
|
24 24 | j: TypeAlias = 'Baz' # OK
|
||||||
|
25 25 | k: TypeAlias = 'k | None' # OK
|
||||||
|
26 26 | l: TypeAlias = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
|
||||||
|
TC008.py:26:16: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
24 | j: TypeAlias = 'Baz' # OK
|
||||||
|
25 | k: TypeAlias = 'k | None' # OK
|
||||||
|
26 | l: TypeAlias = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
| ^^^^^ TC008
|
||||||
|
27 | m: TypeAlias = ('int' # TC008
|
||||||
|
28 | | None)
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
23 23 | i: TypeAlias = Foo['str'] # TC008
|
||||||
|
24 24 | j: TypeAlias = 'Baz' # OK
|
||||||
|
25 25 | k: TypeAlias = 'k | None' # OK
|
||||||
|
26 |-l: TypeAlias = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
26 |+l: TypeAlias = int | None # TC008 (because TC010 is not enabled)
|
||||||
|
27 27 | m: TypeAlias = ('int' # TC008
|
||||||
|
28 28 | | None)
|
||||||
|
29 29 | n: TypeAlias = ('int' # TC008 (fix removes comment currently)
|
||||||
|
|
||||||
|
TC008.py:27:17: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
25 | k: TypeAlias = 'k | None' # OK
|
||||||
|
26 | l: TypeAlias = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
27 | m: TypeAlias = ('int' # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
28 | | None)
|
||||||
|
29 | n: TypeAlias = ('int' # TC008 (fix removes comment currently)
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
24 24 | j: TypeAlias = 'Baz' # OK
|
||||||
|
25 25 | k: TypeAlias = 'k | None' # OK
|
||||||
|
26 26 | l: TypeAlias = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
27 |-m: TypeAlias = ('int' # TC008
|
||||||
|
27 |+m: TypeAlias = (int # TC008
|
||||||
|
28 28 | | None)
|
||||||
|
29 29 | n: TypeAlias = ('int' # TC008 (fix removes comment currently)
|
||||||
|
30 30 | ' | None')
|
||||||
|
|
||||||
|
TC008.py:29:17: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
27 | m: TypeAlias = ('int' # TC008
|
||||||
|
28 | | None)
|
||||||
|
29 | n: TypeAlias = ('int' # TC008 (fix removes comment currently)
|
||||||
|
| _________________^
|
||||||
|
30 | | ' | None')
|
||||||
|
| |_____________^ TC008
|
||||||
|
31 |
|
||||||
|
32 | type B = 'Dict' # TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
26 26 | l: TypeAlias = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
27 27 | m: TypeAlias = ('int' # TC008
|
||||||
|
28 28 | | None)
|
||||||
|
29 |-n: TypeAlias = ('int' # TC008 (fix removes comment currently)
|
||||||
|
30 |- ' | None')
|
||||||
|
29 |+n: TypeAlias = (int | None)
|
||||||
|
31 30 |
|
||||||
|
32 31 | type B = 'Dict' # TC008
|
||||||
|
33 32 | type D = 'Foo[str]' # TC008
|
||||||
|
|
||||||
|
TC008.py:32:10: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
30 | ' | None')
|
||||||
|
31 |
|
||||||
|
32 | type B = 'Dict' # TC008
|
||||||
|
| ^^^^^^ TC008
|
||||||
|
33 | type D = 'Foo[str]' # TC008
|
||||||
|
34 | type E = 'Foo.bar' # TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
29 29 | n: TypeAlias = ('int' # TC008 (fix removes comment currently)
|
||||||
|
30 30 | ' | None')
|
||||||
|
31 31 |
|
||||||
|
32 |-type B = 'Dict' # TC008
|
||||||
|
32 |+type B = Dict # TC008
|
||||||
|
33 33 | type D = 'Foo[str]' # TC008
|
||||||
|
34 34 | type E = 'Foo.bar' # TC008
|
||||||
|
35 35 | type G = 'OptStr' # TC008
|
||||||
|
|
||||||
|
TC008.py:33:10: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
32 | type B = 'Dict' # TC008
|
||||||
|
33 | type D = 'Foo[str]' # TC008
|
||||||
|
| ^^^^^^^^^^ TC008
|
||||||
|
34 | type E = 'Foo.bar' # TC008
|
||||||
|
35 | type G = 'OptStr' # TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
30 30 | ' | None')
|
||||||
|
31 31 |
|
||||||
|
32 32 | type B = 'Dict' # TC008
|
||||||
|
33 |-type D = 'Foo[str]' # TC008
|
||||||
|
33 |+type D = Foo[str] # TC008
|
||||||
|
34 34 | type E = 'Foo.bar' # TC008
|
||||||
|
35 35 | type G = 'OptStr' # TC008
|
||||||
|
36 36 | type I = Foo['str'] # TC008
|
||||||
|
|
||||||
|
TC008.py:34:10: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
32 | type B = 'Dict' # TC008
|
||||||
|
33 | type D = 'Foo[str]' # TC008
|
||||||
|
34 | type E = 'Foo.bar' # TC008
|
||||||
|
| ^^^^^^^^^ TC008
|
||||||
|
35 | type G = 'OptStr' # TC008
|
||||||
|
36 | type I = Foo['str'] # TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
31 31 |
|
||||||
|
32 32 | type B = 'Dict' # TC008
|
||||||
|
33 33 | type D = 'Foo[str]' # TC008
|
||||||
|
34 |-type E = 'Foo.bar' # TC008
|
||||||
|
34 |+type E = Foo.bar # TC008
|
||||||
|
35 35 | type G = 'OptStr' # TC008
|
||||||
|
36 36 | type I = Foo['str'] # TC008
|
||||||
|
37 37 | type J = 'Baz' # TC008
|
||||||
|
|
||||||
|
TC008.py:35:10: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
33 | type D = 'Foo[str]' # TC008
|
||||||
|
34 | type E = 'Foo.bar' # TC008
|
||||||
|
35 | type G = 'OptStr' # TC008
|
||||||
|
| ^^^^^^^^ TC008
|
||||||
|
36 | type I = Foo['str'] # TC008
|
||||||
|
37 | type J = 'Baz' # TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
32 32 | type B = 'Dict' # TC008
|
||||||
|
33 33 | type D = 'Foo[str]' # TC008
|
||||||
|
34 34 | type E = 'Foo.bar' # TC008
|
||||||
|
35 |-type G = 'OptStr' # TC008
|
||||||
|
35 |+type G = OptStr # TC008
|
||||||
|
36 36 | type I = Foo['str'] # TC008
|
||||||
|
37 37 | type J = 'Baz' # TC008
|
||||||
|
38 38 | type K = 'K | None' # TC008
|
||||||
|
|
||||||
|
TC008.py:36:14: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
34 | type E = 'Foo.bar' # TC008
|
||||||
|
35 | type G = 'OptStr' # TC008
|
||||||
|
36 | type I = Foo['str'] # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
37 | type J = 'Baz' # TC008
|
||||||
|
38 | type K = 'K | None' # TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
33 33 | type D = 'Foo[str]' # TC008
|
||||||
|
34 34 | type E = 'Foo.bar' # TC008
|
||||||
|
35 35 | type G = 'OptStr' # TC008
|
||||||
|
36 |-type I = Foo['str'] # TC008
|
||||||
|
36 |+type I = Foo[str] # TC008
|
||||||
|
37 37 | type J = 'Baz' # TC008
|
||||||
|
38 38 | type K = 'K | None' # TC008
|
||||||
|
39 39 | type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
|
||||||
|
TC008.py:37:10: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
35 | type G = 'OptStr' # TC008
|
||||||
|
36 | type I = Foo['str'] # TC008
|
||||||
|
37 | type J = 'Baz' # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
38 | type K = 'K | None' # TC008
|
||||||
|
39 | type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
34 34 | type E = 'Foo.bar' # TC008
|
||||||
|
35 35 | type G = 'OptStr' # TC008
|
||||||
|
36 36 | type I = Foo['str'] # TC008
|
||||||
|
37 |-type J = 'Baz' # TC008
|
||||||
|
37 |+type J = Baz # TC008
|
||||||
|
38 38 | type K = 'K | None' # TC008
|
||||||
|
39 39 | type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
40 40 | type M = ('int' # TC008
|
||||||
|
|
||||||
|
TC008.py:38:10: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
36 | type I = Foo['str'] # TC008
|
||||||
|
37 | type J = 'Baz' # TC008
|
||||||
|
38 | type K = 'K | None' # TC008
|
||||||
|
| ^^^^^^^^^^ TC008
|
||||||
|
39 | type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
40 | type M = ('int' # TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
35 35 | type G = 'OptStr' # TC008
|
||||||
|
36 36 | type I = Foo['str'] # TC008
|
||||||
|
37 37 | type J = 'Baz' # TC008
|
||||||
|
38 |-type K = 'K | None' # TC008
|
||||||
|
38 |+type K = K | None # TC008
|
||||||
|
39 39 | type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
40 40 | type M = ('int' # TC008
|
||||||
|
41 41 | | None)
|
||||||
|
|
||||||
|
TC008.py:39:10: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
37 | type J = 'Baz' # TC008
|
||||||
|
38 | type K = 'K | None' # TC008
|
||||||
|
39 | type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
| ^^^^^ TC008
|
||||||
|
40 | type M = ('int' # TC008
|
||||||
|
41 | | None)
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
36 36 | type I = Foo['str'] # TC008
|
||||||
|
37 37 | type J = 'Baz' # TC008
|
||||||
|
38 38 | type K = 'K | None' # TC008
|
||||||
|
39 |-type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
39 |+type L = int | None # TC008 (because TC010 is not enabled)
|
||||||
|
40 40 | type M = ('int' # TC008
|
||||||
|
41 41 | | None)
|
||||||
|
42 42 | type N = ('int' # TC008 (fix removes comment currently)
|
||||||
|
|
||||||
|
TC008.py:40:11: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
38 | type K = 'K | None' # TC008
|
||||||
|
39 | type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
40 | type M = ('int' # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
41 | | None)
|
||||||
|
42 | type N = ('int' # TC008 (fix removes comment currently)
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
37 37 | type J = 'Baz' # TC008
|
||||||
|
38 38 | type K = 'K | None' # TC008
|
||||||
|
39 39 | type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
40 |-type M = ('int' # TC008
|
||||||
|
40 |+type M = (int # TC008
|
||||||
|
41 41 | | None)
|
||||||
|
42 42 | type N = ('int' # TC008 (fix removes comment currently)
|
||||||
|
43 43 | ' | None')
|
||||||
|
|
||||||
|
TC008.py:42:11: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
40 | type M = ('int' # TC008
|
||||||
|
41 | | None)
|
||||||
|
42 | type N = ('int' # TC008 (fix removes comment currently)
|
||||||
|
| ___________^
|
||||||
|
43 | | ' | None')
|
||||||
|
| |_____________^ TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
39 39 | type L = 'int' | None # TC008 (because TC010 is not enabled)
|
||||||
|
40 40 | type M = ('int' # TC008
|
||||||
|
41 41 | | None)
|
||||||
|
42 |-type N = ('int' # TC008 (fix removes comment currently)
|
||||||
|
43 |- ' | None')
|
||||||
|
42 |+type N = (int | None)
|
||||||
|
44 43 |
|
||||||
|
45 44 |
|
||||||
|
46 45 | class Baz:
|
||||||
|
|
||||||
|
TC008.py:48:14: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
46 | class Baz:
|
||||||
|
47 | a: TypeAlias = 'Baz' # OK
|
||||||
|
48 | type A = 'Baz' # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
49 |
|
||||||
|
50 | class Nested:
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
45 45 |
|
||||||
|
46 46 | class Baz:
|
||||||
|
47 47 | a: TypeAlias = 'Baz' # OK
|
||||||
|
48 |- type A = 'Baz' # TC008
|
||||||
|
48 |+ type A = Baz # TC008
|
||||||
|
49 49 |
|
||||||
|
50 50 | class Nested:
|
||||||
|
51 51 | a: TypeAlias = 'Baz' # OK
|
||||||
|
|
||||||
|
TC008.py:52:18: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
50 | class Nested:
|
||||||
|
51 | a: TypeAlias = 'Baz' # OK
|
||||||
|
52 | type A = 'Baz' # TC008
|
||||||
|
| ^^^^^ TC008
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
49 49 |
|
||||||
|
50 50 | class Nested:
|
||||||
|
51 51 | a: TypeAlias = 'Baz' # OK
|
||||||
|
52 |- type A = 'Baz' # TC008
|
||||||
|
52 |+ type A = Baz # TC008
|
|
@ -1,6 +1,5 @@
|
||||||
---
|
---
|
||||||
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||||
snapshot_kind: text
|
|
||||||
---
|
---
|
||||||
quote.py:57:28: TC004 [*] Move import `pandas.DataFrame` out of type-checking block. Import is used for more than type hinting.
|
quote.py:57:28: TC004 [*] Move import `pandas.DataFrame` out of type-checking block. Import is used for more than type hinting.
|
||||||
|
|
|
|
||||||
|
@ -26,3 +25,27 @@ quote.py:57:28: TC004 [*] Move import `pandas.DataFrame` out of type-checking bl
|
||||||
58 59 |
|
58 59 |
|
||||||
59 60 | def func(value: DataFrame):
|
59 60 | def func(value: DataFrame):
|
||||||
60 61 | ...
|
60 61 | ...
|
||||||
|
|
||||||
|
quote.py:110:28: TC004 [*] Move import `pandas.DataFrame` out of type-checking block. Import is used for more than type hinting.
|
||||||
|
|
|
||||||
|
109 | if TYPE_CHECKING:
|
||||||
|
110 | from pandas import DataFrame
|
||||||
|
| ^^^^^^^^^ TC004
|
||||||
|
111 |
|
||||||
|
112 | x: TypeAlias = DataFrame | None
|
||||||
|
|
|
||||||
|
= help: Move out of type-checking block
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
1 |+from pandas import DataFrame
|
||||||
|
1 2 | def f():
|
||||||
|
2 3 | from pandas import DataFrame
|
||||||
|
3 4 |
|
||||||
|
--------------------------------------------------------------------------------
|
||||||
|
107 108 | from typing import TypeAlias, TYPE_CHECKING
|
||||||
|
108 109 |
|
||||||
|
109 110 | if TYPE_CHECKING:
|
||||||
|
110 |- from pandas import DataFrame
|
||||||
|
111 |+ pass
|
||||||
|
111 112 |
|
||||||
|
112 113 | x: TypeAlias = DataFrame | None
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||||
|
---
|
||||||
|
<filename>:6:21: TC004 [*] Move import `foo.Foo` out of type-checking block. Import is used for more than type hinting.
|
||||||
|
|
|
||||||
|
4 | from typing import TYPE_CHECKING, TypeAlias
|
||||||
|
5 | if TYPE_CHECKING:
|
||||||
|
6 | from foo import Foo # TC004
|
||||||
|
| ^^^ TC004
|
||||||
|
7 |
|
||||||
|
8 | a: TypeAlias = Foo | None # OK
|
||||||
|
|
|
||||||
|
= help: Move out of type-checking block
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
2 2 | from __future__ import annotations
|
||||||
|
3 3 |
|
||||||
|
4 4 | from typing import TYPE_CHECKING, TypeAlias
|
||||||
|
5 |+from foo import Foo
|
||||||
|
5 6 | if TYPE_CHECKING:
|
||||||
|
6 |- from foo import Foo # TC004
|
||||||
|
7 |+ pass # TC004
|
||||||
|
7 8 |
|
||||||
|
8 9 | a: TypeAlias = Foo | None # OK
|
|
@ -0,0 +1,27 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||||
|
---
|
||||||
|
<filename>:6:16: TC008 [*] Remove quotes from type alias
|
||||||
|
|
|
||||||
|
4 | from typing import TypeAlias
|
||||||
|
5 |
|
||||||
|
6 | a: TypeAlias = 'int | None' # TC008
|
||||||
|
| ^^^^^^^^^^^^ TC008
|
||||||
|
7 | b: TypeAlias = 'int' | None # TC010
|
||||||
|
|
|
||||||
|
= help: Remove quotes
|
||||||
|
|
||||||
|
ℹ Safe fix
|
||||||
|
3 3 |
|
||||||
|
4 4 | from typing import TypeAlias
|
||||||
|
5 5 |
|
||||||
|
6 |-a: TypeAlias = 'int | None' # TC008
|
||||||
|
6 |+a: TypeAlias = int | None # TC008
|
||||||
|
7 7 | b: TypeAlias = 'int' | None # TC010
|
||||||
|
|
||||||
|
<filename>:7:16: TC010 Invalid string member in `X | Y`-style union type
|
||||||
|
|
|
||||||
|
6 | a: TypeAlias = 'int | None' # TC008
|
||||||
|
7 | b: TypeAlias = 'int' | None # TC010
|
||||||
|
| ^^^^^ TC010
|
||||||
|
|
|
|
@ -0,0 +1,192 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_linter/src/rules/flake8_type_checking/mod.rs
|
||||||
|
---
|
||||||
|
TC007.py:15:16: TC007 [*] Add quotes to type alias
|
||||||
|
|
|
||||||
|
13 | a: TypeAlias = int # OK
|
||||||
|
14 | b: TypeAlias = Dict # OK
|
||||||
|
15 | c: TypeAlias = Foo # TC007
|
||||||
|
| ^^^ TC007
|
||||||
|
16 | d: TypeAlias = Foo | None # TC007
|
||||||
|
17 | e: TypeAlias = OptStr # TC007
|
||||||
|
|
|
||||||
|
= help: Add quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
12 12 |
|
||||||
|
13 13 | a: TypeAlias = int # OK
|
||||||
|
14 14 | b: TypeAlias = Dict # OK
|
||||||
|
15 |-c: TypeAlias = Foo # TC007
|
||||||
|
15 |+c: TypeAlias = "Foo" # TC007
|
||||||
|
16 16 | d: TypeAlias = Foo | None # TC007
|
||||||
|
17 17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 18 | f: TypeAlias = Bar # TC007
|
||||||
|
|
||||||
|
TC007.py:16:16: TC007 [*] Add quotes to type alias
|
||||||
|
|
|
||||||
|
14 | b: TypeAlias = Dict # OK
|
||||||
|
15 | c: TypeAlias = Foo # TC007
|
||||||
|
16 | d: TypeAlias = Foo | None # TC007
|
||||||
|
| ^^^ TC007
|
||||||
|
17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 | f: TypeAlias = Bar # TC007
|
||||||
|
|
|
||||||
|
= help: Add quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
13 13 | a: TypeAlias = int # OK
|
||||||
|
14 14 | b: TypeAlias = Dict # OK
|
||||||
|
15 15 | c: TypeAlias = Foo # TC007
|
||||||
|
16 |-d: TypeAlias = Foo | None # TC007
|
||||||
|
16 |+d: TypeAlias = "Foo | None" # TC007
|
||||||
|
17 17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
|
||||||
|
TC007.py:17:16: TC007 [*] Add quotes to type alias
|
||||||
|
|
|
||||||
|
15 | c: TypeAlias = Foo # TC007
|
||||||
|
16 | d: TypeAlias = Foo | None # TC007
|
||||||
|
17 | e: TypeAlias = OptStr # TC007
|
||||||
|
| ^^^^^^ TC007
|
||||||
|
18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
|
|
||||||
|
= help: Add quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
14 14 | b: TypeAlias = Dict # OK
|
||||||
|
15 15 | c: TypeAlias = Foo # TC007
|
||||||
|
16 16 | d: TypeAlias = Foo | None # TC007
|
||||||
|
17 |-e: TypeAlias = OptStr # TC007
|
||||||
|
17 |+e: TypeAlias = "OptStr" # TC007
|
||||||
|
18 18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
20 20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
|
||||||
|
TC007.py:18:16: TC007 [*] Add quotes to type alias
|
||||||
|
|
|
||||||
|
16 | d: TypeAlias = Foo | None # TC007
|
||||||
|
17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 | f: TypeAlias = Bar # TC007
|
||||||
|
| ^^^ TC007
|
||||||
|
19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
|
|
||||||
|
= help: Add quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
15 15 | c: TypeAlias = Foo # TC007
|
||||||
|
16 16 | d: TypeAlias = Foo | None # TC007
|
||||||
|
17 17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 |-f: TypeAlias = Bar # TC007
|
||||||
|
18 |+f: TypeAlias = "Bar" # TC007
|
||||||
|
19 19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
20 20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
21 21 | i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
|
||||||
|
TC007.py:19:16: TC007 [*] Add quotes to type alias
|
||||||
|
|
|
||||||
|
17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
| ^^^ TC007
|
||||||
|
20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
21 | i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
|
|
||||||
|
= help: Add quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
16 16 | d: TypeAlias = Foo | None # TC007
|
||||||
|
17 17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 |-g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
19 |+g: TypeAlias = "Foo | Bar" # TC007 x2
|
||||||
|
20 20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
21 21 | i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
22 22 | Bar)
|
||||||
|
|
||||||
|
TC007.py:19:22: TC007 [*] Add quotes to type alias
|
||||||
|
|
|
||||||
|
17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
| ^^^ TC007
|
||||||
|
20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
21 | i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
|
|
||||||
|
= help: Add quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
16 16 | d: TypeAlias = Foo | None # TC007
|
||||||
|
17 17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 |-g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
19 |+g: TypeAlias = "Foo | Bar" # TC007 x2
|
||||||
|
20 20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
21 21 | i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
22 22 | Bar)
|
||||||
|
|
||||||
|
TC007.py:20:16: TC007 [*] Add quotes to type alias
|
||||||
|
|
|
||||||
|
18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
| ^^^ TC007
|
||||||
|
21 | i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
22 | Bar)
|
||||||
|
|
|
||||||
|
= help: Add quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
17 17 | e: TypeAlias = OptStr # TC007
|
||||||
|
18 18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
20 |-h: TypeAlias = Foo[str] # TC007
|
||||||
|
20 |+h: TypeAlias = "Foo[str]" # TC007
|
||||||
|
21 21 | i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
22 22 | Bar)
|
||||||
|
23 23 |
|
||||||
|
|
||||||
|
TC007.py:21:17: TC007 [*] Add quotes to type alias
|
||||||
|
|
|
||||||
|
19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
21 | i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
| ^^^ TC007
|
||||||
|
22 | Bar)
|
||||||
|
|
|
||||||
|
= help: Add quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
18 18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
20 20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
21 |-i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
22 |- Bar)
|
||||||
|
21 |+i: TypeAlias = ("Foo | Bar")
|
||||||
|
23 22 |
|
||||||
|
24 23 | type C = Foo # OK
|
||||||
|
25 24 | type D = Foo | None # OK
|
||||||
|
|
||||||
|
TC007.py:22:5: TC007 [*] Add quotes to type alias
|
||||||
|
|
|
||||||
|
20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
21 | i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
22 | Bar)
|
||||||
|
| ^^^ TC007
|
||||||
|
23 |
|
||||||
|
24 | type C = Foo # OK
|
||||||
|
|
|
||||||
|
= help: Add quotes
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
18 18 | f: TypeAlias = Bar # TC007
|
||||||
|
19 19 | g: TypeAlias = Foo | Bar # TC007 x2
|
||||||
|
20 20 | h: TypeAlias = Foo[str] # TC007
|
||||||
|
21 |-i: TypeAlias = (Foo | # TC007 x2 (fix removes comment currently)
|
||||||
|
22 |- Bar)
|
||||||
|
21 |+i: TypeAlias = ("Foo | Bar")
|
||||||
|
23 22 |
|
||||||
|
24 23 | type C = Foo # OK
|
||||||
|
25 24 | type D = Foo | None # OK
|
|
@ -135,6 +135,35 @@ impl<'a> Binding<'a> {
|
||||||
self.flags.contains(BindingFlags::IN_EXCEPT_HANDLER)
|
self.flags.contains(BindingFlags::IN_EXCEPT_HANDLER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if this [`Binding`] represents a [PEP 613] type alias
|
||||||
|
/// e.g. `OptString` in:
|
||||||
|
/// ```python
|
||||||
|
/// from typing import TypeAlias
|
||||||
|
///
|
||||||
|
/// OptString: TypeAlias = str | None
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [PEP 613]: https://peps.python.org/pep-0613/
|
||||||
|
pub const fn is_annotated_type_alias(&self) -> bool {
|
||||||
|
self.flags.intersects(BindingFlags::ANNOTATED_TYPE_ALIAS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if this [`Binding`] represents a [PEP 695] type alias
|
||||||
|
/// e.g. `OptString` in:
|
||||||
|
/// ```python
|
||||||
|
/// type OptString = str | None
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [PEP 695]: https://peps.python.org/pep-0695/#generic-type-alias
|
||||||
|
pub const fn is_deferred_type_alias(&self) -> bool {
|
||||||
|
self.flags.intersects(BindingFlags::DEFERRED_TYPE_ALIAS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if this [`Binding`] represents either kind of type alias
|
||||||
|
pub const fn is_type_alias(&self) -> bool {
|
||||||
|
self.flags.intersects(BindingFlags::TYPE_ALIAS)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return `true` if this binding "redefines" the given binding, as per Pyflake's definition of
|
/// Return `true` if this binding "redefines" the given binding, as per Pyflake's definition of
|
||||||
/// redefinition.
|
/// redefinition.
|
||||||
pub fn redefines(&self, existing: &Binding) -> bool {
|
pub fn redefines(&self, existing: &Binding) -> bool {
|
||||||
|
@ -366,6 +395,19 @@ bitflags! {
|
||||||
/// y = 42
|
/// y = 42
|
||||||
/// ```
|
/// ```
|
||||||
const IN_EXCEPT_HANDLER = 1 << 10;
|
const IN_EXCEPT_HANDLER = 1 << 10;
|
||||||
|
|
||||||
|
/// The binding represents a [PEP 613] explicit type alias.
|
||||||
|
///
|
||||||
|
/// [PEP 613]: https://peps.python.org/pep-0613/
|
||||||
|
const ANNOTATED_TYPE_ALIAS = 1 << 11;
|
||||||
|
|
||||||
|
/// The binding represents a [PEP 695] type statement
|
||||||
|
///
|
||||||
|
/// [PEP 695]: https://peps.python.org/pep-0695/#generic-type-alias
|
||||||
|
const DEFERRED_TYPE_ALIAS = 1 << 12;
|
||||||
|
|
||||||
|
/// The binding represents any type alias.
|
||||||
|
const TYPE_ALIAS = Self::ANNOTATED_TYPE_ALIAS.bits() | Self::DEFERRED_TYPE_ALIAS.bits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -662,12 +662,135 @@ impl<'a> SemanticModel<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FIXME: Shouldn't this happen above where `class_variables_visible` is set?
|
||||||
seen_function |= scope.kind.is_function();
|
seen_function |= scope.kind.is_function();
|
||||||
}
|
}
|
||||||
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Simulates a runtime load of a given [`ast::ExprName`].
|
||||||
|
///
|
||||||
|
/// This should not be run until after all the bindings have been visited.
|
||||||
|
///
|
||||||
|
/// The main purpose of this method and what makes this different
|
||||||
|
/// from methods like [`SemanticModel::lookup_symbol`] and
|
||||||
|
/// [`SemanticModel::resolve_name`] is that it may be used
|
||||||
|
/// to perform speculative name lookups.
|
||||||
|
///
|
||||||
|
/// In most cases a load can be accurately modeled simply by calling
|
||||||
|
/// [`SemanticModel::resolve_name`] at the right time during semantic
|
||||||
|
/// analysis, however for speculative lookups this is not the case,
|
||||||
|
/// since we're aiming to change the semantic meaning of our load.
|
||||||
|
/// E.g. we want to check what would happen if we changed a forward
|
||||||
|
/// reference to an immediate load or vice versa.
|
||||||
|
///
|
||||||
|
/// Use caution when utilizing this method, since it was primarily designed
|
||||||
|
/// to work for speculative lookups from within type definitions, which
|
||||||
|
/// happen to share some nice properties, where attaching each binding
|
||||||
|
/// to a range in the source code and ordering those bindings based on
|
||||||
|
/// that range is a good enough approximation of which bindings are
|
||||||
|
/// available at runtime for which reference.
|
||||||
|
///
|
||||||
|
/// References from within an [`ast::Comprehension`] can produce incorrect
|
||||||
|
/// results when referring to a [`BindingKind::NamedExprAssignment`].
|
||||||
|
pub fn simulate_runtime_load(&self, name: &ast::ExprName) -> Option<BindingId> {
|
||||||
|
let symbol = name.id.as_str();
|
||||||
|
let range = name.range;
|
||||||
|
let mut seen_function = false;
|
||||||
|
let mut class_variables_visible = true;
|
||||||
|
let mut source_order_sensitive_lookup = true;
|
||||||
|
for (index, scope_id) in self.scopes.ancestor_ids(self.scope_id).enumerate() {
|
||||||
|
let scope = &self.scopes[scope_id];
|
||||||
|
|
||||||
|
// Only once we leave a function scope and its enclosing type scope should
|
||||||
|
// we stop doing source-order lookups. We could e.g. have nested classes
|
||||||
|
// where we lookup symbols from the innermost class scope, which can only see
|
||||||
|
// things from the outer class(es) that have been defined before the inner
|
||||||
|
// class. Source-order lookups take advantage of the fact that most of the
|
||||||
|
// bindings are created sequentially in source order, so if we want to
|
||||||
|
// determine whether or not a given reference can refer to another binding
|
||||||
|
// we can look at their text ranges to check whether or not the binding
|
||||||
|
// could actually be referred to. This is not as robust as back-tracking
|
||||||
|
// the AST, since that can properly take care of the few out-of order
|
||||||
|
// corner-cases, but back-tracking the AST from the reference to the binding
|
||||||
|
// is a lot more expensive than comparing a pair of text ranges.
|
||||||
|
if seen_function && !scope.kind.is_type() {
|
||||||
|
source_order_sensitive_lookup = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if scope.kind.is_class() {
|
||||||
|
if seen_function && matches!(symbol, "__class__") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if !class_variables_visible {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class_variables_visible = scope.kind.is_type() && index == 0;
|
||||||
|
seen_function |= scope.kind.is_function();
|
||||||
|
|
||||||
|
if let Some(binding_id) = scope.get(symbol) {
|
||||||
|
if source_order_sensitive_lookup {
|
||||||
|
// we need to look through all the shadowed bindings
|
||||||
|
// since we may be shadowing a source-order accurate
|
||||||
|
// runtime binding with a source-order inaccurate one
|
||||||
|
for shadowed_id in scope.shadowed_bindings(binding_id) {
|
||||||
|
let binding = &self.bindings[shadowed_id];
|
||||||
|
if binding.context.is_typing() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if let BindingKind::Annotation
|
||||||
|
| BindingKind::Deletion
|
||||||
|
| BindingKind::UnboundException(..)
|
||||||
|
| BindingKind::ConditionalDeletion(..) = binding.kind
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This ensures we perform the correct source-order lookup,
|
||||||
|
// since the ranges for these two types of bindings are trimmed
|
||||||
|
// to just the target, but the name is not available until the
|
||||||
|
// end of the entire statement
|
||||||
|
let binding_range = match binding.statement(self) {
|
||||||
|
Some(Stmt::Assign(stmt)) => stmt.range(),
|
||||||
|
Some(Stmt::AnnAssign(stmt)) => stmt.range(),
|
||||||
|
Some(Stmt::ClassDef(stmt)) => stmt.range(),
|
||||||
|
_ => binding.range,
|
||||||
|
};
|
||||||
|
|
||||||
|
if binding_range.ordering(range).is_lt() {
|
||||||
|
return Some(shadowed_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let candidate_id = match self.bindings[binding_id].kind {
|
||||||
|
BindingKind::Annotation => continue,
|
||||||
|
BindingKind::Deletion | BindingKind::UnboundException(None) => return None,
|
||||||
|
BindingKind::ConditionalDeletion(binding_id) => binding_id,
|
||||||
|
BindingKind::UnboundException(Some(binding_id)) => binding_id,
|
||||||
|
_ => binding_id,
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.bindings[candidate_id].context.is_typing() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Some(candidate_id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if index == 0 && scope.kind.is_class() {
|
||||||
|
if matches!(symbol, "__module__" | "__qualname__") {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// Lookup a qualified attribute in the current scope.
|
/// Lookup a qualified attribute in the current scope.
|
||||||
///
|
///
|
||||||
/// For example, given `["Class", "method"`], resolve the `BindingKind::ClassDefinition`
|
/// For example, given `["Class", "method"`], resolve the `BindingKind::ClassDefinition`
|
||||||
|
@ -1667,6 +1790,42 @@ impl<'a> SemanticModel<'a> {
|
||||||
|| (self.in_future_type_definition() && self.in_typing_only_annotation())
|
|| (self.in_future_type_definition() && self.in_typing_only_annotation())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the model is visiting the value expression
|
||||||
|
/// of a [PEP 613] type alias.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```python
|
||||||
|
/// from typing import TypeAlias
|
||||||
|
///
|
||||||
|
/// OptStr: TypeAlias = str | None # We're visiting the RHS
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [PEP 613]: https://peps.python.org/pep-0613/
|
||||||
|
pub const fn in_annotated_type_alias_value(&self) -> bool {
|
||||||
|
self.flags
|
||||||
|
.intersects(SemanticModelFlags::ANNOTATED_TYPE_ALIAS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the model is visiting the value expression
|
||||||
|
/// of a [PEP 695] type alias.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```python
|
||||||
|
/// type OptStr = str | None # We're visiting the RHS
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [PEP 695]: https://peps.python.org/pep-0695/#generic-type-alias
|
||||||
|
pub const fn in_deferred_type_alias_value(&self) -> bool {
|
||||||
|
self.flags
|
||||||
|
.intersects(SemanticModelFlags::DEFERRED_TYPE_ALIAS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the model is visiting the value expression of
|
||||||
|
/// either kind of type alias.
|
||||||
|
pub const fn in_type_alias_value(&self) -> bool {
|
||||||
|
self.flags.intersects(SemanticModelFlags::TYPE_ALIAS)
|
||||||
|
}
|
||||||
|
|
||||||
/// Return `true` if the model is in an exception handler.
|
/// Return `true` if the model is in an exception handler.
|
||||||
pub const fn in_exception_handler(&self) -> bool {
|
pub const fn in_exception_handler(&self) -> bool {
|
||||||
self.flags.intersects(SemanticModelFlags::EXCEPTION_HANDLER)
|
self.flags.intersects(SemanticModelFlags::EXCEPTION_HANDLER)
|
||||||
|
@ -2243,6 +2402,27 @@ bitflags! {
|
||||||
/// [no_type_check]: https://docs.python.org/3/library/typing.html#typing.no_type_check
|
/// [no_type_check]: https://docs.python.org/3/library/typing.html#typing.no_type_check
|
||||||
/// [#13824]: https://github.com/astral-sh/ruff/issues/13824
|
/// [#13824]: https://github.com/astral-sh/ruff/issues/13824
|
||||||
const NO_TYPE_CHECK = 1 << 26;
|
const NO_TYPE_CHECK = 1 << 26;
|
||||||
|
/// The model is in the value expression of a [PEP 613] explicit type alias.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```python
|
||||||
|
/// from typing import TypeAlias
|
||||||
|
///
|
||||||
|
/// OptStr: TypeAlias = str | None # We're visiting the RHS
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [PEP 613]: https://peps.python.org/pep-0613/
|
||||||
|
const ANNOTATED_TYPE_ALIAS = 1 << 27;
|
||||||
|
|
||||||
|
/// The model is in the value expression of a [PEP 695] type statement.
|
||||||
|
///
|
||||||
|
/// For example:
|
||||||
|
/// ```python
|
||||||
|
/// type OptStr = str | None # We're visiting the RHS
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [PEP 695]: https://peps.python.org/pep-0695/#generic-type-alias
|
||||||
|
const DEFERRED_TYPE_ALIAS = 1 << 28;
|
||||||
|
|
||||||
/// 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();
|
||||||
|
@ -2260,6 +2440,9 @@ bitflags! {
|
||||||
/// The context is in a typing-only context.
|
/// The context is in a typing-only context.
|
||||||
const TYPING_CONTEXT = Self::TYPE_CHECKING_BLOCK.bits() | Self::TYPING_ONLY_ANNOTATION.bits() |
|
const TYPING_CONTEXT = Self::TYPE_CHECKING_BLOCK.bits() | Self::TYPING_ONLY_ANNOTATION.bits() |
|
||||||
Self::STRING_TYPE_DEFINITION.bits() | Self::TYPE_PARAM_DEFINITION.bits();
|
Self::STRING_TYPE_DEFINITION.bits() | Self::TYPE_PARAM_DEFINITION.bits();
|
||||||
|
|
||||||
|
/// The context is in any type alias.
|
||||||
|
const TYPE_ALIAS = Self::ANNOTATED_TYPE_ALIAS.bits() | Self::DEFERRED_TYPE_ALIAS.bits();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -85,6 +85,14 @@ impl ResolvedReference {
|
||||||
self.flags
|
self.flags
|
||||||
.intersects(SemanticModelFlags::DUNDER_ALL_DEFINITION)
|
.intersects(SemanticModelFlags::DUNDER_ALL_DEFINITION)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return `true` if the context is in the r.h.s. of a [PEP 613] type alias.
|
||||||
|
///
|
||||||
|
/// [PEP 613]: https://peps.python.org/pep-0613/
|
||||||
|
pub const fn in_annotated_type_alias_value(&self) -> bool {
|
||||||
|
self.flags
|
||||||
|
.intersects(SemanticModelFlags::ANNOTATED_TYPE_ALIAS)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ranged for ResolvedReference {
|
impl Ranged for ResolvedReference {
|
||||||
|
|
2
ruff.schema.json
generated
2
ruff.schema.json
generated
|
@ -4011,6 +4011,8 @@
|
||||||
"TC004",
|
"TC004",
|
||||||
"TC005",
|
"TC005",
|
||||||
"TC006",
|
"TC006",
|
||||||
|
"TC007",
|
||||||
|
"TC008",
|
||||||
"TC01",
|
"TC01",
|
||||||
"TC010",
|
"TC010",
|
||||||
"TD",
|
"TD",
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue