From 2f12b603cd72c464164d2781f3315d823314e196 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Tue, 5 Sep 2023 14:36:47 -0700 Subject: [PATCH 01/13] Fix inference for overloaded __call__ with generic self Fixes #8283 --- mypy/checkmember.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 60430839ff62..018e984694fd 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -330,13 +330,12 @@ def analyze_instance_member_access( signature = method.type signature = freshen_all_functions_type_vars(signature) if not method.is_static: - if name != "__call__": - # TODO: use proper treatment of special methods on unions instead - # of this hack here and below (i.e. mx.self_type). - dispatched_type = meet.meet_types(mx.original_type, typ) - signature = check_self_arg( - signature, dispatched_type, method.is_class, mx.context, name, mx.msg - ) + # TODO: use proper treatment of special methods on unions instead + # of this hack here and below (i.e. mx.self_type). + dispatched_type = meet.meet_types(mx.original_type, typ) + signature = check_self_arg( + signature, dispatched_type, method.is_class, mx.context, name, mx.msg + ) signature = bind_self(signature, mx.self_type, is_classmethod=method.is_class) # TODO: should we skip these steps for static methods as well? # Since generic static methods should not be allowed. @@ -898,7 +897,7 @@ def f(self: S) -> T: ... selfarg = get_proper_type(item.arg_types[0]) if subtypes.is_subtype(dispatched_arg_type, erase_typevars(erase_to_bound(selfarg))): new_items.append(item) - elif isinstance(selfarg, ParamSpecType): + elif isinstance(selfarg, (ParamSpecType, TupleType)): # TODO: This is not always right. What's the most reasonable thing to do here? new_items.append(item) elif isinstance(selfarg, TypeVarTupleType): From a2abbb6e828c913812899c54e2b36210130fa6de Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 14 Sep 2023 14:29:09 -0700 Subject: [PATCH 02/13] undo lazy hack --- mypy/checkmember.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mypy/checkmember.py b/mypy/checkmember.py index 018e984694fd..d85f45fbcf0d 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -897,7 +897,7 @@ def f(self: S) -> T: ... selfarg = get_proper_type(item.arg_types[0]) if subtypes.is_subtype(dispatched_arg_type, erase_typevars(erase_to_bound(selfarg))): new_items.append(item) - elif isinstance(selfarg, (ParamSpecType, TupleType)): + elif isinstance(selfarg, ParamSpecType): # TODO: This is not always right. What's the most reasonable thing to do here? new_items.append(item) elif isinstance(selfarg, TypeVarTupleType): From d5431e1c899d76c58a6034188ea96701605c4422 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Thu, 14 Sep 2023 23:55:13 -0700 Subject: [PATCH 03/13] regression test --- test-data/unit/check-overloading.test | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/test-data/unit/check-overloading.test b/test-data/unit/check-overloading.test index 4546c7171856..443a6fb5cb10 100644 --- a/test-data/unit/check-overloading.test +++ b/test-data/unit/check-overloading.test @@ -6650,3 +6650,27 @@ def d(x: int) -> int: ... def d(f: int, *, x: int) -> str: ... def d(*args, **kwargs): ... [builtins fixtures/tuple.pyi] + +[case testOverloadCallableGenericSelf] +from typing import Any, TypeVar, Generic, overload, reveal_type + +T = TypeVar("T") + +class MyCallable(Generic[T]): + def __init__(self, t: T): + self.t = t + + @overload + def __call__(self: "MyCallable[int]") -> str: ... + @overload + def __call__(self: "MyCallable[str]") -> int: ... + def __call__(self): ... + +c = MyCallable(5) +reveal_type(c) # N: Revealed type is "__main__.MyCallable[builtins.int]" +reveal_type(c()) # N: Revealed type is "builtins.str" + +c2 = MyCallable("test") +reveal_type(c2) # N: Revealed type is "__main__.MyCallable[builtins.str]" +reveal_type(c2()) # should be int # N: Revealed type is "builtins.int" +[builtins fixtures/tuple.pyi] From c36efdda7434d351cdf0ff9221789a7549cba9ff Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 15 Sep 2023 00:02:43 -0700 Subject: [PATCH 04/13] new, bad test --- test-data/unit/check-tuples.test | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 391fa20db738..efba52f63f0c 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1434,7 +1434,22 @@ def foo(o: CallableTuple) -> int: class CallableTuple(Tuple[str, int]): def __call__(self, n: int, m: int) -> int: return n +[builtins fixtures/tuple.pyi] + +[case testTypeTupleGenericCall] +from typing import Generic, Tuple, TypeVar + +T = TypeVar('T') +def foo(o: CallableTuple[int]) -> int: + reveal_type(o) # N: Revealed type is "Tuple[builtins.str, builtins.int, fallback=__main__.CallableTuple[builtins.int]]" + reveal_type(o.count(3)) # N: Revealed type is "builtins.int" + # TODO: incorrect error below + return o(1, 2) # E: Invalid self argument "CallableTuple[int]" to attribute function "__call__" with type "Callable[[CallableTuple[T], int, int], int]" + +class CallableTuple(Tuple[str, T]): + def __call__(self, n: int, m: int) -> int: + return n [builtins fixtures/tuple.pyi] [case testTupleCompatibleWithSequence] From 89fc492cb122489e5eea79ff3cafca3f3bff3aff Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 15 Sep 2023 00:03:06 -0700 Subject: [PATCH 05/13] fix for non-generic callable tuple subclasses --- mypy/subtypes.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 9ed2e4af4051..b4a40a4f9aa5 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -463,10 +463,10 @@ def visit_instance(self, left: Instance) -> bool: assert unpacked.type.fullname == "builtins.tuple" if isinstance(get_proper_type(unpacked.args[0]), AnyType): return not self.proper_subtype - if mapped.type.fullname == "builtins.tuple" and isinstance( - get_proper_type(mapped.args[0]), AnyType - ): - return not self.proper_subtype + + if mapped.type.fullname == right.partial_fallback.type.fullname: + if all(isinstance(get_proper_type(a), AnyType) for a in mapped.args): + return not self.proper_subtype return False if isinstance(right, TypeVarTupleType): # tuple[Any, ...] is like Any in the world of tuples (see special case above). From 9a776cfe9a5c1050be85c9233e6054ce70be15b1 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 15 Sep 2023 00:25:03 -0700 Subject: [PATCH 06/13] fix --- mypy/subtypes.py | 8 ++++++++ test-data/unit/check-tuples.test | 3 +-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index b4a40a4f9aa5..b9ef369d5f43 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -467,6 +467,14 @@ def visit_instance(self, left: Instance) -> bool: if mapped.type.fullname == right.partial_fallback.type.fullname: if all(isinstance(get_proper_type(a), AnyType) for a in mapped.args): return not self.proper_subtype + if mapped.type.tuple_type: + expanded = expand_type_by_instance(mapped.type.tuple_type, mapped) + assert isinstance(expanded, TupleType) + if self._is_subtype( + expanded, + right.copy_modified(fallback=expanded.partial_fallback) + ): + return not self.proper_subtype return False if isinstance(right, TypeVarTupleType): # tuple[Any, ...] is like Any in the world of tuples (see special case above). diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index efba52f63f0c..ed2c3550a04e 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1444,8 +1444,7 @@ T = TypeVar('T') def foo(o: CallableTuple[int]) -> int: reveal_type(o) # N: Revealed type is "Tuple[builtins.str, builtins.int, fallback=__main__.CallableTuple[builtins.int]]" reveal_type(o.count(3)) # N: Revealed type is "builtins.int" - # TODO: incorrect error below - return o(1, 2) # E: Invalid self argument "CallableTuple[int]" to attribute function "__call__" with type "Callable[[CallableTuple[T], int, int], int]" + return o(1, 2) class CallableTuple(Tuple[str, T]): def __call__(self, n: int, m: int) -> int: From 844826c41dcd3229c22674b1f337dd53271ecf69 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 15 Sep 2023 01:05:07 -0700 Subject: [PATCH 07/13] check fallback args --- mypy/subtypes.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index b9ef369d5f43..c42754f8a41c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -470,11 +470,9 @@ def visit_instance(self, left: Instance) -> bool: if mapped.type.tuple_type: expanded = expand_type_by_instance(mapped.type.tuple_type, mapped) assert isinstance(expanded, TupleType) - if self._is_subtype( - expanded, - right.copy_modified(fallback=expanded.partial_fallback) - ): - return not self.proper_subtype + return self._is_subtype( + expanded, right.copy_modified(fallback=expanded.partial_fallback) + ) and self._is_subtype(left, mypy.typeops.tuple_fallback(right)) return False if isinstance(right, TypeVarTupleType): # tuple[Any, ...] is like Any in the world of tuples (see special case above). From 8004705ff4f450ac3fadcebc620f3c45b788acdc Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 15 Sep 2023 01:07:45 -0700 Subject: [PATCH 08/13] unnecessary if --- mypy/subtypes.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index c42754f8a41c..2b47919f9cf3 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -463,10 +463,8 @@ def visit_instance(self, left: Instance) -> bool: assert unpacked.type.fullname == "builtins.tuple" if isinstance(get_proper_type(unpacked.args[0]), AnyType): return not self.proper_subtype - - if mapped.type.fullname == right.partial_fallback.type.fullname: - if all(isinstance(get_proper_type(a), AnyType) for a in mapped.args): - return not self.proper_subtype + if all(isinstance(get_proper_type(a), AnyType) for a in mapped.args): + return not self.proper_subtype if mapped.type.tuple_type: expanded = expand_type_by_instance(mapped.type.tuple_type, mapped) assert isinstance(expanded, TupleType) From 44999065054885c58ed7dbf5cb3fd2483c4a8c4c Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Fri, 15 Sep 2023 01:21:07 -0700 Subject: [PATCH 09/13] comment --- mypy/subtypes.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 2b47919f9cf3..123e7483611f 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -466,10 +466,11 @@ def visit_instance(self, left: Instance) -> bool: if all(isinstance(get_proper_type(a), AnyType) for a in mapped.args): return not self.proper_subtype if mapped.type.tuple_type: - expanded = expand_type_by_instance(mapped.type.tuple_type, mapped) - assert isinstance(expanded, TupleType) + # similar to logic inside map_instance_to_supertype + tuple_type = expand_type_by_instance(mapped.type.tuple_type, mapped) + assert isinstance(tuple_type, TupleType) return self._is_subtype( - expanded, right.copy_modified(fallback=expanded.partial_fallback) + tuple_type, right.copy_modified(fallback=tuple_type.partial_fallback) ) and self._is_subtype(left, mypy.typeops.tuple_fallback(right)) return False if isinstance(right, TypeVarTupleType): From 54187d197c92d77c224c6df5d6136c3bacc0d712 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Sep 2023 14:46:42 -0700 Subject: [PATCH 10/13] remove my fix for callable generic tuple subclasses --- mypy/subtypes.py | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 123e7483611f..19aac03af8e3 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -465,13 +465,6 @@ def visit_instance(self, left: Instance) -> bool: return not self.proper_subtype if all(isinstance(get_proper_type(a), AnyType) for a in mapped.args): return not self.proper_subtype - if mapped.type.tuple_type: - # similar to logic inside map_instance_to_supertype - tuple_type = expand_type_by_instance(mapped.type.tuple_type, mapped) - assert isinstance(tuple_type, TupleType) - return self._is_subtype( - tuple_type, right.copy_modified(fallback=tuple_type.partial_fallback) - ) and self._is_subtype(left, mypy.typeops.tuple_fallback(right)) return False if isinstance(right, TypeVarTupleType): # tuple[Any, ...] is like Any in the world of tuples (see special case above). From 1d959c3e83257f7c74fa786ae3465ee882084e22 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Sep 2023 14:49:30 -0700 Subject: [PATCH 11/13] ilevkivskyi fix --- mypy/checkexpr.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 22a9852545b7..0ddb4868717f 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1476,6 +1476,7 @@ def check_call( callable_node: Expression | None = None, callable_name: str | None = None, object_type: Type | None = None, + original_type: Type | None = None, ) -> tuple[Type, Type]: """Type check a call. @@ -1538,7 +1539,7 @@ def check_call( is_super=False, is_operator=True, msg=self.msg, - original_type=callee, + original_type=original_type or callee, chk=self.chk, in_literal_context=self.is_literal_context(), ) @@ -1579,6 +1580,7 @@ def check_call( callable_node, callable_name, object_type, + original_type=callee, ) else: return self.msg.not_callable(callee, context), AnyType(TypeOfAny.from_error) From b3aa066901945bd6080e6633de600f7229447408 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Sun, 17 Sep 2023 14:57:12 -0700 Subject: [PATCH 12/13] subtype fixes --- mypy/subtypes.py | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 19aac03af8e3..756e9383225c 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -454,17 +454,22 @@ def visit_instance(self, left: Instance) -> bool: if isinstance(unpacked, Instance): return self._is_subtype(left, unpacked) if left.type.has_base(right.partial_fallback.type.fullname): + if self.proper_subtype: + return False # Special case to consider Foo[*tuple[Any, ...]] (i.e. bare Foo) a # subtype of Foo[], when Foo is user defined variadic tuple type. mapped = map_instance_to_supertype(left, right.partial_fallback.type) - if len(mapped.args) == 1 and isinstance(mapped.args[0], UnpackType): - unpacked = get_proper_type(mapped.args[0].type) - if isinstance(unpacked, Instance): + for arg in map(get_proper_type, mapped.args): + if isinstance(arg, UnpackType): + unpacked = get_proper_type(arg.type) + if not isinstance(unpacked, Instance): + return False assert unpacked.type.fullname == "builtins.tuple" - if isinstance(get_proper_type(unpacked.args[0]), AnyType): - return not self.proper_subtype - if all(isinstance(get_proper_type(a), AnyType) for a in mapped.args): - return not self.proper_subtype + if not isinstance(get_proper_type(unpacked.args[0]), AnyType): + return False + elif not isinstance(arg, AnyType): + return False + return True return False if isinstance(right, TypeVarTupleType): # tuple[Any, ...] is like Any in the world of tuples (see special case above). @@ -532,15 +537,19 @@ def visit_instance(self, left: Instance) -> bool: right_args = ( right_prefix + (TupleType(list(right_middle), fallback),) + right_suffix ) - if len(t.args) == 1 and isinstance(t.args[0], UnpackType): - unpacked = get_proper_type(t.args[0].type) - if isinstance(unpacked, Instance): - assert unpacked.type.fullname == "builtins.tuple" - if ( - isinstance(get_proper_type(unpacked.args[0]), AnyType) - and not self.proper_subtype - ): - return True + if not self.proper_subtype: + for arg in map(get_proper_type, t.args): + if isinstance(arg, UnpackType): + unpacked = get_proper_type(arg.type) + if not isinstance(unpacked, Instance): + break + assert unpacked.type.fullname == "builtins.tuple" + if not isinstance(get_proper_type(unpacked.args[0]), AnyType): + break + elif not isinstance(arg, AnyType): + break + else: + return True type_params = zip(left_args, right_args, right.type.defn.type_vars) else: type_params = zip(t.args, right.args, right.type.defn.type_vars) From ca4c29448b7ca3b60a86ae2e403b99abd4daf987 Mon Sep 17 00:00:00 2001 From: hauntsaninja Date: Mon, 18 Sep 2023 20:53:33 -0700 Subject: [PATCH 13/13] break --- mypy/subtypes.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 756e9383225c..c5399db0a494 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -454,22 +454,22 @@ def visit_instance(self, left: Instance) -> bool: if isinstance(unpacked, Instance): return self._is_subtype(left, unpacked) if left.type.has_base(right.partial_fallback.type.fullname): - if self.proper_subtype: - return False - # Special case to consider Foo[*tuple[Any, ...]] (i.e. bare Foo) a - # subtype of Foo[], when Foo is user defined variadic tuple type. - mapped = map_instance_to_supertype(left, right.partial_fallback.type) - for arg in map(get_proper_type, mapped.args): - if isinstance(arg, UnpackType): - unpacked = get_proper_type(arg.type) - if not isinstance(unpacked, Instance): - return False - assert unpacked.type.fullname == "builtins.tuple" - if not isinstance(get_proper_type(unpacked.args[0]), AnyType): - return False - elif not isinstance(arg, AnyType): - return False - return True + if not self.proper_subtype: + # Special case to consider Foo[*tuple[Any, ...]] (i.e. bare Foo) a + # subtype of Foo[], when Foo is user defined variadic tuple type. + mapped = map_instance_to_supertype(left, right.partial_fallback.type) + for arg in map(get_proper_type, mapped.args): + if isinstance(arg, UnpackType): + unpacked = get_proper_type(arg.type) + if not isinstance(unpacked, Instance): + break + assert unpacked.type.fullname == "builtins.tuple" + if not isinstance(get_proper_type(unpacked.args[0]), AnyType): + break + elif not isinstance(arg, AnyType): + break + else: + return True return False if isinstance(right, TypeVarTupleType): # tuple[Any, ...] is like Any in the world of tuples (see special case above).