Skip to content

Commit 9d10a3e

Browse files
TH3CHARLieilevkivskyi
authored andcommitted
Inferencing type reference of abstract class inferred from concrete type values (#8096)
Resolves #8050, following the purposed solution 2 in #8050 (comment) and using the implementation purposed in #8050 (comment)
1 parent a2a5147 commit 9d10a3e

File tree

3 files changed

+58
-0
lines changed

3 files changed

+58
-0
lines changed

mypy/join.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,11 @@ def visit_callable_type(self, t: CallableType) -> ProperType:
187187
if is_equivalent(t, self.s):
188188
return combine_similar_callables(t, self.s)
189189
result = join_similar_callables(t, self.s)
190+
# We set the from_type_type flag to suppress error when a collection of
191+
# concrete class objects gets inferred as their common abstract superclass.
192+
if not ((t.is_type_obj() and t.type_object().is_abstract) or
193+
(self.s.is_type_obj() and self.s.type_object().is_abstract)):
194+
result.from_type_type = True
190195
if any(isinstance(tp, (NoneType, UninhabitedType))
191196
for tp in get_proper_types(result.arg_types)):
192197
# We don't want to return unusable Callable, attempt fallback instead.

mypy/meet.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -533,6 +533,11 @@ def visit_callable_type(self, t: CallableType) -> ProperType:
533533
if is_equivalent(t, self.s):
534534
return combine_similar_callables(t, self.s)
535535
result = meet_similar_callables(t, self.s)
536+
# We set the from_type_type flag to suppress error when a collection of
537+
# concrete class objects gets inferred as their common abstract superclass.
538+
if not ((t.is_type_obj() and t.type_object().is_abstract) or
539+
(self.s.is_type_obj() and self.s.type_object().is_abstract)):
540+
result.from_type_type = True
536541
if isinstance(get_proper_type(result.ret_type), UninhabitedType):
537542
# Return a plain None or <uninhabited> instead of a weird function.
538543
return self.default(self.s)

test-data/unit/check-abstract.test

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,3 +951,51 @@ default = Config({'cannot': 'modify'}) # OK
951951
default[1] = 2 # E: Unsupported target for indexed assignment
952952
[builtins fixtures/dict.pyi]
953953
[typing fixtures/typing-full.pyi]
954+
955+
[case testSubclassOfABCFromDictionary]
956+
from abc import abstractmethod, ABCMeta
957+
958+
class MyAbstractType(metaclass=ABCMeta):
959+
@abstractmethod
960+
def do(self): pass
961+
962+
class MyConcreteA(MyAbstractType):
963+
def do(self):
964+
print('A')
965+
966+
class MyConcreteB(MyAbstractType):
967+
def do(self):
968+
print('B')
969+
970+
class MyAbstractA(MyAbstractType):
971+
@abstractmethod
972+
def do(self): pass
973+
974+
class MyAbstractB(MyAbstractType):
975+
@abstractmethod
976+
def do(self): pass
977+
978+
my_concrete_types = {
979+
'A': MyConcreteA,
980+
'B': MyConcreteB,
981+
}
982+
983+
my_abstract_types = {
984+
'A': MyAbstractA,
985+
'B': MyAbstractB,
986+
}
987+
988+
reveal_type(my_concrete_types) # N: Revealed type is 'builtins.dict[builtins.str*, def () -> __main__.MyAbstractType]'
989+
reveal_type(my_abstract_types) # N: Revealed type is 'builtins.dict[builtins.str*, def () -> __main__.MyAbstractType]'
990+
991+
a = my_concrete_types['A']()
992+
a.do()
993+
b = my_concrete_types['B']()
994+
b.do()
995+
996+
c = my_abstract_types['A']() # E: Cannot instantiate abstract class 'MyAbstractType' with abstract attribute 'do'
997+
c.do()
998+
d = my_abstract_types['B']() # E: Cannot instantiate abstract class 'MyAbstractType' with abstract attribute 'do'
999+
d.do()
1000+
1001+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)