Skip to content

Commit 3283cbf

Browse files
committed
Major redesign of the plugin system
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.
1 parent ae70141 commit 3283cbf

File tree

10 files changed

+228
-226
lines changed

10 files changed

+228
-226
lines changed

mypy/checker.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@
5757
from mypy.binder import ConditionalTypeBinder, get_declaration
5858
from mypy.meet import is_overlapping_types
5959
from mypy.options import Options
60-
from mypy.plugin import Plugin
60+
from mypy.plugin import Plugin, CheckerPluginInterface
6161

6262
from mypy import experiments
6363

@@ -80,7 +80,7 @@
8080
])
8181

8282

83-
class TypeChecker(NodeVisitor[None]):
83+
class TypeChecker(NodeVisitor[None], CheckerPluginInterface):
8484
"""Mypy type checker.
8585
8686
Type check mypy source files that have been semantically analyzed.

mypy/checkexpr.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@
4545
from mypy.util import split_module_names
4646
from mypy.typevars import fill_typevars
4747
from mypy.visitor import ExpressionVisitor
48-
from mypy.plugin import Plugin, PluginContext, MethodSignatureHook
48+
from mypy.plugin import Plugin, MethodContext, MethodSigContext, FunctionContext
4949
from mypy.typeanal import make_optional_type
5050

5151
from mypy import experiments
@@ -392,17 +392,21 @@ def apply_function_plugin(self,
392392
# Apply function plugin
393393
callback = self.plugin.get_function_hook(fullname)
394394
assert callback is not None # Assume that caller ensures this
395-
return callback(formal_arg_types, formal_arg_exprs, inferred_ret_type,
396-
self.chk.named_generic_type)
395+
return callback(
396+
FunctionContext(formal_arg_types, inferred_ret_type, formal_arg_exprs,
397+
context, self.chk))
397398
else:
398399
# Apply method plugin
399400
method_callback = self.plugin.get_method_hook(fullname)
400401
assert method_callback is not None # Assume that caller ensures this
401-
return method_callback(object_type, formal_arg_types, formal_arg_exprs,
402-
inferred_ret_type, self.create_plugin_context(context))
403-
404-
def apply_method_signature_hook(self, e: CallExpr, callee: FunctionLike, object_type: Type,
405-
signature_hook: MethodSignatureHook) -> FunctionLike:
402+
return method_callback(
403+
MethodContext(object_type, formal_arg_types,
404+
inferred_ret_type, formal_arg_exprs,
405+
context, self.chk))
406+
407+
def apply_method_signature_hook(
408+
self, e: CallExpr, callee: FunctionLike, object_type: Type,
409+
signature_hook: Callable[[MethodSigContext], CallableType]) -> FunctionLike:
406410
"""Apply a plugin hook that may infer a more precise signature for a method."""
407411
if isinstance(callee, CallableType):
408412
arg_kinds = e.arg_kinds
@@ -417,8 +421,8 @@ def apply_method_signature_hook(self, e: CallExpr, callee: FunctionLike, object_
417421
for formal, actuals in enumerate(formal_to_actual):
418422
for actual in actuals:
419423
formal_arg_exprs[formal].append(args[actual])
420-
return signature_hook(object_type, formal_arg_exprs, callee,
421-
self.chk.named_generic_type)
424+
return signature_hook(
425+
MethodSigContext(object_type, formal_arg_exprs, callee, e, self.chk))
422426
else:
423427
assert isinstance(callee, Overloaded)
424428
items = []
@@ -428,9 +432,6 @@ def apply_method_signature_hook(self, e: CallExpr, callee: FunctionLike, object_
428432
items.append(adjusted)
429433
return Overloaded(items)
430434

431-
def create_plugin_context(self, context: Context) -> PluginContext:
432-
return PluginContext(self.chk.named_generic_type, self.msg, context)
433-
434435
def check_call_expr_with_callee_type(self,
435436
callee_type: Type,
436437
e: CallExpr,

mypy/checkmember.py

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
from mypy.expandtype import expand_type_by_instance, expand_type, freshen_function_type_vars
1818
from mypy.infer import infer_type_arguments
1919
from mypy.typevars import fill_typevars
20-
from mypy.plugin import Plugin
20+
from mypy.plugin import Plugin, AttributeContext
2121
from mypy import messages
2222
from mypy import subtypes
2323
MYPY = False
@@ -78,7 +78,7 @@ def analyze_member_access(name: str,
7878
assert isinstance(method, OverloadedFuncDef)
7979
first_item = cast(Decorator, method.items[0])
8080
return analyze_var(name, first_item.var, typ, info, node, is_lvalue, msg,
81-
original_type, not_ready_callback, chk.plugin)
81+
original_type, not_ready_callback, chk=chk)
8282
if is_lvalue:
8383
msg.cant_assign_to_method(node)
8484
signature = function_type(method, builtin_type('builtins.function'))
@@ -228,9 +228,8 @@ def analyze_member_var_access(name: str, itype: Instance, info: TypeInfo,
228228
v = vv.var
229229

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

273272
def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Context,
274273
is_lvalue: bool, msg: MessageBuilder, original_type: Type,
275-
not_ready_callback: Callable[[str, Context], None],
276-
plugin: Optional[Plugin]) -> Type:
274+
not_ready_callback: Callable[[str, Context], None], *,
275+
chk: 'mypy.checker.TypeChecker') -> Type:
277276
"""Analyze access to an attribute via a Var node.
278277
279278
This is conceptually part of analyze_member_access and the arguments are similar.
@@ -320,11 +319,10 @@ def analyze_var(name: str, var: Var, itype: Instance, info: TypeInfo, node: Cont
320319
not_ready_callback(var.name(), node)
321320
# Implicit 'Any' type.
322321
result = AnyType()
323-
if plugin:
324-
fullname = '{}.{}'.format(var.info.fullname(), name)
325-
hook = plugin.get_attribute_hook(fullname)
326-
if hook:
327-
result = hook(original_type, result)
322+
fullname = '{}.{}'.format(var.info.fullname(), name)
323+
hook = chk.plugin.get_attribute_hook(fullname)
324+
if hook:
325+
result = hook(AttributeContext(original_type, result, node, chk))
328326
return result
329327

330328

0 commit comments

Comments
 (0)