| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901 |
- """Plugin system for extending mypy.
- At large scale the plugin system works as following:
- * Plugins are collected from the corresponding mypy config file option
- (either via paths to Python files, or installed Python modules)
- and imported using importlib.
- * Every module should get an entry point function (called 'plugin' by default,
- but may be overridden in the config file) that should accept a single string
- argument that is a full mypy version (includes git commit hash for dev
- versions) and return a subclass of mypy.plugins.Plugin.
- * All plugin class constructors should match the signature of mypy.plugin.Plugin
- (i.e. should accept an mypy.options.Options object), and *must* call
- super().__init__().
- * At several steps during semantic analysis and type checking mypy calls
- special `get_xxx` methods on user plugins with a single string argument that
- is a fully qualified name (full name) of a relevant definition
- (see mypy.plugin.Plugin method docstrings for details).
- * The plugins are called in the order they are passed in the config option.
- Every plugin must decide whether to act on a given full name. The first
- plugin that returns non-None object will be used.
- * The above decision should be made using the limited common API specified by
- mypy.plugin.CommonPluginApi.
- * The callback returned by the plugin will be called with a larger context that
- includes relevant current state (e.g. a default return type, or a default
- attribute type) and a wider relevant API provider (e.g.
- SemanticAnalyzerPluginInterface or CheckerPluginInterface).
- * The result of this is used for further processing. See various `XxxContext`
- named tuples for details about which information is given to each hook.
- Plugin developers should ensure that their plugins work well in incremental and
- daemon modes. In particular, plugins should not hold global state, and should
- always call add_plugin_dependency() in plugin hooks called during semantic
- analysis. See the method docstring for more details.
- There is no dedicated cache storage for plugins, but plugins can store
- per-TypeInfo data in a special .metadata attribute that is serialized to the
- mypy caches between incremental runs. To avoid collisions between plugins, they
- are encouraged to store their state under a dedicated key coinciding with
- plugin name in the metadata dictionary. Every value stored there must be
- JSON-serializable.
- ## Notes about the semantic analyzer
- Mypy 0.710 introduced a new semantic analyzer that changed how plugins are
- expected to work in several notable ways (from mypy 0.730 the old semantic
- analyzer is no longer available):
- 1. The order of processing AST nodes in modules is different. The old semantic
- analyzer processed modules in textual order, one module at a time. The new
- semantic analyzer first processes the module top levels, including bodies of
- any top-level classes and classes nested within classes. ("Top-level" here
- means "not nested within a function/method".) Functions and methods are
- processed only after module top levels have been finished. If there is an
- import cycle, all module top levels in the cycle are processed before
- processing any functions or methods. Each unit of processing (a module top
- level or a function/method) is called a *target*.
- This also means that function signatures in the same module have not been
- analyzed yet when analyzing the module top level. If you need access to
- a function signature, you'll need to explicitly analyze the signature first
- using `anal_type()`.
- 2. Each target can be processed multiple times. This may happen if some forward
- references are not ready yet, for example. This means that semantic analyzer
- related plugin hooks can be called multiple times for the same full name.
- These plugin methods must thus be idempotent.
- 3. The `anal_type` API function returns None if some part of the type is not
- available yet. If this happens, the current target being analyzed will be
- *deferred*, which means that it will be processed again soon, in the hope
- that additional dependencies will be available. This may happen if there are
- forward references to types or inter-module references to types within an
- import cycle.
- Note that if there is a circular definition, mypy may decide to stop
- processing to avoid an infinite number of iterations. When this happens,
- `anal_type` will generate an error and return an `AnyType` type object
- during the final iteration (instead of None).
- 4. There is a new API method `defer()`. This can be used to explicitly request
- the current target to be reprocessed one more time. You don't need this
- to call this if `anal_type` returns None, however.
- 5. There is a new API property `final_iteration`, which is true once mypy
- detected no progress during the previous iteration or if the maximum
- semantic analysis iteration count has been reached. You must never
- defer during the final iteration, as it will cause a crash.
- 6. The `node` attribute of SymbolTableNode objects may contain a reference to
- a PlaceholderNode object. This object means that this definition has not
- been fully processed yet. If you encounter a PlaceholderNode, you should
- defer unless it's the final iteration. If it's the final iteration, you
- should generate an error message. It usually means that there's a cyclic
- definition that cannot be resolved by mypy. PlaceholderNodes can only refer
- to references inside an import cycle. If you are looking up things from
- another module, such as the builtins, that is outside the current module or
- import cycle, you can safely assume that you won't receive a placeholder.
- When testing your plugin, you should have a test case that forces a module top
- level to be processed multiple times. The easiest way to do this is to include
- a forward reference to a class in a top-level annotation. Example:
- c: C # Forward reference causes second analysis pass
- class C: pass
- Note that a forward reference in a function signature won't trigger another
- pass, since all functions are processed only after the top level has been fully
- analyzed.
- You can use `api.options.new_semantic_analyzer` to check whether the new
- semantic analyzer is enabled (it's always true in mypy 0.730 and later).
- """
- from __future__ import annotations
- from abc import abstractmethod
- from typing import Any, Callable, NamedTuple, TypeVar
- from mypy_extensions import mypyc_attr, trait
- from mypy.errorcodes import ErrorCode
- from mypy.lookup import lookup_fully_qualified
- from mypy.message_registry import ErrorMessage
- from mypy.messages import MessageBuilder
- from mypy.nodes import (
- ArgKind,
- CallExpr,
- ClassDef,
- Context,
- Expression,
- MypyFile,
- SymbolTableNode,
- TypeInfo,
- )
- from mypy.options import Options
- from mypy.tvar_scope import TypeVarLikeScope
- from mypy.types import (
- CallableType,
- FunctionLike,
- Instance,
- ProperType,
- Type,
- TypeList,
- UnboundType,
- )
- @trait
- class TypeAnalyzerPluginInterface:
- """Interface for accessing semantic analyzer functionality in plugins.
- Methods docstrings contain only basic info. Look for corresponding implementation
- docstrings in typeanal.py for more details.
- """
- # An options object. Note: these are the cloned options for the current file.
- # This might be different from Plugin.options (that contains default/global options)
- # if there are per-file options in the config. This applies to all other interfaces
- # in this file.
- options: Options
- @abstractmethod
- def fail(self, msg: str, ctx: Context, *, code: ErrorCode | None = None) -> None:
- """Emit an error message at given location."""
- raise NotImplementedError
- @abstractmethod
- def named_type(self, name: str, args: list[Type]) -> Instance:
- """Construct an instance of a builtin type with given name."""
- raise NotImplementedError
- @abstractmethod
- def analyze_type(self, typ: Type) -> Type:
- """Analyze an unbound type using the default mypy logic."""
- raise NotImplementedError
- @abstractmethod
- def analyze_callable_args(
- self, arglist: TypeList
- ) -> tuple[list[Type], list[ArgKind], list[str | None]] | None:
- """Find types, kinds, and names of arguments from extended callable syntax."""
- raise NotImplementedError
- # A context for a hook that semantically analyzes an unbound type.
- class AnalyzeTypeContext(NamedTuple):
- type: UnboundType # Type to analyze
- context: Context # Relevant location context (e.g. for error messages)
- api: TypeAnalyzerPluginInterface
- @mypyc_attr(allow_interpreted_subclasses=True)
- class CommonPluginApi:
- """
- A common plugin API (shared between semantic analysis and type checking phases)
- that all plugin hooks get independently of the context.
- """
- # Global mypy options.
- # Per-file options can be only accessed on various
- # XxxPluginInterface classes.
- options: Options
- @abstractmethod
- def lookup_fully_qualified(self, fullname: str) -> SymbolTableNode | None:
- """Lookup a symbol by its full name (including module).
- This lookup function available for all plugins. Return None if a name
- is not found. This function doesn't support lookup from current scope.
- Use SemanticAnalyzerPluginInterface.lookup_qualified() for this."""
- raise NotImplementedError
- @trait
- class CheckerPluginInterface:
- """Interface for accessing type checker functionality in plugins.
- Methods docstrings contain only basic info. Look for corresponding implementation
- docstrings in checker.py for more details.
- """
- msg: MessageBuilder
- options: Options
- path: str
- # Type context for type inference
- @property
- @abstractmethod
- def type_context(self) -> list[Type | None]:
- """Return the type context of the plugin"""
- raise NotImplementedError
- @abstractmethod
- def fail(
- self, msg: str | ErrorMessage, ctx: Context, *, code: ErrorCode | None = None
- ) -> None:
- """Emit an error message at given location."""
- raise NotImplementedError
- @abstractmethod
- def named_generic_type(self, name: str, args: list[Type]) -> Instance:
- """Construct an instance of a builtin type with given type arguments."""
- raise NotImplementedError
- @abstractmethod
- def get_expression_type(self, node: Expression, type_context: Type | None = None) -> Type:
- """Checks the type of the given expression."""
- raise NotImplementedError
- @trait
- class SemanticAnalyzerPluginInterface:
- """Interface for accessing semantic analyzer functionality in plugins.
- Methods docstrings contain only basic info. Look for corresponding implementation
- docstrings in semanal.py for more details.
- # TODO: clean-up lookup functions.
- """
- modules: dict[str, MypyFile]
- # Options for current file.
- options: Options
- cur_mod_id: str
- msg: MessageBuilder
- @abstractmethod
- def named_type(self, fullname: str, args: list[Type] | None = None) -> Instance:
- """Construct an instance of a builtin type with given type arguments."""
- raise NotImplementedError
- @abstractmethod
- def builtin_type(self, fully_qualified_name: str) -> Instance:
- """Legacy function -- use named_type() instead."""
- # NOTE: Do not delete this since many plugins may still use it.
- raise NotImplementedError
- @abstractmethod
- def named_type_or_none(self, fullname: str, args: list[Type] | None = None) -> Instance | None:
- """Construct an instance of a type with given type arguments.
- Return None if a type could not be constructed for the qualified
- type name. This is possible when the qualified name includes a
- module name and the module has not been imported.
- """
- raise NotImplementedError
- @abstractmethod
- def basic_new_typeinfo(self, name: str, basetype_or_fallback: Instance, line: int) -> TypeInfo:
- raise NotImplementedError
- @abstractmethod
- def parse_bool(self, expr: Expression) -> bool | None:
- """Parse True/False literals."""
- raise NotImplementedError
- @abstractmethod
- def parse_str_literal(self, expr: Expression) -> str | None:
- """Parse string literals."""
- @abstractmethod
- def fail(
- self,
- msg: str,
- ctx: Context,
- serious: bool = False,
- *,
- blocker: bool = False,
- code: ErrorCode | None = None,
- ) -> None:
- """Emit an error message at given location."""
- raise NotImplementedError
- @abstractmethod
- def anal_type(
- self,
- t: Type,
- *,
- tvar_scope: TypeVarLikeScope | None = None,
- allow_tuple_literal: bool = False,
- allow_unbound_tvars: bool = False,
- report_invalid_types: bool = True,
- third_pass: bool = False,
- ) -> Type | None:
- """Analyze an unbound type.
- Return None if some part of the type is not ready yet. In this
- case the current target being analyzed will be deferred and
- analyzed again.
- """
- raise NotImplementedError
- @abstractmethod
- def class_type(self, self_type: Type) -> Type:
- """Generate type of first argument of class methods from type of self."""
- raise NotImplementedError
- @abstractmethod
- def lookup_fully_qualified(self, name: str) -> SymbolTableNode:
- """Lookup a symbol by its fully qualified name.
- Raise an error if not found.
- """
- raise NotImplementedError
- @abstractmethod
- def lookup_fully_qualified_or_none(self, name: str) -> SymbolTableNode | None:
- """Lookup a symbol by its fully qualified name.
- Return None if not found.
- """
- raise NotImplementedError
- @abstractmethod
- def lookup_qualified(
- self, name: str, ctx: Context, suppress_errors: bool = False
- ) -> SymbolTableNode | None:
- """Lookup symbol using a name in current scope.
- This follows Python local->non-local->global->builtins rules.
- """
- raise NotImplementedError
- @abstractmethod
- def add_plugin_dependency(self, trigger: str, target: str | None = None) -> None:
- """Specify semantic dependencies for generated methods/variables.
- If the symbol with full name given by trigger is found to be stale by mypy,
- then the body of node with full name given by target will be re-checked.
- By default, this is the node that is currently analyzed.
- For example, the dataclass plugin adds a generated __init__ method with
- a signature that depends on types of attributes in ancestor classes. If any
- attribute in an ancestor class gets stale (modified), we need to reprocess
- the subclasses (and thus regenerate __init__ methods).
- This is used by fine-grained incremental mode (mypy daemon). See mypy/server/deps.py
- for more details.
- """
- raise NotImplementedError
- @abstractmethod
- def add_symbol_table_node(self, name: str, stnode: SymbolTableNode) -> Any:
- """Add node to global symbol table (or to nearest class if there is one)."""
- raise NotImplementedError
- @abstractmethod
- def qualified_name(self, n: str) -> str:
- """Make qualified name using current module and enclosing class (if any)."""
- raise NotImplementedError
- @abstractmethod
- def defer(self) -> None:
- """Call this to defer the processing of the current node.
- This will request an additional iteration of semantic analysis.
- """
- raise NotImplementedError
- @property
- @abstractmethod
- def final_iteration(self) -> bool:
- """Is this the final iteration of semantic analysis?"""
- raise NotImplementedError
- @property
- @abstractmethod
- def is_stub_file(self) -> bool:
- raise NotImplementedError
- @abstractmethod
- def analyze_simple_literal_type(self, rvalue: Expression, is_final: bool) -> Type | None:
- raise NotImplementedError
- # A context for querying for configuration data about a module for
- # cache invalidation purposes.
- class ReportConfigContext(NamedTuple):
- id: str # Module name
- path: str # Module file path
- is_check: bool # Is this invocation for checking whether the config matches
- # A context for a function signature hook that infers a better signature for a
- # function. Note that argument types aren't available yet. If you need them,
- # you have to use a method hook instead.
- class FunctionSigContext(NamedTuple):
- args: list[list[Expression]] # Actual expressions for each formal argument
- default_signature: CallableType # Original signature of the method
- context: Context # Relevant location context (e.g. for error messages)
- api: CheckerPluginInterface
- # A context for a function hook that infers the return type of a function with
- # a special signature.
- #
- # A no-op callback would just return the inferred return type, but a useful
- # callback at least sometimes can infer a more precise type.
- class FunctionContext(NamedTuple):
- arg_types: list[list[Type]] # List of actual caller types for each formal argument
- arg_kinds: list[list[ArgKind]] # Ditto for argument kinds, see nodes.ARG_* constants
- # Names of formal parameters from the callee definition,
- # these will be sufficient in most cases.
- callee_arg_names: list[str | None]
- # Names of actual arguments in the call expression. For example,
- # in a situation like this:
- # def func(**kwargs) -> None:
- # pass
- # func(kw1=1, kw2=2)
- # callee_arg_names will be ['kwargs'] and arg_names will be [['kw1', 'kw2']].
- arg_names: list[list[str | None]]
- default_return_type: Type # Return type inferred from signature
- args: list[list[Expression]] # Actual expressions for each formal argument
- context: Context # Relevant location context (e.g. for error messages)
- api: CheckerPluginInterface
- # A context for a method signature hook that infers a better signature for a
- # method. Note that argument types aren't available yet. If you need them,
- # you have to use a method hook instead.
- # TODO: document ProperType in the plugin changelog/update issue.
- class MethodSigContext(NamedTuple):
- type: ProperType # Base object type for method call
- args: list[list[Expression]] # Actual expressions for each formal argument
- default_signature: CallableType # Original signature of the method
- context: Context # Relevant location context (e.g. for error messages)
- api: CheckerPluginInterface
- # A context for a method hook that infers the return type of a method with a
- # special signature.
- #
- # This is very similar to FunctionContext (only differences are documented).
- class MethodContext(NamedTuple):
- type: ProperType # Base object type for method call
- arg_types: list[list[Type]] # List of actual caller types for each formal argument
- # see FunctionContext for details about names and kinds
- arg_kinds: list[list[ArgKind]]
- callee_arg_names: list[str | None]
- arg_names: list[list[str | None]]
- default_return_type: Type # Return type inferred by mypy
- args: list[list[Expression]] # Lists of actual expressions for every formal argument
- context: Context
- api: CheckerPluginInterface
- # A context for an attribute type hook that infers the type of an attribute.
- class AttributeContext(NamedTuple):
- type: ProperType # Type of object with attribute
- default_attr_type: Type # Original attribute type
- context: Context # Relevant location context (e.g. for error messages)
- api: CheckerPluginInterface
- # A context for a class hook that modifies the class definition.
- class ClassDefContext(NamedTuple):
- cls: ClassDef # The class definition
- reason: Expression # The expression being applied (decorator, metaclass, base class)
- api: SemanticAnalyzerPluginInterface
- # A context for dynamic class definitions like
- # Base = declarative_base()
- class DynamicClassDefContext(NamedTuple):
- call: CallExpr # The r.h.s. of dynamic class definition
- name: str # The name this class is being assigned to
- api: SemanticAnalyzerPluginInterface
- @mypyc_attr(allow_interpreted_subclasses=True)
- class Plugin(CommonPluginApi):
- """Base class of all type checker plugins.
- This defines a no-op plugin. Subclasses can override some methods to
- provide some actual functionality.
- All get_ methods are treated as pure functions (you should assume that
- results might be cached). A plugin should return None from a get_ method
- to give way to other plugins.
- Look at the comments of various *Context objects for additional information on
- various hooks.
- """
- def __init__(self, options: Options) -> None:
- self.options = options
- self.python_version = options.python_version
- # This can't be set in __init__ because it is executed too soon in build.py.
- # Therefore, build.py *must* set it later before graph processing starts
- # by calling set_modules().
- self._modules: dict[str, MypyFile] | None = None
- def set_modules(self, modules: dict[str, MypyFile]) -> None:
- self._modules = modules
- def lookup_fully_qualified(self, fullname: str) -> SymbolTableNode | None:
- assert self._modules is not None
- return lookup_fully_qualified(fullname, self._modules)
- def report_config_data(self, ctx: ReportConfigContext) -> Any:
- """Get representation of configuration data for a module.
- The data must be encodable as JSON and will be stored in the
- cache metadata for the module. A mismatch between the cached
- values and the returned will result in that module's cache
- being invalidated and the module being rechecked.
- This can be called twice for each module, once after loading
- the cache to check if it is valid and once while writing new
- cache information.
- If is_check in the context is true, then the return of this
- call will be checked against the cached version. Otherwise the
- call is being made to determine what to put in the cache. This
- can be used to allow consulting extra cache files in certain
- complex situations.
- This can be used to incorporate external configuration information
- that might require changes to typechecking.
- """
- return None
- def get_additional_deps(self, file: MypyFile) -> list[tuple[int, str, int]]:
- """Customize dependencies for a module.
- This hook allows adding in new dependencies for a module. It
- is called after parsing a file but before analysis. This can
- be useful if a library has dependencies that are dynamic based
- on configuration information, for example.
- Returns a list of (priority, module name, line number) tuples.
- The line number can be -1 when there is not a known real line number.
- Priorities are defined in mypy.build (but maybe shouldn't be).
- 10 is a good choice for priority.
- """
- return []
- def get_type_analyze_hook(self, fullname: str) -> Callable[[AnalyzeTypeContext], Type] | None:
- """Customize behaviour of the type analyzer for given full names.
- This method is called during the semantic analysis pass whenever mypy sees an
- unbound type. For example, while analysing this code:
- from lib import Special, Other
- var: Special
- def func(x: Other[int]) -> None:
- ...
- this method will be called with 'lib.Special', and then with 'lib.Other'.
- The callback returned by plugin must return an analyzed type,
- i.e. an instance of `mypy.types.Type`.
- """
- return None
- def get_function_signature_hook(
- self, fullname: str
- ) -> Callable[[FunctionSigContext], FunctionLike] | None:
- """Adjust the signature of a function.
- This method is called before type checking a function call. Plugin
- may infer a better type for the function.
- from lib import Class, do_stuff
- do_stuff(42)
- Class()
- This method will be called with 'lib.do_stuff' and then with 'lib.Class'.
- """
- return None
- def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None:
- """Adjust the return type of a function call.
- This method is called after type checking a call. Plugin may adjust the return
- type inferred by mypy, and/or emit some error messages. Note, this hook is also
- called for class instantiation calls, so that in this example:
- from lib import Class, do_stuff
- do_stuff(42)
- Class()
- This method will be called with 'lib.do_stuff' and then with 'lib.Class'.
- """
- return None
- def get_method_signature_hook(
- self, fullname: str
- ) -> Callable[[MethodSigContext], FunctionLike] | None:
- """Adjust the signature of a method.
- This method is called before type checking a method call. Plugin
- may infer a better type for the method. The hook is also called for special
- Python dunder methods except __init__ and __new__ (use get_function_hook to customize
- class instantiation). This function is called with the method full name using
- the class where it was _defined_. For example, in this code:
- from lib import Special
- class Base:
- def method(self, arg: Any) -> Any:
- ...
- class Derived(Base):
- ...
- var: Derived
- var.method(42)
- x: Special
- y = x[0]
- this method is called with '__main__.Base.method', and then with
- 'lib.Special.__getitem__'.
- """
- return None
- def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None:
- """Adjust return type of a method call.
- This is the same as get_function_hook(), but is called with the
- method full name (again, using the class where the method is defined).
- """
- return None
- def get_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None:
- """Adjust type of an instance attribute.
- This method is called with attribute full name using the class of the instance where
- the attribute was defined (or Var.info.fullname for generated attributes).
- For classes without __getattr__ or __getattribute__, this hook is only called for
- names of fields/properties (but not methods) that exist in the instance MRO.
- For classes that implement __getattr__ or __getattribute__, this hook is called
- for all fields/properties, including nonexistent ones (but still not methods).
- For example:
- class Base:
- x: Any
- def __getattr__(self, attr: str) -> Any: ...
- class Derived(Base):
- ...
- var: Derived
- var.x
- var.y
- get_attribute_hook is called with '__main__.Base.x' and '__main__.Base.y'.
- However, if we had not implemented __getattr__ on Base, you would only get
- the callback for 'var.x'; 'var.y' would produce an error without calling the hook.
- """
- return None
- def get_class_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None:
- """
- Adjust type of a class attribute.
- This method is called with attribute full name using the class where the attribute was
- defined (or Var.info.fullname for generated attributes).
- For example:
- class Cls:
- x: Any
- Cls.x
- get_class_attribute_hook is called with '__main__.Cls.x' as fullname.
- """
- return None
- def get_class_decorator_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
- """Update class definition for given class decorators.
- The plugin can modify a TypeInfo _in place_ (for example add some generated
- methods to the symbol table). This hook is called after the class body was
- semantically analyzed, but *there may still be placeholders* (typically
- caused by forward references).
- NOTE: Usually get_class_decorator_hook_2 is the better option, since it
- guarantees that there are no placeholders.
- The hook is called with full names of all class decorators.
- The hook can be called multiple times per class, so it must be
- idempotent.
- """
- return None
- def get_class_decorator_hook_2(
- self, fullname: str
- ) -> Callable[[ClassDefContext], bool] | None:
- """Update class definition for given class decorators.
- Similar to get_class_decorator_hook, but this runs in a later pass when
- placeholders have been resolved.
- The hook can return False if some base class hasn't been
- processed yet using class hooks. It causes all class hooks
- (that are run in this same pass) to be invoked another time for
- the file(s) currently being processed.
- The hook can be called multiple times per class, so it must be
- idempotent.
- """
- return None
- def get_metaclass_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
- """Update class definition for given declared metaclasses.
- Same as get_class_decorator_hook() but for metaclasses. Note:
- this hook will be only called for explicit metaclasses, not for
- inherited ones.
- TODO: probably it should also be called on inherited metaclasses.
- """
- return None
- def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
- """Update class definition for given base classes.
- Same as get_class_decorator_hook() but for base classes. Base classes
- don't need to refer to TypeInfos, if a base class refers to a variable with
- Any type, this hook will still be called.
- """
- return None
- def get_customize_class_mro_hook(
- self, fullname: str
- ) -> Callable[[ClassDefContext], None] | None:
- """Customize MRO for given classes.
- The plugin can modify the class MRO _in place_. This method is called
- with the class full name before its body was semantically analyzed.
- """
- return None
- def get_dynamic_class_hook(
- self, fullname: str
- ) -> Callable[[DynamicClassDefContext], None] | None:
- """Semantically analyze a dynamic class definition.
- This plugin hook allows one to semantically analyze dynamic class definitions like:
- from lib import dynamic_class
- X = dynamic_class('X', [])
- For such definition, this hook will be called with 'lib.dynamic_class'.
- The plugin should create the corresponding TypeInfo, and place it into a relevant
- symbol table, e.g. using ctx.api.add_symbol_table_node().
- """
- return None
- T = TypeVar("T")
- class ChainedPlugin(Plugin):
- """A plugin that represents a sequence of chained plugins.
- Each lookup method returns the hook for the first plugin that
- reports a match.
- This class should not be subclassed -- use Plugin as the base class
- for all plugins.
- """
- # TODO: Support caching of lookup results (through a LRU cache, for example).
- def __init__(self, options: Options, plugins: list[Plugin]) -> None:
- """Initialize chained plugin.
- Assume that the child plugins aren't mutated (results may be cached).
- """
- super().__init__(options)
- self._plugins = plugins
- def set_modules(self, modules: dict[str, MypyFile]) -> None:
- for plugin in self._plugins:
- plugin.set_modules(modules)
- def report_config_data(self, ctx: ReportConfigContext) -> Any:
- config_data = [plugin.report_config_data(ctx) for plugin in self._plugins]
- return config_data if any(x is not None for x in config_data) else None
- def get_additional_deps(self, file: MypyFile) -> list[tuple[int, str, int]]:
- deps = []
- for plugin in self._plugins:
- deps.extend(plugin.get_additional_deps(file))
- return deps
- def get_type_analyze_hook(self, fullname: str) -> Callable[[AnalyzeTypeContext], Type] | None:
- return self._find_hook(lambda plugin: plugin.get_type_analyze_hook(fullname))
- def get_function_signature_hook(
- self, fullname: str
- ) -> Callable[[FunctionSigContext], FunctionLike] | None:
- return self._find_hook(lambda plugin: plugin.get_function_signature_hook(fullname))
- def get_function_hook(self, fullname: str) -> Callable[[FunctionContext], Type] | None:
- return self._find_hook(lambda plugin: plugin.get_function_hook(fullname))
- def get_method_signature_hook(
- self, fullname: str
- ) -> Callable[[MethodSigContext], FunctionLike] | None:
- return self._find_hook(lambda plugin: plugin.get_method_signature_hook(fullname))
- def get_method_hook(self, fullname: str) -> Callable[[MethodContext], Type] | None:
- return self._find_hook(lambda plugin: plugin.get_method_hook(fullname))
- def get_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None:
- return self._find_hook(lambda plugin: plugin.get_attribute_hook(fullname))
- def get_class_attribute_hook(self, fullname: str) -> Callable[[AttributeContext], Type] | None:
- return self._find_hook(lambda plugin: plugin.get_class_attribute_hook(fullname))
- def get_class_decorator_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
- return self._find_hook(lambda plugin: plugin.get_class_decorator_hook(fullname))
- def get_class_decorator_hook_2(
- self, fullname: str
- ) -> Callable[[ClassDefContext], bool] | None:
- return self._find_hook(lambda plugin: plugin.get_class_decorator_hook_2(fullname))
- def get_metaclass_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
- return self._find_hook(lambda plugin: plugin.get_metaclass_hook(fullname))
- def get_base_class_hook(self, fullname: str) -> Callable[[ClassDefContext], None] | None:
- return self._find_hook(lambda plugin: plugin.get_base_class_hook(fullname))
- def get_customize_class_mro_hook(
- self, fullname: str
- ) -> Callable[[ClassDefContext], None] | None:
- return self._find_hook(lambda plugin: plugin.get_customize_class_mro_hook(fullname))
- def get_dynamic_class_hook(
- self, fullname: str
- ) -> Callable[[DynamicClassDefContext], None] | None:
- return self._find_hook(lambda plugin: plugin.get_dynamic_class_hook(fullname))
- def _find_hook(self, lookup: Callable[[Plugin], T]) -> T | None:
- for plugin in self._plugins:
- hook = lookup(plugin)
- if hook:
- return hook
- return None
|