mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-08 20:58:32 +00:00
Track TYPE_CHECKING
blocks in Importer
(#4593)
This commit is contained in:
parent
0854543328
commit
ea31229be0
9 changed files with 155 additions and 121 deletions
|
@ -16,6 +16,7 @@ ruff_index = { path = "../ruff_index" }
|
|||
bitflags = { workspace = true }
|
||||
is-macro = { workspace = true }
|
||||
nohash-hasher = { workspace = true }
|
||||
num-traits = { workspace = true }
|
||||
rustc-hash = { workspace = true }
|
||||
rustpython-parser = { workspace = true }
|
||||
smallvec = { workspace = true }
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use rustpython_parser::ast::{self, Constant, Expr, Operator};
|
||||
|
||||
use num_traits::identities::Zero;
|
||||
use ruff_python_ast::call_path::{from_unqualified_name, CallPath};
|
||||
use ruff_python_stdlib::typing::{
|
||||
IMMUTABLE_GENERIC_TYPES, IMMUTABLE_TYPES, PEP_585_GENERICS, PEP_593_SUBSCRIPTS, SUBSCRIPTS,
|
||||
|
@ -26,39 +27,41 @@ pub enum SubscriptKind {
|
|||
|
||||
pub fn match_annotated_subscript<'a>(
|
||||
expr: &Expr,
|
||||
model: &SemanticModel,
|
||||
semantic_model: &SemanticModel,
|
||||
typing_modules: impl Iterator<Item = &'a str>,
|
||||
) -> Option<SubscriptKind> {
|
||||
if !matches!(expr, Expr::Name(_) | Expr::Attribute(_)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
model.resolve_call_path(expr).and_then(|call_path| {
|
||||
if SUBSCRIPTS.contains(&call_path.as_slice()) {
|
||||
return Some(SubscriptKind::AnnotatedSubscript);
|
||||
}
|
||||
if PEP_593_SUBSCRIPTS.contains(&call_path.as_slice()) {
|
||||
return Some(SubscriptKind::PEP593AnnotatedSubscript);
|
||||
}
|
||||
semantic_model
|
||||
.resolve_call_path(expr)
|
||||
.and_then(|call_path| {
|
||||
if SUBSCRIPTS.contains(&call_path.as_slice()) {
|
||||
return Some(SubscriptKind::AnnotatedSubscript);
|
||||
}
|
||||
if PEP_593_SUBSCRIPTS.contains(&call_path.as_slice()) {
|
||||
return Some(SubscriptKind::PEP593AnnotatedSubscript);
|
||||
}
|
||||
|
||||
for module in typing_modules {
|
||||
let module_call_path: CallPath = from_unqualified_name(module);
|
||||
if call_path.starts_with(&module_call_path) {
|
||||
for subscript in SUBSCRIPTS.iter() {
|
||||
if call_path.last() == subscript.last() {
|
||||
return Some(SubscriptKind::AnnotatedSubscript);
|
||||
for module in typing_modules {
|
||||
let module_call_path: CallPath = from_unqualified_name(module);
|
||||
if call_path.starts_with(&module_call_path) {
|
||||
for subscript in SUBSCRIPTS.iter() {
|
||||
if call_path.last() == subscript.last() {
|
||||
return Some(SubscriptKind::AnnotatedSubscript);
|
||||
}
|
||||
}
|
||||
}
|
||||
for subscript in PEP_593_SUBSCRIPTS.iter() {
|
||||
if call_path.last() == subscript.last() {
|
||||
return Some(SubscriptKind::PEP593AnnotatedSubscript);
|
||||
for subscript in PEP_593_SUBSCRIPTS.iter() {
|
||||
if call_path.last() == subscript.last() {
|
||||
return Some(SubscriptKind::PEP593AnnotatedSubscript);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
})
|
||||
None
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||
|
@ -80,25 +83,27 @@ impl std::fmt::Display for ModuleMember {
|
|||
|
||||
/// Returns the PEP 585 standard library generic variant for a `typing` module reference, if such
|
||||
/// a variant exists.
|
||||
pub fn to_pep585_generic(expr: &Expr, model: &SemanticModel) -> Option<ModuleMember> {
|
||||
model.resolve_call_path(expr).and_then(|call_path| {
|
||||
let [module, name] = call_path.as_slice() else {
|
||||
pub fn to_pep585_generic(expr: &Expr, semantic_model: &SemanticModel) -> Option<ModuleMember> {
|
||||
semantic_model
|
||||
.resolve_call_path(expr)
|
||||
.and_then(|call_path| {
|
||||
let [module, name] = call_path.as_slice() else {
|
||||
return None;
|
||||
};
|
||||
PEP_585_GENERICS
|
||||
.iter()
|
||||
.find_map(|((from_module, from_member), (to_module, to_member))| {
|
||||
if module == from_module && name == from_member {
|
||||
if to_module.is_empty() {
|
||||
Some(ModuleMember::BuiltIn(to_member))
|
||||
PEP_585_GENERICS.iter().find_map(
|
||||
|((from_module, from_member), (to_module, to_member))| {
|
||||
if module == from_module && name == from_member {
|
||||
if to_module.is_empty() {
|
||||
Some(ModuleMember::BuiltIn(to_member))
|
||||
} else {
|
||||
Some(ModuleMember::Member(to_module, to_member))
|
||||
}
|
||||
} else {
|
||||
Some(ModuleMember::Member(to_module, to_member))
|
||||
None
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
})
|
||||
},
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return whether a given expression uses a PEP 585 standard library generic.
|
||||
|
@ -128,7 +133,7 @@ pub enum Pep604Operator {
|
|||
pub fn to_pep604_operator(
|
||||
value: &Expr,
|
||||
slice: &Expr,
|
||||
model: &SemanticModel,
|
||||
semantic_model: &SemanticModel,
|
||||
) -> Option<Pep604Operator> {
|
||||
/// Returns `true` if any argument in the slice is a string.
|
||||
fn any_arg_is_str(slice: &Expr) -> bool {
|
||||
|
@ -148,13 +153,13 @@ pub fn to_pep604_operator(
|
|||
return None;
|
||||
}
|
||||
|
||||
model
|
||||
semantic_model
|
||||
.resolve_call_path(value)
|
||||
.as_ref()
|
||||
.and_then(|call_path| {
|
||||
if model.match_typing_call_path(call_path, "Optional") {
|
||||
if semantic_model.match_typing_call_path(call_path, "Optional") {
|
||||
Some(Pep604Operator::Optional)
|
||||
} else if model.match_typing_call_path(call_path, "Union") {
|
||||
} else if semantic_model.match_typing_call_path(call_path, "Union") {
|
||||
Some(Pep604Operator::Union)
|
||||
} else {
|
||||
None
|
||||
|
@ -164,18 +169,21 @@ pub fn to_pep604_operator(
|
|||
|
||||
/// Return `true` if `Expr` represents a reference to a type annotation that resolves to an
|
||||
/// immutable type.
|
||||
pub fn is_immutable_annotation(model: &SemanticModel, expr: &Expr) -> bool {
|
||||
pub fn is_immutable_annotation(semantic_model: &SemanticModel, expr: &Expr) -> bool {
|
||||
match expr {
|
||||
Expr::Name(_) | Expr::Attribute(_) => {
|
||||
model.resolve_call_path(expr).map_or(false, |call_path| {
|
||||
IMMUTABLE_TYPES
|
||||
.iter()
|
||||
.chain(IMMUTABLE_GENERIC_TYPES)
|
||||
.any(|target| call_path.as_slice() == *target)
|
||||
})
|
||||
semantic_model
|
||||
.resolve_call_path(expr)
|
||||
.map_or(false, |call_path| {
|
||||
IMMUTABLE_TYPES
|
||||
.iter()
|
||||
.chain(IMMUTABLE_GENERIC_TYPES)
|
||||
.any(|target| call_path.as_slice() == *target)
|
||||
})
|
||||
}
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => {
|
||||
model.resolve_call_path(value).map_or(false, |call_path| {
|
||||
Expr::Subscript(ast::ExprSubscript { value, slice, .. }) => semantic_model
|
||||
.resolve_call_path(value)
|
||||
.map_or(false, |call_path| {
|
||||
if IMMUTABLE_GENERIC_TYPES
|
||||
.iter()
|
||||
.any(|target| call_path.as_slice() == *target)
|
||||
|
@ -183,30 +191,33 @@ pub fn is_immutable_annotation(model: &SemanticModel, expr: &Expr) -> bool {
|
|||
true
|
||||
} else if call_path.as_slice() == ["typing", "Union"] {
|
||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||
elts.iter().all(|elt| is_immutable_annotation(model, elt))
|
||||
elts.iter()
|
||||
.all(|elt| is_immutable_annotation(semantic_model, elt))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else if call_path.as_slice() == ["typing", "Optional"] {
|
||||
is_immutable_annotation(model, slice)
|
||||
is_immutable_annotation(semantic_model, slice)
|
||||
} else if call_path.as_slice() == ["typing", "Annotated"] {
|
||||
if let Expr::Tuple(ast::ExprTuple { elts, .. }) = slice.as_ref() {
|
||||
elts.first()
|
||||
.map_or(false, |elt| is_immutable_annotation(model, elt))
|
||||
.map_or(false, |elt| is_immutable_annotation(semantic_model, elt))
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
})
|
||||
}
|
||||
}),
|
||||
Expr::BinOp(ast::ExprBinOp {
|
||||
left,
|
||||
op: Operator::BitOr,
|
||||
right,
|
||||
range: _range,
|
||||
}) => is_immutable_annotation(model, left) && is_immutable_annotation(model, right),
|
||||
}) => {
|
||||
is_immutable_annotation(semantic_model, left)
|
||||
&& is_immutable_annotation(semantic_model, right)
|
||||
}
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::None,
|
||||
..
|
||||
|
@ -238,16 +249,57 @@ const IMMUTABLE_FUNCS: &[&[&str]] = &[
|
|||
|
||||
/// Return `true` if `func` is a function that returns an immutable object.
|
||||
pub fn is_immutable_func(
|
||||
model: &SemanticModel,
|
||||
semantic_model: &SemanticModel,
|
||||
func: &Expr,
|
||||
extend_immutable_calls: &[CallPath],
|
||||
) -> bool {
|
||||
model.resolve_call_path(func).map_or(false, |call_path| {
|
||||
IMMUTABLE_FUNCS
|
||||
.iter()
|
||||
.any(|target| call_path.as_slice() == *target)
|
||||
|| extend_immutable_calls
|
||||
semantic_model
|
||||
.resolve_call_path(func)
|
||||
.map_or(false, |call_path| {
|
||||
IMMUTABLE_FUNCS
|
||||
.iter()
|
||||
.any(|target| call_path == *target)
|
||||
})
|
||||
.any(|target| call_path.as_slice() == *target)
|
||||
|| extend_immutable_calls
|
||||
.iter()
|
||||
.any(|target| call_path == *target)
|
||||
})
|
||||
}
|
||||
|
||||
/// Return `true` if [`Expr`] is a guard for a type-checking block.
|
||||
pub fn is_type_checking_block(stmt: &ast::StmtIf, semantic_model: &SemanticModel) -> bool {
|
||||
let ast::StmtIf { test, .. } = stmt;
|
||||
|
||||
// Ex) `if False:`
|
||||
if matches!(
|
||||
test.as_ref(),
|
||||
Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Bool(false),
|
||||
..
|
||||
})
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Ex) `if 0:`
|
||||
if let Expr::Constant(ast::ExprConstant {
|
||||
value: Constant::Int(value),
|
||||
..
|
||||
}) = test.as_ref()
|
||||
{
|
||||
if value.is_zero() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Ex) `if typing.TYPE_CHECKING:`
|
||||
if semantic_model
|
||||
.resolve_call_path(test)
|
||||
.map_or(false, |call_path| {
|
||||
call_path.as_slice() == ["typing", "TYPE_CHECKING"]
|
||||
})
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue