From f551224faf58b660713e58ef58fbfeb0b01b6dc0 Mon Sep 17 00:00:00 2001 From: David Peter Date: Tue, 16 Dec 2025 10:20:22 +0100 Subject: [PATCH] =?UTF-8?q?[ty]=20Infer=20precise=20types=20for=20`isinsta?= =?UTF-8?q?nce(=E2=80=A6)`=20calls=20involving=20typevars?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../resources/mdtest/call/builtins.md | 25 +++++++++++++++++++ .../ty_python_semantic/src/types/function.rs | 18 ++++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/crates/ty_python_semantic/resources/mdtest/call/builtins.md b/crates/ty_python_semantic/resources/mdtest/call/builtins.md index 0d58d86b3f..f9342151ea 100644 --- a/crates/ty_python_semantic/resources/mdtest/call/builtins.md +++ b/crates/ty_python_semantic/resources/mdtest/call/builtins.md @@ -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 diff --git a/crates/ty_python_semantic/src/types/function.rs b/crates/ty_python_semantic/src/types/function.rs index e727e6663b..37c9a6d184 100644 --- a/crates/ty_python_semantic/src/types/function.rs +++ b/crates/ty_python_semantic/src/types/function.rs @@ -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(..)