From cc10e60a3eced4fd6ec6feb4b284e1835ea489cc Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 9 Dec 2018 19:32:35 +0000 Subject: [PATCH 1/3] All protocols and their explicit subclasses are ABCs --- mypy/semanal.py | 9 ++++++++- test-data/unit/check-protocols.test | 9 +++++++++ test-data/unit/lib-stub/abc.pyi | 7 ++++++- test-data/unit/lib-stub/typing.pyi | 1 + 4 files changed, 24 insertions(+), 2 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 7f8c5754ff2f..40351ec4b586 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,13 @@ 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. + abc_meta = self.named_type_or_none('abc.ABCMeta', []) + assert abc_meta is not None, "abc.ABCMeta not found" + 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..df9cdce6549e 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2395,3 +2395,12 @@ 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' +[out] 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..67aa82d47b9c 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -1,5 +1,6 @@ # Stub for typing module. Many of the definitions have special handling in # the type checker, so they can just be initialized to anything. +import abc # like in real typeshed cast = 0 overload = 0 From 4bd05eac39158112c4876c9ec72d3a0907ce5734 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 9 Dec 2018 21:08:08 +0000 Subject: [PATCH 2/3] Fix tests --- mypy/semanal.py | 4 ++-- test-data/unit/check-protocols.test | 1 + test-data/unit/check-serialize.test | 1 + test-data/unit/lib-stub/typing.pyi | 3 +-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 40351ec4b586..f1284a27a31f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1344,8 +1344,8 @@ def analyze_metaclass(self, defn: ClassDef) -> None: defn.info.metaclass_type.type.fullname() == 'builtins.type'): # All protocols and their subclasses have ABCMeta metaclass by default. abc_meta = self.named_type_or_none('abc.ABCMeta', []) - assert abc_meta is not None, "abc.ABCMeta not found" - defn.info.metaclass_type = abc_meta + 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 df9cdce6549e..769d2566fa96 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -2403,4 +2403,5 @@ 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/typing.pyi b/test-data/unit/lib-stub/typing.pyi index 67aa82d47b9c..157f3a2ca798 100644 --- a/test-data/unit/lib-stub/typing.pyi +++ b/test-data/unit/lib-stub/typing.pyi @@ -1,6 +1,5 @@ # Stub for typing module. Many of the definitions have special handling in # the type checker, so they can just be initialized to anything. -import abc # like in real typeshed cast = 0 overload = 0 @@ -9,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 From 030fbc7bc35807cf5036be69b56dd4311d6b39b9 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Mon, 10 Dec 2018 14:22:12 +0000 Subject: [PATCH 3/3] Add a TODO item --- mypy/semanal.py | 1 + 1 file changed, 1 insertion(+) diff --git a/mypy/semanal.py b/mypy/semanal.py index f1284a27a31f..350f3717d3a1 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -1343,6 +1343,7 @@ def analyze_metaclass(self, defn: ClassDef) -> None: 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