mirror of https://github.com/astral-sh/ruff
[ty] Infer precise types for `isinstance(…)` calls involving typevars (#21999)
## Summary Infer `Literal[True]` for `isinstance(x, C)` calls when `x: T` and `T` has a bound `B` that satisfies the `isinstance` check against `C`. Similar for constrained typevars. closes https://github.com/astral-sh/ty/issues/1895 ## Test Plan * New Markdown tests * Verified the the example in the linked ticket checks without errors
This commit is contained in:
parent
4fdbe26445
commit
2acf1cc0fd
|
|
@ -114,6 +114,7 @@ but fall back to `bool` otherwise.
|
||||||
```py
|
```py
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from types import FunctionType
|
from types import FunctionType
|
||||||
|
from typing import TypeVar
|
||||||
|
|
||||||
class Answer(Enum):
|
class Answer(Enum):
|
||||||
NO = 0
|
NO = 0
|
||||||
|
|
@ -137,6 +138,7 @@ reveal_type(isinstance("", int)) # revealed: bool
|
||||||
|
|
||||||
class A: ...
|
class A: ...
|
||||||
class SubclassOfA(A): ...
|
class SubclassOfA(A): ...
|
||||||
|
class OtherSubclassOfA(A): ...
|
||||||
class B: ...
|
class B: ...
|
||||||
|
|
||||||
reveal_type(isinstance(A, type)) # revealed: Literal[True]
|
reveal_type(isinstance(A, type)) # revealed: Literal[True]
|
||||||
|
|
@ -161,6 +163,29 @@ def _(x: A | B, y: list[int]):
|
||||||
else:
|
else:
|
||||||
reveal_type(x) # revealed: B & ~A
|
reveal_type(x) # revealed: B & ~A
|
||||||
reveal_type(isinstance(x, B)) # revealed: Literal[True]
|
reveal_type(isinstance(x, B)) # revealed: Literal[True]
|
||||||
|
|
||||||
|
T = TypeVar("T")
|
||||||
|
T_bound_A = TypeVar("T_bound_A", bound=A)
|
||||||
|
T_constrained = TypeVar("T_constrained", SubclassOfA, OtherSubclassOfA)
|
||||||
|
|
||||||
|
def _(
|
||||||
|
x: T,
|
||||||
|
x_bound_a: T_bound_A,
|
||||||
|
x_constrained_sub_a: T_constrained,
|
||||||
|
):
|
||||||
|
reveal_type(isinstance(x, object)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x, A)) # revealed: bool
|
||||||
|
|
||||||
|
reveal_type(isinstance(x_bound_a, object)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x_bound_a, A)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x_bound_a, SubclassOfA)) # revealed: bool
|
||||||
|
reveal_type(isinstance(x_bound_a, B)) # revealed: bool
|
||||||
|
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, object)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, A)) # revealed: Literal[True]
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, SubclassOfA)) # revealed: bool
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, OtherSubclassOfA)) # revealed: bool
|
||||||
|
reveal_type(isinstance(x_constrained_sub_a, B)) # revealed: bool
|
||||||
```
|
```
|
||||||
|
|
||||||
Certain special forms in the typing module are not instances of `type`, so are strictly-speaking
|
Certain special forms in the typing module are not instances of `type`, so are strictly-speaking
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,8 @@ use crate::types::{
|
||||||
ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
|
ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
|
||||||
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
|
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
|
||||||
NormalizedVisitor, SpecialFormType, SubclassOfInner, SubclassOfType, Truthiness, Type,
|
NormalizedVisitor, SpecialFormType, SubclassOfInner, SubclassOfType, Truthiness, Type,
|
||||||
TypeContext, TypeMapping, TypeRelation, UnionBuilder, binding_type, definition_expression_type,
|
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, UnionBuilder, binding_type,
|
||||||
infer_definition_types, walk_signature,
|
definition_expression_type, infer_definition_types, walk_signature,
|
||||||
};
|
};
|
||||||
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
use crate::{Db, FxOrderSet, ModuleName, resolve_module};
|
||||||
|
|
||||||
|
|
@ -1268,6 +1268,19 @@ fn is_instance_truthiness<'db>(
|
||||||
|
|
||||||
Type::TypeAlias(alias) => is_instance_truthiness(db, alias.value_type(db), class),
|
Type::TypeAlias(alias) => is_instance_truthiness(db, alias.value_type(db), class),
|
||||||
|
|
||||||
|
Type::TypeVar(bound_typevar) => match bound_typevar.typevar(db).bound_or_constraints(db) {
|
||||||
|
None => is_instance_truthiness(db, Type::object(), class),
|
||||||
|
Some(TypeVarBoundOrConstraints::UpperBound(bound)) => {
|
||||||
|
is_instance_truthiness(db, bound, class)
|
||||||
|
}
|
||||||
|
Some(TypeVarBoundOrConstraints::Constraints(constraints)) => always_true_if(
|
||||||
|
constraints
|
||||||
|
.elements(db)
|
||||||
|
.iter()
|
||||||
|
.all(|c| is_instance_truthiness(db, *c, class).is_always_true()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
|
||||||
Type::BoundMethod(..)
|
Type::BoundMethod(..)
|
||||||
| Type::KnownBoundMethod(..)
|
| Type::KnownBoundMethod(..)
|
||||||
| Type::WrapperDescriptor(..)
|
| Type::WrapperDescriptor(..)
|
||||||
|
|
@ -1281,7 +1294,6 @@ fn is_instance_truthiness<'db>(
|
||||||
| Type::PropertyInstance(..)
|
| Type::PropertyInstance(..)
|
||||||
| Type::AlwaysTruthy
|
| Type::AlwaysTruthy
|
||||||
| Type::AlwaysFalsy
|
| Type::AlwaysFalsy
|
||||||
| Type::TypeVar(..)
|
|
||||||
| Type::BoundSuper(..)
|
| Type::BoundSuper(..)
|
||||||
| Type::TypeIs(..)
|
| Type::TypeIs(..)
|
||||||
| Type::Callable(..)
|
| Type::Callable(..)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue