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
44 changes: 44 additions & 0 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -1112,6 +1112,10 @@ def f(self: S) -> T: ...
),
ignore_pos_arg_names=True,
):
if not self_callable and not has_valid_self_type_bounds(
item, selfarg, dispatched_arg_type
):
continue
new_items.append(item)
elif isinstance(selfarg, ParamSpecType):
# TODO: This is not always right. What's the most reasonable thing to do here?
Expand All @@ -1129,6 +1133,46 @@ def f(self: S) -> T: ...
return Overloaded(new_items)


def has_valid_self_type_bounds(
item: CallableType, selfarg: ProperType, dispatched_arg_type: Type
) -> bool:
"""Check that inferred self type arguments satisfy their bounds/values.

This only applies to explicit self type variables, so we don't disturb
PEP 673 Self handling while still rejecting bounded self types like
``list[SupportsRichComparisonT]`` when the receiver can't satisfy the bound.
"""
if not item.variables or not supported_self_type(selfarg):
return True

self_ids = {tv.id for tv in get_all_type_vars(selfarg)}
self_vars = [tv for tv in item.variables if tv.id in self_ids and tv.name != "Self"]
if not self_vars:
return True

from mypy.infer import infer_type_arguments

typeargs = infer_type_arguments(
self_vars, selfarg, dispatched_arg_type, is_supertype=True, erase_types=False
)
return all(
self_type_argument_within_bounds(tvar, typ) for tvar, typ in zip(self_vars, typeargs)
)


def self_type_argument_within_bounds(tvar: TypeVarLikeType, typ: Type | None) -> bool:
if typ is None or isinstance(tvar, (ParamSpecType, TypeVarTupleType)):
return True

assert isinstance(tvar, TypeVarType)
if tvar.name == "Self":
return True
if tvar.values:
return any(is_subtype(typ, value) for value in tvar.values)

return is_subtype(typ, tvar.upper_bound)


def analyze_class_attribute_access(
itype: Instance,
name: str,
Expand Down
10 changes: 10 additions & 0 deletions test-data/unit/check-selftype.test
Original file line number Diff line number Diff line change
Expand Up @@ -749,6 +749,16 @@ def bad(x: str) -> str: ...
reveal_type(ci.from_item(conv)) # N: Revealed type is "builtins.str"
ci.from_item(bad) # E: Argument 1 to "from_item" of "C" has incompatible type "Callable[[str], str]"; expected "Callable[[int], str]"

[case testListSortSelfTypeOverload]
xs: list[int | None] = [2, None]
xs.sort() # E: Missing named argument "key" for "sort" of "list"
[2, None].sort() # E: Missing named argument "key" for "sort" of "list"
xs.sort(key=lambda x: 0)

ys: list[int] = [2, 1]
ys.sort()
[builtins fixtures/list-sort-selftype.pyi]

[case testSelfTypeRestrictedMethodOverloadInit]
from typing import TypeVar
from lib import P, C
Expand Down
48 changes: 48 additions & 0 deletions test-data/unit/fixtures/list-sort-selftype.pyi
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# Builtins stub used in list.sort self-type overload tests.

from typing import TypeVar, Iterable, Iterator, Sequence, overload, Callable

T = TypeVar('T')
SupportsRichComparisonT = TypeVar('SupportsRichComparisonT', bound='SupportsRichComparison')

class object:
def __init__(self) -> None: pass
def __eq__(self, other: object) -> bool: pass

class SupportsRichComparison:
def __lt__(self, other: object) -> bool: pass

class type: pass
class ellipsis: pass

class list(Sequence[T]):
@overload
def __init__(self) -> None: pass
@overload
def __init__(self, x: Iterable[T]) -> None: pass
def __iter__(self) -> Iterator[T]: pass
def __len__(self) -> int: pass
def __contains__(self, item: object) -> bool: pass
def __getitem__(self, x: int) -> T: pass
@overload
def sort(self: list[SupportsRichComparisonT], *, key: None = None, reverse: bool = False) -> None: ...
@overload
def sort(self, *, key: Callable[[T], SupportsRichComparison], reverse: bool = False) -> None: ...

class tuple(Sequence[T]): pass
class function: pass

class int(SupportsRichComparison):
def __bool__(self) -> bool: pass

class float:
def __bool__(self) -> bool: pass

class str:
def __len__(self) -> bool: pass

class bool(int): pass

property = object()

class dict: pass