[ty] Infer precise types for `isinstance(…)` calls involving typevars

This commit is contained in:
David Peter 2025-12-16 10:20:22 +01:00
parent 5372bb3440
commit f551224faf
2 changed files with 40 additions and 3 deletions

View File

@ -114,6 +114,7 @@ but fall back to `bool` otherwise.
```py
from enum import Enum
from types import FunctionType
from typing import TypeVar
class Answer(Enum):
NO = 0
@ -137,6 +138,7 @@ reveal_type(isinstance("", int)) # revealed: bool
class A: ...
class SubclassOfA(A): ...
class OtherSubclassOfA(A): ...
class B: ...
reveal_type(isinstance(A, type)) # revealed: Literal[True]
@ -161,6 +163,29 @@ def _(x: A | B, y: list[int]):
else:
reveal_type(x) # revealed: B & ~A
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

View File

@ -84,8 +84,8 @@ use crate::types::{
ClassBase, ClassLiteral, ClassType, DeprecatedInstance, DynamicType, FindLegacyTypeVarsVisitor,
HasRelationToVisitor, IsDisjointVisitor, IsEquivalentVisitor, KnownClass, KnownInstanceType,
NormalizedVisitor, SpecialFormType, SubclassOfInner, SubclassOfType, Truthiness, Type,
TypeContext, TypeMapping, TypeRelation, UnionBuilder, binding_type, definition_expression_type,
infer_definition_types, walk_signature,
TypeContext, TypeMapping, TypeRelation, TypeVarBoundOrConstraints, UnionBuilder, binding_type,
definition_expression_type, infer_definition_types, walk_signature,
};
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::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::KnownBoundMethod(..)
| Type::WrapperDescriptor(..)
@ -1281,7 +1294,6 @@ fn is_instance_truthiness<'db>(
| Type::PropertyInstance(..)
| Type::AlwaysTruthy
| Type::AlwaysFalsy
| Type::TypeVar(..)
| Type::BoundSuper(..)
| Type::TypeIs(..)
| Type::Callable(..)