Skip to content

Commit

Permalink
Merge pull request #1 from Shoobx/lregebro-investigations
Browse files Browse the repository at this point in the history
Support for type inference by implementedBy/providedBy
  • Loading branch information
kedder committed Jan 26, 2019
2 parents eefae5b + ca7d510 commit 18ea20a
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 6 deletions.
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,17 @@ Type of the `adapter` variable will be set to `IEUPowerSocket`.
`zope.schema` packages. They are enabled automatically as soon as plugin is
enabled.

### Conditional type inference

When using `zope.interface`'s `implementedBy()` and `providedBy()` methods
in an if statement, `mypy` will know which type it is inside those statements.

```python
if IAnimal.providedBy(ob):
ob.number_of_legs += 2

```

## What is not supported?

These `zope.interface` features are not supported:
Expand Down
17 changes: 16 additions & 1 deletion src/mypy_zope/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@

from mypy.nodes import (
Decorator, Var, Argument, FuncDef, CallExpr, RefExpr, Expression,
ClassDef, Statement, Block, IndexExpr,
ClassDef, Statement, Block, IndexExpr, MemberExpr,
MDEF, ARG_POS, ARG_OPT
)

Expand Down Expand Up @@ -104,6 +104,21 @@ def get_method_signature_hook(self, fullname: str
def get_method_hook(self, fullname: str
) -> Optional[Callable[[MethodContext], Type]]:
# print(f"get_method_hook: {fullname}")

methodname = fullname.split('.')[-1]
if methodname in ('providedBy', 'implementedBy'):
def analyze(method_ctx: MethodContext) -> Type:
assert isinstance(method_ctx.context, CallExpr)
assert isinstance(method_ctx.context.callee, MemberExpr)
if method_ctx.context.callee.name == 'providedBy':
method_ctx.context.callee.fullname = 'builtins.isinstance'
else:
method_ctx.context.callee.fullname = 'builtins.issubclass'
method_ctx.context.args = [method_ctx.args[0][0],
method_ctx.context.callee.expr]

return method_ctx.default_return_type
return analyze
return None

def get_attribute_hook(self, fullname: str
Expand Down
8 changes: 3 additions & 5 deletions src/mypy_zope/stubs/zope/interface/interface.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ class Element:
def setTaggedValue(self, tag: Any, value: Any) -> None: ...

class SpecificationBasePy:
def providedBy(self, ob: Any): ...
def implementedBy(self, cls: Any): ...
def isOrExtends(self, interface: Any): ...
def providedBy(self, ob: Any) -> bool: ...
def implementedBy(self, cls: Any) -> bool: ...
def isOrExtends(self, interface: Any) -> bool: ...
__call__: Any = ...
SpecificationBase = SpecificationBasePy

Expand All @@ -36,8 +36,6 @@ InterfaceBase = InterfaceBasePy
adapter_hooks: Any

class Specification(SpecificationBase):
isOrExtends: Any = ...
providedBy: Any = ...
dependents: Any = ...
__bases__: Any = ...
def __init__(self, bases: Any = ...) -> None: ...
Expand Down
43 changes: 43 additions & 0 deletions tests/samples/interface_implications.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
"""zope.interface provides an abstract attribute for classes
It is translated to Any type.
"""
import zope.interface
from typing import Optional

class IBookmark(zope.interface.Interface):
field = zope.interface.Attribute('Arbitrary Attribute')


@zope.interface.implementer(IBookmark)
class Bookmark(object):
pass

def main(obj: Optional[IBookmark]) -> None:

if not IBookmark.providedBy(obj):
reveal_type(obj)
else:
reveal_type(obj)
reveal_type(obj)

cls = obj.__class__
if IBookmark.implementedBy(cls):
reveal_type(cls)
else:
reveal_type(cls)
reveal_type(cls)

if __name__ == '__main__':
main(Bookmark())

"""
<output>
interface_implications.py:19: error: Revealed type is 'None'
interface_implications.py:21: error: Revealed type is '__main__.IBookmark'
interface_implications.py:22: error: Revealed type is 'Union[__main__.IBookmark, None]'
interface_implications.py:26: error: Revealed type is 'Type[__main__.IBookmark]'
interface_implications.py:28: error: Revealed type is 'Union[Type[__main__.IBookmark], Type[None]]'
interface_implications.py:29: error: Revealed type is 'Union[Type[__main__.IBookmark], Type[None]]'
</output>
"""

0 comments on commit 18ea20a

Please sign in to comment.