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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[stubtest] False positive involving overloaded function with default argument for parameter with type variable

1 participant