diff --git a/mypy/semanal.py b/mypy/semanal.py index 7f8c5754ff2f..350f3717d3a1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -817,8 +817,8 @@ def analyze_class_body(self, defn: ClassDef) -> Iterator[bool]: else: self.setup_class_def_analysis(defn) self.analyze_base_classes(defn) - self.analyze_metaclass(defn) defn.info.is_protocol = is_protocol + self.analyze_metaclass(defn) defn.info.runtime_protocol = False for decorator in defn.decorators: self.analyze_class_decorator(defn, decorator) @@ -1339,6 +1339,14 @@ def analyze_metaclass(self, defn: ClassDef) -> None: assert isinstance(inst, Instance) defn.info.declared_metaclass = inst defn.info.metaclass_type = defn.info.calculate_metaclass_type() + if any(info.is_protocol for info in defn.info.mro): + if (not defn.info.metaclass_type or + defn.info.metaclass_type.type.fullname() == 'builtins.type'): + # All protocols and their subclasses have ABCMeta metaclass by default. + # TODO: add a metaclass conflict check if there is another metaclass. + abc_meta = self.named_type_or_none('abc.ABCMeta', []) + if abc_meta is not None: # May be None in tests with incomplete lib-stub. + defn.info.metaclass_type = abc_meta if defn.info.metaclass_type is None: # Inconsistency may happen due to multiple baseclasses even in classes that # do not declare explicit metaclass, but it's harder to catch at this stage diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 935824afbc5b..769d2566fa96 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2395,3 +2395,13 @@ reveal_type([a, b]) # E: Revealed type is 'builtins.list[def (x: def (__main__. reveal_type([b, a]) # E: Revealed type is 'builtins.list[def (x: def (__main__.B) -> __main__.B)]' [builtins fixtures/list.pyi] [out] + +[case testProtocolsAlwaysABCs] +from typing import Protocol + +class P(Protocol): ... +class C(P): ... + +reveal_type(C.register(int)) # E: Revealed type is 'def () -> builtins.int' +[typing fixtures/typing-full.pyi] +[out] diff --git a/test-data/unit/check-serialize.test b/test-data/unit/check-serialize.test index 028dc6847d09..70dc873083e9 100644 --- a/test-data/unit/check-serialize.test +++ b/test-data/unit/check-serialize.test @@ -378,6 +378,7 @@ class A(metaclass=ABCMeta): def f(self) -> None: pass @abstractproperty def x(self) -> int: return 0 +[typing fixtures/typing-full.pyi] [out2] tmp/a.py:2: error: Cannot instantiate abstract class 'A' with abstract attributes 'f' and 'x' tmp/a.py:9: error: Property "x" defined in "A" is read-only diff --git a/test-data/unit/lib-stub/abc.pyi b/test-data/unit/lib-stub/abc.pyi index 9208f42e9635..0b1a51c78f5c 100644 --- a/test-data/unit/lib-stub/abc.pyi +++ b/test-data/unit/lib-stub/abc.pyi @@ -1,3 +1,8 @@ -class ABCMeta(type): pass +from typing import Type, Any, TypeVar + +T = TypeVar('T', bound=Type[Any]) + +class ABCMeta(type): + def register(cls, tp: T) -> T: pass abstractmethod = object() abstractproperty = object() diff --git a/test-data/unit/lib-stub/typing.pyi b/test-data/unit/lib-stub/typing.pyi index 0aa899e5095b..157f3a2ca798 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -8,7 +8,7 @@ Union = 0 Optional = 0 TypeVar = 0 Generic = 0 -Protocol = 0 # This is not yet defined in typeshed, see PR typeshed/#1220 +Protocol = 0 Tuple = 0 Callable = 0 NamedTuple = 0