Skip to content

Commit 16af91f

Browse files
authored
Support compiling the plugin infrastructure with mypyc (#5582)
1 parent e4d91d9 commit 16af91f

File tree

4 files changed

+146
-7
lines changed

4 files changed

+146
-7
lines changed

mypy/build.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
from mypy.stats import dump_type_stats
5555
from mypy.types import Type
5656
from mypy.version import __version__
57-
from mypy.plugin import Plugin, DefaultPlugin, ChainedPlugin
57+
from mypy.plugin import Plugin, DefaultPlugin, ChainedPlugin, plugin_types
5858
from mypy.defaults import PYTHON3_VERSION_MIN
5959
from mypy.server.deps import get_dependencies
6060
from mypy.fscache import FileSystemCache
@@ -569,7 +569,7 @@ def plugin_error(message: str) -> None:
569569
plugin_error(
570570
'Type object expected as the return value of "plugin"; got {!r} (in {})'.format(
571571
plugin_type, plugin_path))
572-
if not issubclass(plugin_type, Plugin):
572+
if not issubclass(plugin_type, plugin_types):
573573
plugin_error(
574574
'Return value of "plugin" must be a subclass of "mypy.plugin.Plugin" '
575575
'(in {})'.format(plugin_path))

mypy/interpreted_plugin.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""Hack for handling non-mypyc compiled plugins with a mypyc-compiled mypy"""
2+
3+
from typing import Optional, Callable, Any
4+
from mypy.options import Options
5+
from mypy.types import Type, CallableType
6+
7+
8+
MYPY = False
9+
if MYPY:
10+
import mypy.plugin
11+
12+
13+
class InterpretedPlugin:
14+
"""Base class of type checker plugins as exposed to external code.
15+
16+
This is a hack around mypyc not currently supporting interpreted subclasses
17+
of compiled classes.
18+
mypy.plugin will arrange for interpreted code to be find this class when it looks
19+
for Plugin, and this class has a __new__ method that returns a WrapperPlugin object
20+
that proxies to this interpreted version.
21+
"""
22+
23+
def __new__(cls, *args: Any, **kwargs: Any) -> 'mypy.plugin.Plugin':
24+
from mypy.plugin import WrapperPlugin
25+
plugin = object.__new__(cls) # type: ignore
26+
plugin.__init__(*args, **kwargs)
27+
return WrapperPlugin(plugin)
28+
29+
def __init__(self, options: Options) -> None:
30+
self.options = options
31+
self.python_version = options.python_version
32+
33+
def get_type_analyze_hook(self, fullname: str
34+
) -> Optional[Callable[['mypy.plugin.AnalyzeTypeContext'], Type]]:
35+
return None
36+
37+
def get_function_hook(self, fullname: str
38+
) -> Optional[Callable[['mypy.plugin.FunctionContext'], Type]]:
39+
return None
40+
41+
def get_method_signature_hook(self, fullname: str
42+
) -> Optional[Callable[['mypy.plugin.MethodSigContext'],
43+
CallableType]]:
44+
return None
45+
46+
def get_method_hook(self, fullname: str
47+
) -> Optional[Callable[['mypy.plugin.MethodContext'], Type]]:
48+
return None
49+
50+
def get_attribute_hook(self, fullname: str
51+
) -> Optional[Callable[['mypy.plugin.AttributeContext'], Type]]:
52+
return None
53+
54+
def get_class_decorator_hook(self, fullname: str
55+
) -> Optional[Callable[['mypy.plugin.ClassDefContext'], None]]:
56+
return None
57+
58+
def get_metaclass_hook(self, fullname: str
59+
) -> Optional[Callable[['mypy.plugin.ClassDefContext'], None]]:
60+
return None
61+
62+
def get_base_class_hook(self, fullname: str
63+
) -> Optional[Callable[['mypy.plugin.ClassDefContext'], None]]:
64+
return None
65+
66+
def get_customize_class_mro_hook(self, fullname: str
67+
) -> Optional[Callable[['mypy.plugin.ClassDefContext'],
68+
None]]:
69+
return None

mypy/plugin.py

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Plugin system for extending mypy."""
22

3+
import types
4+
35
from abc import abstractmethod
46
from functools import partial
57
from typing import Callable, List, Tuple, Optional, NamedTuple, TypeVar, Dict
@@ -16,6 +18,7 @@
1618
)
1719
from mypy.messages import MessageBuilder
1820
from mypy.options import Options
21+
import mypy.interpreted_plugin
1922

2023

2124
@trait
@@ -222,6 +225,58 @@ def get_customize_class_mro_hook(self, fullname: str
222225
T = TypeVar('T')
223226

224227

228+
class WrapperPlugin(Plugin):
229+
"""A plugin that wraps an interpreted plugin.
230+
231+
This is a ugly workaround the limitation that mypyc-compiled
232+
classes can't be subclassed by interpreted ones, so instead we
233+
create a new class for interpreted clients to inherit from and
234+
dispatch to it from here.
235+
236+
Eventually mypyc ought to do something like this automatically.
237+
"""
238+
239+
def __init__(self, plugin: mypy.interpreted_plugin.InterpretedPlugin) -> None:
240+
super().__init__(plugin.options)
241+
self.plugin = plugin
242+
243+
def get_type_analyze_hook(self, fullname: str
244+
) -> Optional[Callable[[AnalyzeTypeContext], Type]]:
245+
return self.plugin.get_type_analyze_hook(fullname)
246+
247+
def get_function_hook(self, fullname: str
248+
) -> Optional[Callable[[FunctionContext], Type]]:
249+
return self.plugin.get_function_hook(fullname)
250+
251+
def get_method_signature_hook(self, fullname: str
252+
) -> Optional[Callable[[MethodSigContext], CallableType]]:
253+
return self.plugin.get_method_signature_hook(fullname)
254+
255+
def get_method_hook(self, fullname: str
256+
) -> Optional[Callable[[MethodContext], Type]]:
257+
return self.plugin.get_method_hook(fullname)
258+
259+
def get_attribute_hook(self, fullname: str
260+
) -> Optional[Callable[[AttributeContext], Type]]:
261+
return self.plugin.get_attribute_hook(fullname)
262+
263+
def get_class_decorator_hook(self, fullname: str
264+
) -> Optional[Callable[[ClassDefContext], None]]:
265+
return self.plugin.get_class_decorator_hook(fullname)
266+
267+
def get_metaclass_hook(self, fullname: str
268+
) -> Optional[Callable[[ClassDefContext], None]]:
269+
return self.plugin.get_metaclass_hook(fullname)
270+
271+
def get_base_class_hook(self, fullname: str
272+
) -> Optional[Callable[[ClassDefContext], None]]:
273+
return self.plugin.get_base_class_hook(fullname)
274+
275+
def get_customize_class_mro_hook(self, fullname: str
276+
) -> Optional[Callable[[ClassDefContext], None]]:
277+
return self.plugin.get_customize_class_mro_hook(fullname)
278+
279+
225280
class ChainedPlugin(Plugin):
226281
"""A plugin that represents a sequence of chained plugins.
227282
@@ -444,3 +499,14 @@ def int_pow_callback(ctx: MethodContext) -> Type:
444499
else:
445500
return ctx.api.named_generic_type('builtins.float', [])
446501
return ctx.default_return_type
502+
503+
504+
# This is an incredibly frumious hack. If this module is compiled by mypyc,
505+
# set the module 'Plugin' attribute to point to InterpretedPlugin. This means
506+
# that anything interpreted that imports Plugin will get InterpretedPlugin
507+
# while anything compiled alongside this module will get the real Plugin.
508+
if isinstance(int_pow_callback, types.BuiltinFunctionType):
509+
plugin_types = (Plugin, mypy.interpreted_plugin.InterpretedPlugin) # type: Tuple[type, ...]
510+
globals()['Plugin'] = mypy.interpreted_plugin.InterpretedPlugin
511+
else:
512+
plugin_types = (Plugin,)

mypy/plugins/common.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
from typing import List, Optional
1+
from typing import List, Optional, Any
22

33
from mypy.nodes import (
44
ARG_POS, MDEF, Argument, Block, CallExpr, Expression, FuncBase,
@@ -47,11 +47,15 @@ def _get_argument(call: CallExpr, name: str) -> Optional[Expression]:
4747
#
4848
# Note: I'm not hard-coding the index so that in the future we can support other
4949
# attrib and class makers.
50+
if not isinstance(call.callee, RefExpr):
51+
return None
52+
5053
callee_type = None
51-
if (isinstance(call.callee, RefExpr)
52-
and isinstance(call.callee.node, (Var, FuncBase))
53-
and call.callee.node.type):
54-
callee_node_type = call.callee.node.type
54+
# mypyc hack to workaround mypy misunderstanding multiple inheritance (#3603)
55+
callee_node = call.callee.node # type: Any
56+
if (isinstance(callee_node, (Var, FuncBase))
57+
and callee_node.type):
58+
callee_node_type = callee_node.type
5559
if isinstance(callee_node_type, Overloaded):
5660
# We take the last overload.
5761
callee_type = callee_node_type.items()[-1]

0 commit comments

Comments
 (0)