Skip to content

Commit

Permalink
Check tuples of abstract types (#15366)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
sobolevn authored Jul 12, 2023
1 parent 4d394c1 commit edb41e0
Show file tree
Hide file tree
Showing 2 changed files with 39 additions and 9 deletions.
30 changes: 21 additions & 9 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -2352,15 +2352,7 @@ def check_arg(
if isinstance(caller_type, DeletedType):
self.msg.deleted_as_rvalue(caller_type, context)
# Only non-abstract non-protocol class can be given where Type[...] is expected...
elif (
isinstance(caller_type, CallableType)
and isinstance(callee_type, TypeType)
and caller_type.is_type_obj()
and (caller_type.type_object().is_abstract or caller_type.type_object().is_protocol)
and isinstance(callee_type.item, Instance)
and (callee_type.item.type.is_abstract or callee_type.item.type.is_protocol)
and not self.chk.allow_abstract_call
):
elif self.has_abstract_type_part(caller_type, callee_type):
self.msg.concrete_only_call(callee_type, context)
elif not is_subtype(caller_type, callee_type, options=self.chk.options):
code = self.msg.incompatible_argument(
Expand Down Expand Up @@ -5484,6 +5476,26 @@ def narrow_type_from_binder(
return narrow_declared_type(known_type, restriction)
return known_type

def has_abstract_type_part(self, caller_type: ProperType, callee_type: ProperType) -> bool:
# TODO: support other possible types here
if isinstance(caller_type, TupleType) and isinstance(callee_type, TupleType):
return any(
self.has_abstract_type(get_proper_type(caller), get_proper_type(callee))
for caller, callee in zip(caller_type.items, callee_type.items)
)
return self.has_abstract_type(caller_type, callee_type)

def has_abstract_type(self, caller_type: ProperType, callee_type: ProperType) -> bool:
return (
isinstance(caller_type, CallableType)
and isinstance(callee_type, TypeType)
and caller_type.is_type_obj()
and (caller_type.type_object().is_abstract or caller_type.type_object().is_protocol)
and isinstance(callee_type.item, Instance)
and (callee_type.item.type.is_abstract or callee_type.item.type.is_protocol)
and not self.chk.allow_abstract_call
)


def has_any_type(t: Type, ignore_in_type_obj: bool = False) -> bool:
"""Whether t contains an Any type"""
Expand Down
18 changes: 18 additions & 0 deletions test-data/unit/check-abstract.test
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,24 @@ x: Type[B]
f(x) # OK
[out]

[case testAbstractTypeInADict]
from typing import Dict, Type
from abc import abstractmethod

class Class:
@abstractmethod
def method(self) -> None:
pass

my_dict_init: Dict[int, Type[Class]] = {0: Class} # E: Only concrete class can be given where "Tuple[int, Type[Class]]" is expected

class Child(Class):
def method(self) -> None: ...

other_dict_init: Dict[int, Type[Class]] = {0: Child} # ok
[builtins fixtures/dict.pyi]
[out]

[case testInstantiationAbstractsInTypeForAliases]
from typing import Type
from abc import abstractmethod
Expand Down

0 comments on commit edb41e0

Please sign in to comment.