diff --git a/mypy/stubtest.py b/mypy/stubtest.py index 4a38939a03907..98d23abac14a9 100644 --- a/mypy/stubtest.py +++ b/mypy/stubtest.py @@ -833,6 +833,24 @@ def names_approx_match(a: str, b: str) -> bool: ) +def _resolve_typevar_upper_bounds(stub_type: mypy.types.Type) -> mypy.types.Type: + """Replace type variables with their upper bounds, including inside a union. + + Merging the items of an overload contributes one type variable per item, so an + argument annotated with the same ``TypeVar`` in several items ends up as a union of + distinct type variables. No runtime default value is a subtype of that. + """ + proper_type = mypy.types.get_proper_type(stub_type) + if isinstance(proper_type, mypy.types.TypeVarType): + return proper_type.upper_bound + if isinstance(proper_type, mypy.types.UnionType): + with mypy.state.state.strict_optional_set(True): + return mypy.typeops.make_simplified_union( + [_resolve_typevar_upper_bounds(item) for item in proper_type.items] + ) + return stub_type + + def _verify_arg_default_value( stub_arg: nodes.Argument, runtime_arg: inspect.Parameter ) -> Iterator[str]: @@ -854,8 +872,8 @@ def _verify_arg_default_value( # UnboundTypes have ugly question marks following them, so default to var type. # Note we do this same fallback when constructing signatures in from_overloadedfuncdef stub_type = stub_arg.variable.type or stub_arg.type_annotation - if isinstance(stub_type, mypy.types.TypeVarType): - stub_type = stub_type.upper_bound + if stub_type is not None: + stub_type = _resolve_typevar_upper_bounds(stub_type) if ( runtime_type is not None and stub_type is not None diff --git a/mypy/test/teststubtest.py b/mypy/test/teststubtest.py index 2db149ce65c97..014476f78898e 100644 --- a/mypy/test/teststubtest.py +++ b/mypy/test/teststubtest.py @@ -939,6 +939,39 @@ def f(a, *args): ... """, error=None, ) + # Merging the overload items contributes one type variable per item, so the + # default value is checked against a union of them rather than a single one. + yield Case( + stub=""" + from typing import TypeVar + + _T1 = TypeVar("_T1") + + @overload + def f_typevar_default(x: int = 0) -> int: ... + @overload + def f_typevar_default(x: int, ret: _T1) -> _T1: ... + @overload + def f_typevar_default(x: int = 0, *, ret: _T1) -> _T1: ... + """, + runtime="def f_typevar_default(x=0, ret=1): return ret", + error=None, + ) + # An upper bound still has to accept the runtime default. + yield Case( + stub=""" + _T2 = TypeVar("_T2", bound=str) + + @overload + def f_typevar_bound_default(x: int = 0) -> int: ... + @overload + def f_typevar_bound_default(x: int, ret: _T2) -> _T2: ... + @overload + def f_typevar_bound_default(x: int = 0, *, ret: _T2) -> _T2: ... + """, + runtime="def f_typevar_bound_default(x=0, ret=1): return ret", + error="f_typevar_bound_default", + ) @collect_cases def test_decorated_overload(self) -> Iterator[Case]: