Skip to content

Commit d262121

Browse files
committed
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 <bahtyar153@qq.com>
1 parent bb05513 commit d262121

File tree

2 files changed

+26
-0
lines changed

2 files changed

+26
-0
lines changed

mypy/checkexpr.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1900,6 +1900,15 @@ def can_return_none(self, type: TypeInfo, attr_name: str) -> bool:
19001900
return is_subtype(NoneType(), node.type.ret_type)
19011901
return False
19021902

1903+
def _replace_callable_return_type(self, tp: Type, replacement: Type) -> ProperType:
1904+
"""Replace the return type of a callable or overloaded type with replacement."""
1905+
ptp = get_proper_type(tp)
1906+
if isinstance(ptp, CallableType):
1907+
return ptp.copy_modified(ret_type=replacement)
1908+
if isinstance(ptp, Overloaded):
1909+
return Overloaded([c.copy_modified(ret_type=replacement) for c in ptp.items])
1910+
return ptp
1911+
19031912
def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type:
19041913
"""Analyze the callee X in X(...) where X is Type[item].
19051914
@@ -1936,6 +1945,14 @@ def analyze_type_type_callee(self, item: ProperType, context: Context) -> Type:
19361945
callee = callee.copy_modified(ret_type=item)
19371946
elif isinstance(callee, Overloaded):
19381947
callee = Overloaded([c.copy_modified(ret_type=item) for c in callee.items])
1948+
elif isinstance(callee, UnionType):
1949+
callee = UnionType(
1950+
[
1951+
self._replace_callable_return_type(tp, item)
1952+
for tp in callee.relevant_items()
1953+
],
1954+
callee.line,
1955+
)
19391956
return callee
19401957
# We support Type of namedtuples but not of tuples in general
19411958
if isinstance(item, TupleType) and tuple_fallback(item).type.fullname != "builtins.tuple":

mypy/types.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2311,6 +2311,15 @@ def type_object(self) -> mypy.nodes.TypeInfo:
23112311
ret = get_proper_type(self.ret_type)
23122312
if isinstance(ret, TypeVarType):
23132313
ret = get_proper_type(ret.upper_bound)
2314+
if isinstance(ret, UnionType):
2315+
# When the TypeVar has a union bound, pick the first item's
2316+
# fallback. This is only used for is_protocol checks, which
2317+
# are not applicable to union-bound typevars.
2318+
first = get_proper_type(ret.items[0])
2319+
if isinstance(first, Instance):
2320+
ret = first
2321+
else:
2322+
ret = self.fallback
23142323
if isinstance(ret, TupleType):
23152324
ret = ret.partial_fallback
23162325
if isinstance(ret, TypedDictType):

0 commit comments

Comments
 (0)