Skip to content

fix: handle union-bound TypeVar in type[T] callable analysis#21191

Open
Bahtya wants to merge 1 commit intopython:masterfrom
Bahtya:fix/union-bound-generic-inference
Open

fix: handle union-bound TypeVar in type[T] callable analysis#21191
Bahtya wants to merge 1 commit intopython:masterfrom
Bahtya:fix/union-bound-generic-inference

Conversation

@Bahtya
Copy link
Copy Markdown

@Bahtya Bahtya commented Apr 9, 2026

Problem

When a TypeVar T has a union bound (e.g. T: bool | int | float | str), calling a value of type type[T] produces a false positive:

T = TypeVar("T", bound="bool | int | float | str")

def func(ftype: type[T], value: str | None) -> T:
    if value is None:
        return ftype()      # error: Incompatible return value type
    return ftype(value)     # error: Incompatible return value type

The error is: Incompatible return value type (got "int | float | str", expected "T").

With a single-type bound (e.g. T: int) this works correctly.

Root Cause

Two issues in checkexpr.py and types.py:

  1. analyze_type_type_callee (checkexpr.py): When handling TypeVarType, it recurses on the upper bound. For a union bound, this returns a UnionType of callables, but the code only replaced return types with the TypeVar for CallableType and Overloaded — not UnionType. The union members kept their original return types (e.g. int, float) instead of T.

  2. CallableType.type_object() (types.py): When a TypeVar's upper bound is a union, get_proper_type(ret.upper_bound) returns a UnionType, which hits the final assert isinstance(ret, Instance) and crashes.

Solution

  1. Added a _replace_callable_return_type helper and a UnionType branch in the TypeVarType handling in analyze_type_type_callee, so each callable in the union gets its return type replaced with the TypeVar.

  2. Added a UnionType case in CallableType.type_object() that resolves to the first Instance in the union (this is only used for is_protocol checks, which don't apply to union-bound TypeVars).

Testing

Verified with the exact reproduction case from the issue — now passes with no errors. Also tested:

  • Single-type bounds still work (no regression)
  • Unbounded TypeVars still work
  • Correct type inference: reveal_type(func(bool, 'True')) shows bool, not the full union
  • Self-check passes for both modified files (mypy_self_check.ini)

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 python#21106

Signed-off-by: bahtya <bahtyar153@qq.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 9, 2026

According to mypy_primer, this change doesn't affect type check results on a corpus of open source code. ✅

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant