From da2405e29fa4627ef2c66ea94f68124b9e7c6da1 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 2 Oct 2024 16:21:19 +0100 Subject: [PATCH 01/11] WIP add test case --- test-data/unit/check-inference.test | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 0dbefbc774a3..f0b4cda57d2c 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3851,3 +3851,31 @@ def a4(x: List[str], y: List[Never]) -> None: reveal_type(z2) # N: Revealed type is "builtins.list[builtins.object]" z1[1].append("asdf") # E: "object" has no attribute "append" [builtins fixtures/dict.pyi] + +[case testTypeVarValueConstraintAgainstGenericProtocol] +from typing import TypeVar, Generic, Protocol, overload + +_T_contra = TypeVar("_T_contra", contravariant=True) + +AnyStr = TypeVar("AnyStr", str, bytes) + +class SupportsWrite(Protocol[_T_contra]): + def write(self, s: _T_contra, /) -> object: ... + +class memoryview: ... + +class Buffer(Protocol): + def __buffer__(self, flags: int, /) -> memoryview: ... + +class IO(Generic[AnyStr]): + @overload + def write(self: IO[bytes], s: Buffer, /) -> int: ... + @overload + def write(self, s: AnyStr, /) -> int: ... + def write(*args, **kwargs): ... + +def foo(fdst: SupportsWrite[AnyStr], length: int = 0) -> None: ... + +f: IO[str] +foo(f) +[builtins fixtures/tuple.pyi] From fcb9192eb532275686386fdf487c2b4566d39a18 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Wed, 2 Oct 2024 16:24:16 +0100 Subject: [PATCH 02/11] Simplify test --- test-data/unit/check-inference.test | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index f0b4cda57d2c..18398e2a47d9 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3855,27 +3855,22 @@ def a4(x: List[str], y: List[Never]) -> None: [case testTypeVarValueConstraintAgainstGenericProtocol] from typing import TypeVar, Generic, Protocol, overload -_T_contra = TypeVar("_T_contra", contravariant=True) - +T_contra = TypeVar("T_contra", contravariant=True) AnyStr = TypeVar("AnyStr", str, bytes) -class SupportsWrite(Protocol[_T_contra]): - def write(self, s: _T_contra, /) -> object: ... - -class memoryview: ... +class SupportsWrite(Protocol[T_contra]): + def write(self, s: T_contra, /) -> None: ... -class Buffer(Protocol): - def __buffer__(self, flags: int, /) -> memoryview: ... +class Buffer: ... class IO(Generic[AnyStr]): @overload - def write(self: IO[bytes], s: Buffer, /) -> int: ... + def write(self: IO[bytes], s: Buffer, /) -> None: ... @overload - def write(self, s: AnyStr, /) -> int: ... - def write(*args, **kwargs): ... + def write(self, s: AnyStr, /) -> None: ... + def write(self, s): ... -def foo(fdst: SupportsWrite[AnyStr], length: int = 0) -> None: ... +def foo(fdst: SupportsWrite[AnyStr]) -> None: ... -f: IO[str] -foo(f) -[builtins fixtures/tuple.pyi] +x: IO[str] +foo(x) From 595b8a510a459f9f2c24f6c14743f56e286d7c59 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 3 Oct 2024 12:41:08 +0100 Subject: [PATCH 03/11] WIP attempted fix --- mypy/typeops.py | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 7f530d13d4e2..3678544e948b 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -305,9 +305,16 @@ class B(A): pass """ if isinstance(method, Overloaded): - items = [ - bind_self(c, original_type, is_classmethod, ignore_instances) for c in method.items - ] + from mypy.meet import is_overlapping_types + items = [] + for c in method.items: + keep = True + if (original_type is not None and c.arg_types and isinstance(c.arg_types[0], Instance) + and c.arg_types[0].args): + + keep = keep and is_overlapping_types(original_type, c.arg_types[0]) + if keep: + items.append(bind_self(c, original_type, is_classmethod, ignore_instances)) return cast(F, Overloaded(items)) assert isinstance(method, CallableType) func = method From cd3bb19d150d5182f89143688bbe6e5918ea3a8d Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 3 Oct 2024 13:12:30 +0100 Subject: [PATCH 04/11] Improvements --- mypy/typeops.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 3678544e948b..1cedd7d29b05 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -309,10 +309,13 @@ class B(A): pass items = [] for c in method.items: keep = True - if (original_type is not None and c.arg_types and isinstance(c.arg_types[0], Instance) - and c.arg_types[0].args): - - keep = keep and is_overlapping_types(original_type, c.arg_types[0]) + if (isinstance(original_type, Instance) and c.arg_types and isinstance((arg_type := c.arg_types[0]), Instance) + and arg_type.args and original_type.type.fullname != "functools._SingleDispatchCallable"): + if original_type.type is not arg_type.type: + keep = True + else: + ov = is_overlapping_types(original_type, c.arg_types[0]) + keep = keep and ov if keep: items.append(bind_self(c, original_type, is_classmethod, ignore_instances)) return cast(F, Overloaded(items)) From cb7b4b64a971578b5169d113427e28a79e677a6a Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 3 Oct 2024 13:17:52 +0100 Subject: [PATCH 05/11] Update --- mypy/typeops.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 1cedd7d29b05..5a4ea9c6b57e 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -14,6 +14,7 @@ from mypy.expandtype import expand_type, expand_type_by_instance from mypy.maptype import map_instance_to_supertype from mypy.nodes import ( + ARG_OPT, ARG_POS, ARG_STAR, ARG_STAR2, @@ -306,11 +307,19 @@ class B(A): pass """ if isinstance(method, Overloaded): from mypy.meet import is_overlapping_types + items = [] + original_type = get_proper_type(original_type) for c in method.items: keep = True - if (isinstance(original_type, Instance) and c.arg_types and isinstance((arg_type := c.arg_types[0]), Instance) - and arg_type.args and original_type.type.fullname != "functools._SingleDispatchCallable"): + if ( + isinstance(original_type, Instance) + and c.arg_types + and isinstance((arg_type := get_proper_type(c.arg_types[0])), Instance) + and c.arg_kinds[0] in (ARG_POS, ARG_OPT) + and arg_type.args + and original_type.type.fullname != "functools._SingleDispatchCallable" + ): if original_type.type is not arg_type.type: keep = True else: From f950a3926589b6cadcedc23bfb5e57fe9b1d411e Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 3 Oct 2024 13:33:01 +0100 Subject: [PATCH 06/11] Refactor and make tweaks --- mypy/typeops.py | 50 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 34 insertions(+), 16 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 5a4ea9c6b57e..8b4e5772bac8 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -306,27 +306,23 @@ class B(A): pass """ if isinstance(method, Overloaded): - from mypy.meet import is_overlapping_types - items = [] original_type = get_proper_type(original_type) for c in method.items: - keep = True - if ( - isinstance(original_type, Instance) - and c.arg_types - and isinstance((arg_type := get_proper_type(c.arg_types[0])), Instance) - and c.arg_kinds[0] in (ARG_POS, ARG_OPT) - and arg_type.args - and original_type.type.fullname != "functools._SingleDispatchCallable" - ): - if original_type.type is not arg_type.type: - keep = True - else: - ov = is_overlapping_types(original_type, c.arg_types[0]) - keep = keep and ov + if isinstance(original_type, Instance): + # Filter based on whether declared self type can match actual object type. + # For example, if self has type C[int] and method is accessed on a C[str] value, + # omit this item. This is best effort since bind_self can be called in many + # contexts, and doing complete validation might trigger infinite recursion. + keep = is_valid_self_type_best_effort(c, original_type) + else: + keep = True if keep: items.append(bind_self(c, original_type, is_classmethod, ignore_instances)) + if len(items) == 0: + # We must return a valid overloaded type, so pick the first item if none + # are matching (arbitrarily). + items.append(bind_self(method.items[0], original_type, is_classmethod, ignore_instances)) return cast(F, Overloaded(items)) assert isinstance(method, CallableType) func = method @@ -398,6 +394,28 @@ class B(A): pass return cast(F, res) +def is_valid_self_type_best_effort(c: CallableType, self_type: Instance) -> bool: + """Quickly check if self_type might match the self in a callable. + + Avoid performing any complex type operations. + """ + if ( + c.arg_types + and isinstance((arg_type := get_proper_type(c.arg_types[0])), Instance) + and c.arg_kinds[0] in (ARG_POS, ARG_OPT) + and arg_type.args + and self_type.type.fullname != "functools._SingleDispatchCallable" + ): + if self_type.type is not arg_type.type: + # We can't map to supertype, since it could trigger expensive checks for + # protocol types, so we consevatively assume this is fine. + return True + else: + from mypy.meet import is_overlapping_types + return is_overlapping_types(self_type, c.arg_types[0]) + return True + + def erase_to_bound(t: Type) -> Type: # TODO: use value restrictions to produce a union? t = get_proper_type(t) From 310730b66bfb86a72a70ccc28241279d2fab4061 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 3 Oct 2024 14:07:10 +0100 Subject: [PATCH 07/11] Optimize --- mypy/typeops.py | 32 ++++++++++++++++++++++++++------ 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 8b4e5772bac8..9eacc5c7fd31 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -314,6 +314,9 @@ class B(A): pass # For example, if self has type C[int] and method is accessed on a C[str] value, # omit this item. This is best effort since bind_self can be called in many # contexts, and doing complete validation might trigger infinite recursion. + # + # Note that overload item filtering normally happens elsewhere. This is needed + # at least during constraint inference. keep = is_valid_self_type_best_effort(c, original_type) else: keep = True @@ -322,7 +325,9 @@ class B(A): pass if len(items) == 0: # We must return a valid overloaded type, so pick the first item if none # are matching (arbitrarily). - items.append(bind_self(method.items[0], original_type, is_classmethod, ignore_instances)) + items.append( + bind_self(method.items[0], original_type, is_classmethod, ignore_instances) + ) return cast(F, Overloaded(items)) assert isinstance(method, CallableType) func = method @@ -397,10 +402,13 @@ class B(A): pass def is_valid_self_type_best_effort(c: CallableType, self_type: Instance) -> bool: """Quickly check if self_type might match the self in a callable. - Avoid performing any complex type operations. + Avoid performing any complex type operations. This is performance-critical. + + Default to returning True if we don't know (or it would be too expensive). """ if ( - c.arg_types + self_type.args + and c.arg_types and isinstance((arg_type := get_proper_type(c.arg_types[0])), Instance) and c.arg_kinds[0] in (ARG_POS, ARG_OPT) and arg_type.args @@ -410,9 +418,21 @@ def is_valid_self_type_best_effort(c: CallableType, self_type: Instance) -> bool # We can't map to supertype, since it could trigger expensive checks for # protocol types, so we consevatively assume this is fine. return True - else: - from mypy.meet import is_overlapping_types - return is_overlapping_types(self_type, c.arg_types[0]) + + # Fast path: no explicit annotation on self + if all( + ( + type(arg) is TypeVarType + and type(arg.upper_bound) is Instance + and arg.upper_bound.type.fullname == "builtins.object" + ) + for arg in arg_type.args + ): + return True + + from mypy.meet import is_overlapping_types + + return is_overlapping_types(self_type, c.arg_types[0]) return True From 4de9b67e90a023125010466e34eebbe14f24900a Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 3 Oct 2024 14:12:27 +0100 Subject: [PATCH 08/11] Add test case --- test-data/unit/check-inference.test | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 18398e2a47d9..41b292d0b8a8 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3874,3 +3874,29 @@ def foo(fdst: SupportsWrite[AnyStr]) -> None: ... x: IO[str] foo(x) + +[case testTypeVarValueConstraintAgainstGenericProtocol2] +from typing import Generic, Protocol, TypeVar, overload + +AnyStr = TypeVar("AnyStr", str, bytes) +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + +class SupportsRead(Generic[T_co]): + def read(self) -> T_co: ... + +class SupportsWrite(Protocol[T_contra]): + def write(self, s: T_contra) -> object: ... + +def copyfileobj(fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr]) -> None: ... + +class WriteToMe(Generic[AnyStr]): + @overload + def write(self: WriteToMe[str], s: str) -> int: ... + @overload + def write(self: WriteToMe[bytes], s: bytes) -> int: ... + def write(self, s): ... + +class WriteToMeOrReadFromMe(WriteToMe[AnyStr], SupportsRead[AnyStr]): ... + +copyfileobj(WriteToMeOrReadFromMe[bytes](), WriteToMe[bytes]()) From 86c11240ce7c9f1d6fb0150cdd0062bb28d59476 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 3 Oct 2024 14:29:08 +0100 Subject: [PATCH 09/11] Add test case from #14975 by @tyralla --- test-data/unit/check-inference.test | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 41b292d0b8a8..3d2cd1bd6ac6 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3900,3 +3900,42 @@ class WriteToMe(Generic[AnyStr]): class WriteToMeOrReadFromMe(WriteToMe[AnyStr], SupportsRead[AnyStr]): ... copyfileobj(WriteToMeOrReadFromMe[bytes](), WriteToMe[bytes]()) + +[case testOverloadedMethodWithExplictSelfTypes] +from typing import Generic, overload, Protocol, TypeVar, Union + +AnyStr = TypeVar("AnyStr", str, bytes) +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + +class SupportsRead(Protocol[T_co]): + def read(self) -> T_co: ... + +class SupportsWrite(Protocol[T_contra]): + def write(self, s: T_contra) -> int: ... + +class Input(Generic[AnyStr]): + def read(self) -> AnyStr: ... + +class Output(Generic[AnyStr]): + @overload + def write(self: Output[str], s: str) -> int: ... + @overload + def write(self: Output[bytes], s: bytes) -> int: ... + def write(self, s: Union[str, bytes]) -> int: ... + +def f(src: SupportsRead[AnyStr], dst: SupportsWrite[AnyStr]) -> None: ... + +def g1(a: Input[bytes], b: Output[bytes]) -> None: + f(a, b) + +def g2(a: Input[bytes], b: Output[bytes]) -> None: + f(a, b) + +def g3(a: Input[str], b: Output[bytes]) -> None: + f(a, b) # E: Cannot infer type argument 1 of "f" + +def g4(a: Input[bytes], b: Output[str]) -> None: + f(a, b) # E: Cannot infer type argument 1 of "f" + +[builtins fixtures/tuple.pyi] From 71a94224b38c15a22f7f122ba72911c15e89fdc6 Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 3 Oct 2024 14:30:20 +0100 Subject: [PATCH 10/11] Move tests to check-protocols.test --- test-data/unit/check-inference.test | 88 ----------------------------- test-data/unit/check-protocols.test | 88 +++++++++++++++++++++++++++++ 2 files changed, 88 insertions(+), 88 deletions(-) diff --git a/test-data/unit/check-inference.test b/test-data/unit/check-inference.test index 3d2cd1bd6ac6..0dbefbc774a3 100644 --- a/test-data/unit/check-inference.test +++ b/test-data/unit/check-inference.test @@ -3851,91 +3851,3 @@ def a4(x: List[str], y: List[Never]) -> None: reveal_type(z2) # N: Revealed type is "builtins.list[builtins.object]" z1[1].append("asdf") # E: "object" has no attribute "append" [builtins fixtures/dict.pyi] - -[case testTypeVarValueConstraintAgainstGenericProtocol] -from typing import TypeVar, Generic, Protocol, overload - -T_contra = TypeVar("T_contra", contravariant=True) -AnyStr = TypeVar("AnyStr", str, bytes) - -class SupportsWrite(Protocol[T_contra]): - def write(self, s: T_contra, /) -> None: ... - -class Buffer: ... - -class IO(Generic[AnyStr]): - @overload - def write(self: IO[bytes], s: Buffer, /) -> None: ... - @overload - def write(self, s: AnyStr, /) -> None: ... - def write(self, s): ... - -def foo(fdst: SupportsWrite[AnyStr]) -> None: ... - -x: IO[str] -foo(x) - -[case testTypeVarValueConstraintAgainstGenericProtocol2] -from typing import Generic, Protocol, TypeVar, overload - -AnyStr = TypeVar("AnyStr", str, bytes) -T_co = TypeVar("T_co", covariant=True) -T_contra = TypeVar("T_contra", contravariant=True) - -class SupportsRead(Generic[T_co]): - def read(self) -> T_co: ... - -class SupportsWrite(Protocol[T_contra]): - def write(self, s: T_contra) -> object: ... - -def copyfileobj(fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr]) -> None: ... - -class WriteToMe(Generic[AnyStr]): - @overload - def write(self: WriteToMe[str], s: str) -> int: ... - @overload - def write(self: WriteToMe[bytes], s: bytes) -> int: ... - def write(self, s): ... - -class WriteToMeOrReadFromMe(WriteToMe[AnyStr], SupportsRead[AnyStr]): ... - -copyfileobj(WriteToMeOrReadFromMe[bytes](), WriteToMe[bytes]()) - -[case testOverloadedMethodWithExplictSelfTypes] -from typing import Generic, overload, Protocol, TypeVar, Union - -AnyStr = TypeVar("AnyStr", str, bytes) -T_co = TypeVar("T_co", covariant=True) -T_contra = TypeVar("T_contra", contravariant=True) - -class SupportsRead(Protocol[T_co]): - def read(self) -> T_co: ... - -class SupportsWrite(Protocol[T_contra]): - def write(self, s: T_contra) -> int: ... - -class Input(Generic[AnyStr]): - def read(self) -> AnyStr: ... - -class Output(Generic[AnyStr]): - @overload - def write(self: Output[str], s: str) -> int: ... - @overload - def write(self: Output[bytes], s: bytes) -> int: ... - def write(self, s: Union[str, bytes]) -> int: ... - -def f(src: SupportsRead[AnyStr], dst: SupportsWrite[AnyStr]) -> None: ... - -def g1(a: Input[bytes], b: Output[bytes]) -> None: - f(a, b) - -def g2(a: Input[bytes], b: Output[bytes]) -> None: - f(a, b) - -def g3(a: Input[str], b: Output[bytes]) -> None: - f(a, b) # E: Cannot infer type argument 1 of "f" - -def g4(a: Input[bytes], b: Output[str]) -> None: - f(a, b) # E: Cannot infer type argument 1 of "f" - -[builtins fixtures/tuple.pyi] diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index ee7556461fd3..5ed2351e33e6 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -4127,3 +4127,91 @@ class P(Protocol): class C(P): ... C(0) # OK + +[case testTypeVarValueConstraintAgainstGenericProtocol] +from typing import TypeVar, Generic, Protocol, overload + +T_contra = TypeVar("T_contra", contravariant=True) +AnyStr = TypeVar("AnyStr", str, bytes) + +class SupportsWrite(Protocol[T_contra]): + def write(self, s: T_contra, /) -> None: ... + +class Buffer: ... + +class IO(Generic[AnyStr]): + @overload + def write(self: IO[bytes], s: Buffer, /) -> None: ... + @overload + def write(self, s: AnyStr, /) -> None: ... + def write(self, s): ... + +def foo(fdst: SupportsWrite[AnyStr]) -> None: ... + +x: IO[str] +foo(x) + +[case testTypeVarValueConstraintAgainstGenericProtocol2] +from typing import Generic, Protocol, TypeVar, overload + +AnyStr = TypeVar("AnyStr", str, bytes) +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + +class SupportsRead(Generic[T_co]): + def read(self) -> T_co: ... + +class SupportsWrite(Protocol[T_contra]): + def write(self, s: T_contra) -> object: ... + +def copyfileobj(fsrc: SupportsRead[AnyStr], fdst: SupportsWrite[AnyStr]) -> None: ... + +class WriteToMe(Generic[AnyStr]): + @overload + def write(self: WriteToMe[str], s: str) -> int: ... + @overload + def write(self: WriteToMe[bytes], s: bytes) -> int: ... + def write(self, s): ... + +class WriteToMeOrReadFromMe(WriteToMe[AnyStr], SupportsRead[AnyStr]): ... + +copyfileobj(WriteToMeOrReadFromMe[bytes](), WriteToMe[bytes]()) + +[case testOverloadedMethodWithExplictSelfTypes] +from typing import Generic, overload, Protocol, TypeVar, Union + +AnyStr = TypeVar("AnyStr", str, bytes) +T_co = TypeVar("T_co", covariant=True) +T_contra = TypeVar("T_contra", contravariant=True) + +class SupportsRead(Protocol[T_co]): + def read(self) -> T_co: ... + +class SupportsWrite(Protocol[T_contra]): + def write(self, s: T_contra) -> int: ... + +class Input(Generic[AnyStr]): + def read(self) -> AnyStr: ... + +class Output(Generic[AnyStr]): + @overload + def write(self: Output[str], s: str) -> int: ... + @overload + def write(self: Output[bytes], s: bytes) -> int: ... + def write(self, s: Union[str, bytes]) -> int: ... + +def f(src: SupportsRead[AnyStr], dst: SupportsWrite[AnyStr]) -> None: ... + +def g1(a: Input[bytes], b: Output[bytes]) -> None: + f(a, b) + +def g2(a: Input[bytes], b: Output[bytes]) -> None: + f(a, b) + +def g3(a: Input[str], b: Output[bytes]) -> None: + f(a, b) # E: Cannot infer type argument 1 of "f" + +def g4(a: Input[bytes], b: Output[str]) -> None: + f(a, b) # E: Cannot infer type argument 1 of "f" + +[builtins fixtures/tuple.pyi] From 87ac981d0c8a8c293978a2c7a3204a1fee4c4eca Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 3 Oct 2024 15:27:29 +0100 Subject: [PATCH 11/11] Preserve old behavior in some error cases --- mypy/typeops.py | 9 ++++----- test-data/unit/check-overloading.test | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/mypy/typeops.py b/mypy/typeops.py index 9eacc5c7fd31..0699cda53cfa 100644 --- a/mypy/typeops.py +++ b/mypy/typeops.py @@ -323,11 +323,10 @@ class B(A): pass if keep: items.append(bind_self(c, original_type, is_classmethod, ignore_instances)) if len(items) == 0: - # We must return a valid overloaded type, so pick the first item if none - # are matching (arbitrarily). - items.append( - bind_self(method.items[0], original_type, is_classmethod, ignore_instances) - ) + # If no item matches, returning all items helps avoid some spurious errors + items = [ + bind_self(c, original_type, is_classmethod, ignore_instances) for c in method.items + ] return cast(F, Overloaded(items)) assert isinstance(method, CallableType) func = method diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 48d5996b226f..e414c1c9b0b6 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -6750,3 +6750,21 @@ def foo(x: object) -> str: ... def bar(x: int) -> int: ... @overload def bar(x: Any) -> str: ... + +[case testOverloadOnInvalidTypeArgument] +from typing import TypeVar, Self, Generic, overload + +class C: pass + +T = TypeVar("T", bound=C) + +class D(Generic[T]): + @overload + def f(self, x: int) -> int: ... + @overload + def f(self, x: str) -> str: ... + def f(Self, x): ... + +a: D[str] # E: Type argument "str" of "D" must be a subtype of "C" +reveal_type(a.f(1)) # N: Revealed type is "builtins.int" +reveal_type(a.f("x")) # N: Revealed type is "builtins.str"