From d262121e41cd11ad4e92dc3234f0b187d312fc58 Mon Sep 17 00:00:00 2001 From: bahtya Date: Thu, 9 Apr 2026 08:43:41 +0800 Subject: [PATCH 1/2] fix: handle union-bound TypeVar in type[T] callable analysis When a TypeVar T has a union bound (e.g. T: bool|int|float|str), calling a value of type type[T] would incorrectly infer the return type as the union rather than T. This happened because analyze_type_type_callee only replaced return types for CallableType and Overloaded, not for UnionType. Additionally, CallableType.type_object() would crash with an assertion error when encountering a TypeVarType whose upper_bound is a union, since it expected all paths to resolve to an Instance. Fixes #21106 Signed-off-by: bahtya --- mypy/checkexpr.py | 17 +++++++++++++++++ mypy/types.py | 9 +++++++++ 2 files changed, 26 insertions(+) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index a173025cda299..4e7fab62f9afc 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1900,6 +1900,15 @@ def can_return_none(self, type: TypeInfo, attr_name: str) -> bool: return is_subtype(NoneType(), node.type.ret_type) return False + def _replace_callable_return_type(self, tp: Type, replacement: Type) -> ProperType: + """Replace the return type of a callable or overloaded type with replacement.""" + ptp = get_proper_type(tp) + if isinstance(ptp, CallableType): + return ptp.copy_modified(ret_type=replacement) + if isinstance(ptp, Overloaded): + return Overloaded([c.copy_modified(ret_type=replacement) for c in ptp.items]) + return ptp + def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type: """Analyze the callee X in X(...) where X is Type[item]. @@ -1936,6 +1945,14 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type: callee = callee.copy_modified(ret_type=item) elif isinstance(callee, Overloaded): callee = Overloaded([c.copy_modified(ret_type=item) for c in callee.items]) + elif isinstance(callee, UnionType): + callee = UnionType( + [ + self._replace_callable_return_type(tp, item) + for tp in callee.relevant_items() + ], + callee.line, + ) return callee # We support Type of namedtuples but not of tuples in general if isinstance(item, TupleType) and tuple_fallback(item).type.fullname != "builtins.tuple": diff --git a/mypy/types.py b/mypy/types.py index d4ed728f4c9b8..1ab06a338631f 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -2311,6 +2311,15 @@ def type_object(self) -> mypy.nodes.TypeInfo: ret = get_proper_type(self.ret_type) if isinstance(ret, TypeVarType): ret = get_proper_type(ret.upper_bound) + if isinstance(ret, UnionType): + # When the TypeVar has a union bound, pick the first item's + # fallback. This is only used for is_protocol checks, which + # are not applicable to union-bound typevars. + first = get_proper_type(ret.items[0]) + if isinstance(first, Instance): + ret = first + else: + ret = self.fallback if isinstance(ret, TupleType): ret = ret.partial_fallback if isinstance(ret, TypedDictType): From 8f458ba0a90435301393b008821657fa5d6e5cad Mon Sep 17 00:00:00 2001 From: Bahtya Date: Thu, 9 Apr 2026 19:22:57 +0800 Subject: [PATCH 2/2] fix: use union bound as return type for TypeVar with union bound in type[T] analysis When a TypeVar has a union bound (e.g., T = TypeVar("T", bound=A | B)), analyzing type[T] as a callable should return the union bound (A | B) as the return type, not the TypeVar (T) itself. This fixes the testMatchTypeObjectTypeVar test case where calling a type[T_Choice] where T_Choice is bound to One | Two should return One | Two, not T_Choice. Bahtya --- mypy/checkexpr.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 4e7fab62f9afc..ec421e6da238b 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1941,14 +1941,21 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type: # with typevar. callee = self.analyze_type_type_callee(get_proper_type(item.upper_bound), context) callee = get_proper_type(callee) + # When the TypeVar has a union bound, use the bound as the return type + # instead of the TypeVar itself, since calling the constructor should + # return an instance of one of the union members. + return_type = item + upper_bound = get_proper_type(item.upper_bound) + if isinstance(upper_bound, UnionType): + return_type = upper_bound if isinstance(callee, CallableType): - callee = callee.copy_modified(ret_type=item) + callee = callee.copy_modified(ret_type=return_type) elif isinstance(callee, Overloaded): - callee = Overloaded([c.copy_modified(ret_type=item) for c in callee.items]) + callee = Overloaded([c.copy_modified(ret_type=return_type) for c in callee.items]) elif isinstance(callee, UnionType): callee = UnionType( [ - self._replace_callable_return_type(tp, item) + self._replace_callable_return_type(tp, return_type) for tp in callee.relevant_items() ], callee.line,