Skip to content

Commit 0742d51

Browse files
authored
Document how plugins can support the new semantic analyzer (#7135)
Work towards #7071 (and more).
1 parent 3549c0e commit 0742d51

File tree

2 files changed

+139
-29
lines changed

2 files changed

+139
-29
lines changed

docs/source/extending_mypy.rst

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,3 +235,30 @@ insert some entries there) before the class body is analyzed.
235235
module. It is called before semantic analysis. For example, this can
236236
be used if a library has dependencies that are dynamically loaded
237237
based on configuration information.
238+
239+
Supporting the new semantic analyzer
240+
************************************
241+
242+
Support for the new semantic analyzer (enabled through
243+
``--new-semantic-analyzer``) requires some changes to plugins. Here is
244+
a short summary of the most important changes:
245+
246+
* The order of processing AST nodes is different. Code outside
247+
functions is processed first, and functions and methods are
248+
processed afterwards.
249+
250+
* Each AST node can be processed multiple times to resolve forward
251+
references. The same plugin hook may be called multiple times, so
252+
they need to be idempotent.
253+
254+
* The ``anal_type()`` API method returns ``None`` if some part of
255+
the type is not available yet due to forward references, for example.
256+
257+
* When looking up symbols, you may encounter *placeholder nodes* that
258+
are used for names that haven't been fully processed yet. You'll
259+
generally want to request another semantic analysis iteration by
260+
*deferring* in that case.
261+
262+
See the docstring at the top of
263+
`mypy/plugin.py <https://github.com/python/mypy/blob/master/mypy/plugin.py>`_
264+
for more details.

mypy/plugin.py

Lines changed: 112 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,123 @@
11
"""Plugin system for extending mypy.
22
33
At large scale the plugin system works as following:
4-
* Plugins are collected from the corresponding config option
5-
(either a paths to Python files, or installed Python modules)
6-
and imported using importlib
4+
5+
* Plugins are collected from the corresponding mypy config file option
6+
(either via paths to Python files, or installed Python modules)
7+
and imported using importlib.
8+
79
* Every module should get an entry point function (called 'plugin' by default,
8-
but may be overridden in the config file), that should accept a single string
9-
argument that is a full mypy version (includes git commit hash for dev versions)
10-
and return a subclass of mypy.plugins.Plugin
10+
but may be overridden in the config file) that should accept a single string
11+
argument that is a full mypy version (includes git commit hash for dev
12+
versions) and return a subclass of mypy.plugins.Plugin.
13+
1114
* All plugin class constructors should match the signature of mypy.plugin.Plugin
12-
(i.e. should accept an mypy.options.Options object), and *must* call super().__init__
13-
* At several steps during semantic analysis and type checking mypy calls special `get_xxx`
14-
methods on user plugins with a single string argument that is a full name of a relevant
15-
node (see mypy.plugin.Plugin method docstrings for details)
16-
* The plugins are called in the order they are passed in the config option. Every plugin must
17-
decide whether to act on a given full name. The first plugin that returns non-None object
18-
will be used
15+
(i.e. should accept an mypy.options.Options object), and *must* call
16+
super().__init__().
17+
18+
* At several steps during semantic analysis and type checking mypy calls
19+
special `get_xxx` methods on user plugins with a single string argument that
20+
is a fully qualified name (full name) of a relevant definition
21+
(see mypy.plugin.Plugin method docstrings for details).
22+
23+
* The plugins are called in the order they are passed in the config option.
24+
Every plugin must decide whether to act on a given full name. The first
25+
plugin that returns non-None object will be used.
26+
1927
* The above decision should be made using the limited common API specified by
20-
mypy.plugin.CommonPluginApi
21-
* The callback returned by the plugin will be called with a larger context that includes
22-
relevant current state (e.g. a default return type, or a default attribute type) and
23-
a wider relevant API provider (e.g. SemanticAnalyzerPluginInterface or
24-
CheckerPluginInterface)
25-
* The result of this is used for further processing. See various `XxxContext` named tuples
26-
for details about which information is given to each hook.
28+
mypy.plugin.CommonPluginApi.
29+
30+
* The callback returned by the plugin will be called with a larger context that
31+
includes relevant current state (e.g. a default return type, or a default
32+
attribute type) and a wider relevant API provider (e.g.
33+
SemanticAnalyzerPluginInterface or CheckerPluginInterface).
34+
35+
* The result of this is used for further processing. See various `XxxContext`
36+
named tuples for details about which information is given to each hook.
2737
2838
Plugin developers should ensure that their plugins work well in incremental and
29-
daemon modes. In particular, plugins should not hold global state, and should always call
30-
add_plugin_dependency() in plugin hooks called during semantic analysis, see the method
31-
docstring for more details.
32-
33-
There is no dedicated cache storage for plugins, but plugins can store per-TypeInfo data
34-
in a special .metadata attribute that is serialized to cache between incremental runs.
35-
To avoid collisions between plugins they are encouraged to store their state
36-
under a dedicated key coinciding with plugin name in the metadata dictionary.
37-
Every value stored there must be JSON-serializable.
39+
daemon modes, and with both the old and new semantic analyzers (the old semantic
40+
analyzer will be removed soon). In particular, plugins should not hold global
41+
state, and should always call add_plugin_dependency() in plugin hooks called
42+
during semantic analysis. See the method docstring for more details.
43+
44+
There is no dedicated cache storage for plugins, but plugins can store
45+
per-TypeInfo data in a special .metadata attribute that is serialized to the
46+
mypy caches between incremental runs. To avoid collisions between plugins, they
47+
are encouraged to store their state under a dedicated key coinciding with
48+
plugin name in the metadata dictionary. Every value stored there must be
49+
JSON-serializable.
50+
51+
## New semantic analyzer
52+
53+
The new semantic analyzer (enabled through the --new-semantic-analyzer flag)
54+
changes how plugins are expected to work in several notable ways:
55+
56+
1. The order of processing AST nodes in modules is different. The old semantic
57+
analyzer processes modules in textual order, one module at a time. The new
58+
semantic analyzer first processes the module top levels, including bodies of
59+
any top-level classes and classes nested within classes. ("Top-level" here
60+
means "not nested within a function/method".) Functions and methods are
61+
processed only after module top levels have been finished. If there is an
62+
import cycle, all module top levels in the cycle are processed before
63+
processing any functions or methods. Each unit of processing (a module top
64+
level or a function/method) is called a *target*.
65+
66+
This also means that function signatures in the same module have not been
67+
analyzed yet when analyzing the module top level. If you need access to
68+
a function signature, you'll need to explicitly analyze the signature first
69+
using `anal_type()`.
70+
71+
2. Each target can be processed multiple times. This may happen if some forward
72+
references are not ready yet, for example. This means that semantic analyzer
73+
related plugin hooks can be called multiple times for the same full name.
74+
These plugin methods must thus be idempotent.
75+
76+
3. The `anal_type` API function returns None if some part of the type is not
77+
available yet. If this happens, the current target being analyzed will be
78+
*deferred*, which means that it will be processed again soon, in the hope
79+
that additional dependencies will be available. This may happen if there are
80+
forward references to types or inter-module references to types within an
81+
import cycle.
82+
83+
Note that if there is a circular definition, mypy may decide to stop
84+
processing to avoid an infinite number of iterations. When this happens,
85+
`anal_type` will generate an error and return an `AnyType` type object
86+
during the final iteration (instead of None).
87+
88+
4. There is a new API method `defer()`. This can be used to explicitly request
89+
the current target to be reprocessed one more time. You don't need this
90+
to call this if `anal_type` returns None, however.
91+
92+
5. There is a new API property `final_iteration`, which is true once mypy
93+
detected no progress during the previous iteration or if the maximum
94+
semantic analysis iteration count has been reached. You must never
95+
defer during the final iteration, as it will cause a crash.
96+
97+
6. The `node` attribute of SymbolTableNode objects may contain a reference to
98+
a PlaceholderNode object. This object means that this definition has not
99+
been fully processed yet. If you encounter a PlaceholderNode, you should
100+
defer unless it's the final iteration. If it's the final iteration, you
101+
should generate an error message. It usually means that there's a cyclic
102+
definition that cannot be resolved by mypy. PlaceholderNodes can only refer
103+
to references inside an import cycle. If you are looking up things from
104+
another module, such as the builtins, that is outside the current module or
105+
import cycle, you can safely assume that you won't receive a placeholder.
106+
107+
When testing your plugin with the new semantic analyzer, you should have a test
108+
case that forces a module top level to be processed multiple times. The easiest
109+
way to do this is to include a forward reference to a class in a top-level
110+
annotation. Example:
111+
112+
c: C # Forward reference causes second analysis pass
113+
class C: pass
114+
115+
Note that a forward reference in a function signature won't trigger another
116+
pass, since all functions are processed only after the top level has been fully
117+
analyzed.
118+
119+
You can use `api.options.new_semantic_analyzer` to check whether the new
120+
semantic analyzer is enabled.
38121
"""
39122

40123
import types

0 commit comments

Comments
 (0)