Skip to content

Fix stubtest false positive for a typevar default across overloads - #21862

Open
arose26 wants to merge 1 commit into
python:masterfrom
arose26:stubtest-typevar-union-default
Open

Fix stubtest false positive for a typevar default across overloads#21862
arose26 wants to merge 1 commit into
python:masterfrom
arose26:stubtest-typevar-union-default

Conversation

@arose26

@arose26 arose26 commented Aug 17, 2026

Copy link
Copy Markdown

Fixes #21597

Adding a third overload that reuses _T makes stubtest reject a runtime default it accepted with two:

# runtime
def foo(x=0, ret=1):
    return ret
_T = TypeVar("_T")

@overload
def foo(x: int = 0) -> int: ...
@overload
def foo(x: int, ret: _T) -> _T: ...
@overload
def foo(x: int = 0, *, ret: _T) -> _T: ...  # adding this triggers the error
error: m.foo is inconsistent, runtime parameter "ret" has a default value of type
Literal[1], which is incompatible with stub parameter type _T | _T.
Inferred signature: def (x: int = ..., ret: _T | _T = ...)

Cause

_T | _T is not a failure to deduplicate. Signature.from_overloadedfuncdef already merges argument types with make_simplified_union, but each overload item has its own type variable ids, so the _T from one item and the _T from another are distinct variables and the union is kept.

The problem is in _verify_arg_default_value, which unwrapped only a bare type variable:

if isinstance(stub_type, mypy.types.TypeVarType):
    stub_type = stub_type.upper_bound

A union of type variables never took that branch, and nothing is a subtype of it, so any runtime default failed as soon as a second overload item mentioned the same TypeVar.

Change

The unwrapping now applies inside a union too, so _T | _T is checked as object rather than as a union no value inhabits.

Upper bounds are preserved rather than erased: erase_typevars() would have replaced the variables with Any and quietly stopped catching real mismatches, so the helper maps each variable to its own upper bound. A bounded typevar still rejects an incompatible default — there is a test for exactly that, and the message it produces reads stub parameter type str instead of str | str.

Tests

Two cases added to test_overload:

  • three overloads sharing an unbounded _T with a runtime default: no error (fails before this change)
  • the same shape with TypeVar(bound=str) and a runtime default of 1: still an error

mypy/test/teststubtest.py passes (67 tests), self-check on mypy/stubtest.py is clean, and ruff / black are clean on both files.

The default value check unwrapped a bare TypeVar to its upper bound, but
merging overload items contributes one type variable per item, so the same
TypeVar used in several items arrives as a union that nothing can satisfy.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant