From c178b6b257f8fa30db4d9bfe9ae95d8f54537a3b Mon Sep 17 00:00:00 2001 From: Andrey Lebedev Date: Fri, 10 Feb 2023 21:33:32 +0200 Subject: [PATCH 1/2] Reproduce #91 --- mypy-zope.ini | 1 + tests/samples/dmr_unreachable.py | 31 +++++++++++++++++++++++++++++++ tests/samples/isinstance_impl.py | 29 +++++++++++++++++++++++++++++ tests/test_samples.py | 1 + 4 files changed, 62 insertions(+) create mode 100644 tests/samples/dmr_unreachable.py create mode 100644 tests/samples/isinstance_impl.py diff --git a/mypy-zope.ini b/mypy-zope.ini index 3a6cd6b..3a0ed67 100644 --- a/mypy-zope.ini +++ b/mypy-zope.ini @@ -1,3 +1,4 @@ [mypy] namespace_packages=True +warn_unreachable=True plugins=mypy_zope:plugin diff --git a/tests/samples/dmr_unreachable.py b/tests/samples/dmr_unreachable.py new file mode 100644 index 0000000..36f2b13 --- /dev/null +++ b/tests/samples/dmr_unreachable.py @@ -0,0 +1,31 @@ +from typing import Optional + +from zope.interface import Interface, implementer + + +class IFoo(Interface): + pass + + +@implementer(IFoo) +class BaseFoo: + pass + + +class ChildFoo(BaseFoo): + pass + + +class IFooFactory(Interface): + def build() -> Optional[IFoo]: + pass + + +def build_and_use_foo(client_factory: IFooFactory) -> None: + client_protocol = client_factory.build() + assert isinstance(client_protocol, ChildFoo) + print("Hello") + + +""" +""" \ No newline at end of file diff --git a/tests/samples/isinstance_impl.py b/tests/samples/isinstance_impl.py new file mode 100644 index 0000000..242744d --- /dev/null +++ b/tests/samples/isinstance_impl.py @@ -0,0 +1,29 @@ +from typing import Optional +from zope.interface import implementer, Interface + + +class IFoo(Interface): + ... + + +@implementer(IFoo) +class MyFoo: + ... + + +def make_foo() -> Optional[IFoo]: + return MyFoo() + + +x = make_foo() +reveal_type(x) +assert isinstance(x, MyFoo) + +# The code below should not be considered unreachable +print("hello") + +""" + +isinstance_impl.py:19: note: Revealed type is "Union[__main__.IFoo, None]" + +""" diff --git a/tests/test_samples.py b/tests/test_samples.py index f853009..6111e7e 100644 --- a/tests/test_samples.py +++ b/tests/test_samples.py @@ -26,6 +26,7 @@ def test_samples(samplefile, mypy_cache_dir): opts.show_traceback = True opts.namespace_packages = True opts.hide_error_codes = True + opts.warn_unreachable = True opts.plugins = ['mypy_zope:plugin'] # Config file is needed to load plugins, it doesn't not exist and is not # supposed to. From 282269e484cdece7aa024639f763927015cfdac5 Mon Sep 17 00:00:00 2001 From: Andrey Lebedev Date: Tue, 14 Mar 2023 00:38:41 +0200 Subject: [PATCH 2/2] Make sure mypy treats implementations as subtype of their interface Fixes #91 --- src/mypy_zope/plugin.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/mypy_zope/plugin.py b/src/mypy_zope/plugin.py index fcf0999..220ce75 100644 --- a/src/mypy_zope/plugin.py +++ b/src/mypy_zope/plugin.py @@ -3,6 +3,7 @@ from typing import Type as PyType from typing import cast +from mypy.typestate import type_state from mypy.types import ( Type, Instance, @@ -722,6 +723,13 @@ def _apply_interface(self, impl: TypeInfo, iface: TypeInfo) -> None: if promote not in impl._promote: impl._promote.append(promote) + # Remember implementation as a subtype of an interface. HACK: we are + # writing to a global variable here, so potentially this might be a + # memory leak. Needs testing with a large codebase. + asmpt = (Instance(impl, []), promote) + type_state.get_assumptions(False).append(asmpt) + type_state.get_assumptions(True).append(asmpt) + def plugin(version: str) -> PyType[Plugin]: return ZopeInterfacePlugin