diff --git a/mypy/checkmember.py b/mypy/checkmember.py index e75a8ed7a5b03..80813b21408dd 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -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? @@ -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, diff --git a/test-data/unit/check-selftype.test b/test-data/unit/check-selftype.test index c157200352562..35f993e844ef8 100644 --- a/test-data/unit/check-selftype.test +++ b/test-data/unit/check-selftype.test @@ -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 diff --git a/test-data/unit/fixtures/list-sort-selftype.pyi b/test-data/unit/fixtures/list-sort-selftype.pyi new file mode 100644 index 0000000000000..5644b1c69af43 --- /dev/null +++ b/test-data/unit/fixtures/list-sort-selftype.pyi @@ -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