Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions mypy/stubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand All @@ -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
Expand Down
33 changes: 33 additions & 0 deletions mypy/test/teststubtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -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]:
Expand Down
Loading