Skip to content

Commit edb41e0

Browse files
authored
Check tuples of abstract types (#15366)
The PR is quite simple (and I would like to keep it this way): - Before we were only checking `type[]` type - Now we also check `tuple[type[], ...]` type There might be other types that we want to add in the future here: `TypedDictType`, etc? But, let's do it one by one, because smaller PRs are easier to merge :) Closes #15264
1 parent 4d394c1 commit edb41e0

File tree

2 files changed

+39
-9
lines changed

2 files changed

+39
-9
lines changed

mypy/checkexpr.py

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2352,15 +2352,7 @@ def check_arg(
23522352
if isinstance(caller_type, DeletedType):
23532353
self.msg.deleted_as_rvalue(caller_type, context)
23542354
# Only non-abstract non-protocol class can be given where Type[...] is expected...
2355-
elif (
2356-
isinstance(caller_type, CallableType)
2357-
and isinstance(callee_type, TypeType)
2358-
and caller_type.is_type_obj()
2359-
and (caller_type.type_object().is_abstract or caller_type.type_object().is_protocol)
2360-
and isinstance(callee_type.item, Instance)
2361-
and (callee_type.item.type.is_abstract or callee_type.item.type.is_protocol)
2362-
and not self.chk.allow_abstract_call
2363-
):
2355+
elif self.has_abstract_type_part(caller_type, callee_type):
23642356
self.msg.concrete_only_call(callee_type, context)
23652357
elif not is_subtype(caller_type, callee_type, options=self.chk.options):
23662358
code = self.msg.incompatible_argument(
@@ -5484,6 +5476,26 @@ def narrow_type_from_binder(
54845476
return narrow_declared_type(known_type, restriction)
54855477
return known_type
54865478

5479+
def has_abstract_type_part(self, caller_type: ProperType, callee_type: ProperType) -> bool:
5480+
# TODO: support other possible types here
5481+
if isinstance(caller_type, TupleType) and isinstance(callee_type, TupleType):
5482+
return any(
5483+
self.has_abstract_type(get_proper_type(caller), get_proper_type(callee))
5484+
for caller, callee in zip(caller_type.items, callee_type.items)
5485+
)
5486+
return self.has_abstract_type(caller_type, callee_type)
5487+
5488+
def has_abstract_type(self, caller_type: ProperType, callee_type: ProperType) -> bool:
5489+
return (
5490+
isinstance(caller_type, CallableType)
5491+
and isinstance(callee_type, TypeType)
5492+
and caller_type.is_type_obj()
5493+
and (caller_type.type_object().is_abstract or caller_type.type_object().is_protocol)
5494+
and isinstance(callee_type.item, Instance)
5495+
and (callee_type.item.type.is_abstract or callee_type.item.type.is_protocol)
5496+
and not self.chk.allow_abstract_call
5497+
)
5498+
54875499

54885500
def has_any_type(t: Type, ignore_in_type_obj: bool = False) -> bool:
54895501
"""Whether t contains an Any type"""

test-data/unit/check-abstract.test

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,24 @@ x: Type[B]
198198
f(x) # OK
199199
[out]
200200

201+
[case testAbstractTypeInADict]
202+
from typing import Dict, Type
203+
from abc import abstractmethod
204+
205+
class Class:
206+
@abstractmethod
207+
def method(self) -> None:
208+
pass
209+
210+
my_dict_init: Dict[int, Type[Class]] = {0: Class} # E: Only concrete class can be given where "Tuple[int, Type[Class]]" is expected
211+
212+
class Child(Class):
213+
def method(self) -> None: ...
214+
215+
other_dict_init: Dict[int, Type[Class]] = {0: Child} # ok
216+
[builtins fixtures/dict.pyi]
217+
[out]
218+
201219
[case testInstantiationAbstractsInTypeForAliases]
202220
from typing import Type
203221
from abc import abstractmethod

0 commit comments

Comments
 (0)