Skip to content

Commit

Permalink
Major redesign of the plugin system
Browse files Browse the repository at this point in the history
Instead of passing several arguments to hook function, always
pass just a single object. This simplifies the signatures of
hooks.

Instead of passing callback functions to hooks, pass an object
that implements a specific interface.

These changes are intended to make it easier to write plugins,
and to make it easier to evolve the plugin system. Adding
extra attributes to context or extra methods to the internal
interfaces doesn't require changes to existing plugins.
  • Loading branch information
JukkaL committed Jun 20, 2017
1 parent ae70141 commit 3283cbf
Show file tree
Hide file tree
Showing 10 changed files with 228 additions and 226 deletions.
4 changes: 2 additions & 2 deletions mypy/checker.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@
from mypy.binder import ConditionalTypeBinder, get_declaration
from mypy.meet import is_overlapping_types
from mypy.options import Options
from mypy.plugin import Plugin
from mypy.plugin import Plugin, CheckerPluginInterface

from mypy import experiments

Expand All @@ -80,7 +80,7 @@
])


class TypeChecker(NodeVisitor[None]):
class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
"""Mypy type checker.
Type check mypy source files that have been semantically analyzed.
Expand Down
27 changes: 14 additions & 13 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
from mypy.util import split_module_names
from mypy.typevars import fill_typevars
from mypy.visitor import ExpressionVisitor
from mypy.plugin import Plugin, PluginContext, MethodSignatureHook
from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext
from mypy.typeanal import make_optional_type

from mypy import experiments
Expand Down Expand Up @@ -392,17 +392,21 @@ def apply_function_plugin(self,
# Apply function plugin
callback = self.plugin.get_function_hook(fullname)
assert callback is not None # Assume that caller ensures this
return callback(formal_arg_types, formal_arg_exprs, inferred_ret_type,
self.chk.named_generic_type)
return callback(
FunctionContext(formal_arg_types, inferred_ret_type, formal_arg_exprs,
context, self.chk))
else:
# Apply method plugin
method_callback = self.plugin.get_method_hook(fullname)
assert method_callback is not None # Assume that caller ensures this
return method_callback(object_type, formal_arg_types, formal_arg_exprs,
inferred_ret_type, self.create_plugin_context(context))

def apply_method_signature_hook(self, e: CallExpr, callee: FunctionLike, object_type: Type,
signature_hook: MethodSignatureHook) -> FunctionLike:
return method_callback(
MethodContext(object_type, formal_arg_types,
inferred_ret_type, formal_arg_exprs,
context, self.chk))

def apply_method_signature_hook(
self, e: CallExpr, callee: FunctionLike, object_type: Type,
signature_hook: Callable[[MethodSigContext], CallableType]) -> FunctionLike:
"""Apply a plugin hook that may infer a more precise signature for a method."""
if isinstance(callee, CallableType):
arg_kinds = e.arg_kinds
Expand All @@ -417,8 +421,8 @@ def apply_method_signature_hook(self, e: CallExpr, callee: FunctionLike, object_
for formal, actuals in enumerate(formal_to_actual):
for actual in actuals:
formal_arg_exprs[formal].append(args[actual])
return signature_hook(object_type, formal_arg_exprs, callee,
self.chk.named_generic_type)
return signature_hook(
MethodSigContext(object_type, formal_arg_exprs, callee, e, self.chk))
else:
assert isinstance(callee, Overloaded)
items = []
Expand All @@ -428,9 +432,6 @@ def apply_method_signature_hook(self, e: CallExpr, callee: FunctionLike, object_
items.append(adjusted)
return Overloaded(items)

def create_plugin_context(self, context: Context) -> PluginContext:
return PluginContext(self.chk.named_generic_type, self.msg, context)

def check_call_expr_with_callee_type(self,
callee_type: Type,
e: CallExpr,
Expand Down
20 changes: 9 additions & 11 deletions mypy/checkmember.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from mypy.expandtype import expand_type_by_instance, expand_type, freshen_function_type_vars
from mypy.infer import infer_type_arguments
from mypy.typevars import fill_typevars
from mypy.plugin import Plugin
from mypy.plugin import Plugin, AttributeContext
from mypy import messages
from mypy import subtypes
MYPY = False
Expand Down Expand Up @@ -78,7 +78,7 @@ def analyze_member_access(name: str,
assert isinstance(method, OverloadedFuncDef)
first_item = cast(Decorator, method.items[0])
return analyze_var(name, first_item.var, typ, info, node, is_lvalue, msg,
original_type, not_ready_callback, chk.plugin)
original_type, not_ready_callback, chk=chk)
if is_lvalue:
msg.cant_assign_to_method(node)
signature = function_type(method, builtin_type('builtins.function'))
Expand Down Expand Up @@ -228,9 +228,8 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
v = vv.var

if isinstance(v, Var):
plugin = chk.plugin if chk is not None else None
return analyze_var(name, v, itype, info, node, is_lvalue, msg,
original_type, not_ready_callback, plugin)
original_type, not_ready_callback, chk=chk)
elif isinstance(v, FuncDef):
assert False, "Did not expect a function"
elif not v and name not in ['__getattr__', '__setattr__', '__getattribute__']:
Expand Down Expand Up @@ -272,8 +271,8 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,

def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context,
is_lvalue: bool, msg: MessageBuilder, original_type: Type,
not_ready_callback: Callable[[str, Context], None],
plugin: Optional[Plugin]) -> Type:
not_ready_callback: Callable[[str, Context], None], *,
chk: 'mypy.checker.TypeChecker') -> Type:
"""Analyze access to an attribute via a Var node.
This is conceptually part of analyze_member_access and the arguments are similar.
Expand Down Expand Up @@ -320,11 +319,10 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont
not_ready_callback(var.name(), node)
# Implicit 'Any' type.
result = AnyType()
if plugin:
fullname = '{}.{}'.format(var.info.fullname(), name)
hook = plugin.get_attribute_hook(fullname)
if hook:
result = hook(original_type, result)
fullname = '{}.{}'.format(var.info.fullname(), name)
hook = chk.plugin.get_attribute_hook(fullname)
if hook:
result = hook(AttributeContext(original_type, result, node, chk))
return result


Expand Down
Loading

0 comments on commit 3283cbf

Please sign in to comment.