| """Abstract syntax tree node classes (i.e. parse tree).""" |
| |
| from __future__ import annotations |
| |
| import os |
| from abc import abstractmethod |
| from collections import defaultdict |
| from enum import Enum, unique |
| from typing import ( |
| TYPE_CHECKING, |
| Any, |
| Callable, |
| Dict, |
| Final, |
| Iterator, |
| List, |
| Optional, |
| Sequence, |
| Tuple, |
| TypeVar, |
| Union, |
| cast, |
| ) |
| from typing_extensions import TypeAlias as _TypeAlias, TypeGuard |
| |
| from mypy_extensions import trait |
| |
| import mypy.strconv |
| from mypy.options import Options |
| from mypy.util import short_type |
| from mypy.visitor import ExpressionVisitor, NodeVisitor, StatementVisitor |
| |
| if TYPE_CHECKING: |
| from mypy.patterns import Pattern |
| |
| |
| class Context: |
| """Base type for objects that are valid as error message locations.""" |
| |
| __slots__ = ("line", "column", "end_line", "end_column") |
| |
| def __init__(self, line: int = -1, column: int = -1) -> None: |
| self.line = line |
| self.column = column |
| self.end_line: int | None = None |
| self.end_column: int | None = None |
| |
| def set_line( |
| self, |
| target: Context | int, |
| column: int | None = None, |
| end_line: int | None = None, |
| end_column: int | None = None, |
| ) -> None: |
| """If target is a node, pull line (and column) information |
| into this node. If column is specified, this will override any column |
| information coming from a node. |
| """ |
| if isinstance(target, int): |
| self.line = target |
| else: |
| self.line = target.line |
| self.column = target.column |
| self.end_line = target.end_line |
| self.end_column = target.end_column |
| |
| if column is not None: |
| self.column = column |
| |
| if end_line is not None: |
| self.end_line = end_line |
| |
| if end_column is not None: |
| self.end_column = end_column |
| |
| |
| if TYPE_CHECKING: |
| # break import cycle only needed for mypy |
| import mypy.types |
| |
| |
| T = TypeVar("T") |
| |
| JsonDict: _TypeAlias = Dict[str, Any] |
| |
| |
| # Symbol table node kinds |
| # |
| # TODO rename to use more descriptive names |
| |
| LDEF: Final = 0 |
| GDEF: Final = 1 |
| MDEF: Final = 2 |
| |
| # Placeholder for a name imported via 'from ... import'. Second phase of |
| # semantic will replace this the actual imported reference. This is |
| # needed so that we can detect whether a name has been imported during |
| # XXX what? |
| UNBOUND_IMPORTED: Final = 3 |
| |
| # RevealExpr node kinds |
| REVEAL_TYPE: Final = 0 |
| REVEAL_LOCALS: Final = 1 |
| |
| LITERAL_YES: Final = 2 |
| LITERAL_TYPE: Final = 1 |
| LITERAL_NO: Final = 0 |
| |
| node_kinds: Final = {LDEF: "Ldef", GDEF: "Gdef", MDEF: "Mdef", UNBOUND_IMPORTED: "UnboundImported"} |
| inverse_node_kinds: Final = {_kind: _name for _name, _kind in node_kinds.items()} |
| |
| |
| implicit_module_attrs: Final = { |
| "__name__": "__builtins__.str", |
| "__doc__": None, # depends on Python version, see semanal.py |
| "__path__": None, # depends on if the module is a package |
| "__file__": "__builtins__.str", |
| "__package__": "__builtins__.str", |
| "__annotations__": None, # dict[str, Any] bounded in add_implicit_module_attrs() |
| } |
| |
| |
| # These aliases exist because built-in class objects are not subscriptable. |
| # For example `list[int]` fails at runtime. Instead List[int] should be used. |
| type_aliases: Final = { |
| "typing.List": "builtins.list", |
| "typing.Dict": "builtins.dict", |
| "typing.Set": "builtins.set", |
| "typing.FrozenSet": "builtins.frozenset", |
| "typing.ChainMap": "collections.ChainMap", |
| "typing.Counter": "collections.Counter", |
| "typing.DefaultDict": "collections.defaultdict", |
| "typing.Deque": "collections.deque", |
| "typing.OrderedDict": "collections.OrderedDict", |
| # HACK: a lie in lieu of actual support for PEP 675 |
| "typing.LiteralString": "builtins.str", |
| } |
| |
| # This keeps track of the oldest supported Python version where the corresponding |
| # alias source is available. |
| type_aliases_source_versions: Final = { |
| "typing.List": (2, 7), |
| "typing.Dict": (2, 7), |
| "typing.Set": (2, 7), |
| "typing.FrozenSet": (2, 7), |
| "typing.ChainMap": (3, 3), |
| "typing.Counter": (2, 7), |
| "typing.DefaultDict": (2, 7), |
| "typing.Deque": (2, 7), |
| "typing.OrderedDict": (3, 7), |
| "typing.LiteralString": (3, 11), |
| } |
| |
| # This keeps track of aliases in `typing_extensions`, which we treat specially. |
| typing_extensions_aliases: Final = { |
| # See: https://github.com/python/mypy/issues/11528 |
| "typing_extensions.OrderedDict": "collections.OrderedDict", |
| # HACK: a lie in lieu of actual support for PEP 675 |
| "typing_extensions.LiteralString": "builtins.str", |
| } |
| |
| reverse_builtin_aliases: Final = { |
| "builtins.list": "typing.List", |
| "builtins.dict": "typing.Dict", |
| "builtins.set": "typing.Set", |
| "builtins.frozenset": "typing.FrozenSet", |
| } |
| |
| _nongen_builtins: Final = {"builtins.tuple": "typing.Tuple", "builtins.enumerate": ""} |
| _nongen_builtins.update((name, alias) for alias, name in type_aliases.items()) |
| # Drop OrderedDict from this for backward compatibility |
| del _nongen_builtins["collections.OrderedDict"] |
| # HACK: consequence of hackily treating LiteralString as an alias for str |
| del _nongen_builtins["builtins.str"] |
| |
| |
| def get_nongen_builtins(python_version: tuple[int, int]) -> dict[str, str]: |
| # After 3.9 with pep585 generic builtins are allowed |
| return _nongen_builtins if python_version < (3, 9) else {} |
| |
| |
| RUNTIME_PROTOCOL_DECOS: Final = ( |
| "typing.runtime_checkable", |
| "typing_extensions.runtime", |
| "typing_extensions.runtime_checkable", |
| ) |
| |
| |
| class Node(Context): |
| """Common base class for all non-type parse tree nodes.""" |
| |
| __slots__ = () |
| |
| def __str__(self) -> str: |
| ans = self.accept(mypy.strconv.StrConv(options=Options())) |
| if ans is None: |
| return repr(self) |
| return ans |
| |
| def str_with_options(self, options: Options) -> str: |
| ans = self.accept(mypy.strconv.StrConv(options=options)) |
| assert ans |
| return ans |
| |
| def accept(self, visitor: NodeVisitor[T]) -> T: |
| raise RuntimeError("Not implemented", type(self)) |
| |
| |
| @trait |
| class Statement(Node): |
| """A statement node.""" |
| |
| __slots__ = () |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| raise RuntimeError("Not implemented", type(self)) |
| |
| |
| @trait |
| class Expression(Node): |
| """An expression node.""" |
| |
| __slots__ = () |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| raise RuntimeError("Not implemented", type(self)) |
| |
| |
| class FakeExpression(Expression): |
| """A dummy expression. |
| |
| We need a dummy expression in one place, and can't instantiate Expression |
| because it is a trait and mypyc barfs. |
| """ |
| |
| __slots__ = () |
| |
| |
| # TODO: |
| # Lvalue = Union['NameExpr', 'MemberExpr', 'IndexExpr', 'SuperExpr', 'StarExpr' |
| # 'TupleExpr']; see #1783. |
| Lvalue: _TypeAlias = Expression |
| |
| |
| @trait |
| class SymbolNode(Node): |
| """Nodes that can be stored in a symbol table.""" |
| |
| __slots__ = () |
| |
| @property |
| @abstractmethod |
| def name(self) -> str: |
| pass |
| |
| # Fully qualified name |
| @property |
| @abstractmethod |
| def fullname(self) -> str: |
| pass |
| |
| @abstractmethod |
| def serialize(self) -> JsonDict: |
| pass |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> SymbolNode: |
| classname = data[".class"] |
| method = deserialize_map.get(classname) |
| if method is not None: |
| return method(data) |
| raise NotImplementedError(f"unexpected .class {classname}") |
| |
| |
| # Items: fullname, related symbol table node, surrounding type (if any) |
| Definition: _TypeAlias = Tuple[str, "SymbolTableNode", Optional["TypeInfo"]] |
| |
| |
| class MypyFile(SymbolNode): |
| """The abstract syntax tree of a single source file.""" |
| |
| __slots__ = ( |
| "_fullname", |
| "path", |
| "defs", |
| "alias_deps", |
| "is_bom", |
| "names", |
| "imports", |
| "ignored_lines", |
| "skipped_lines", |
| "is_stub", |
| "is_cache_skeleton", |
| "is_partial_stub_package", |
| "plugin_deps", |
| "future_import_flags", |
| ) |
| |
| __match_args__ = ("name", "path", "defs") |
| |
| # Fully qualified module name |
| _fullname: str |
| # Path to the file (empty string if not known) |
| path: str |
| # Top-level definitions and statements |
| defs: list[Statement] |
| # Type alias dependencies as mapping from target to set of alias full names |
| alias_deps: defaultdict[str, set[str]] |
| # Is there a UTF-8 BOM at the start? |
| is_bom: bool |
| names: SymbolTable |
| # All import nodes within the file (also ones within functions etc.) |
| imports: list[ImportBase] |
| # Lines on which to ignore certain errors when checking. |
| # If the value is empty, ignore all errors; otherwise, the list contains all |
| # error codes to ignore. |
| ignored_lines: dict[int, list[str]] |
| # Lines that were skipped during semantic analysis e.g. due to ALWAYS_FALSE, MYPY_FALSE, |
| # or platform/version checks. Those lines would not be type-checked. |
| skipped_lines: set[int] |
| # Is this file represented by a stub file (.pyi)? |
| is_stub: bool |
| # Is this loaded from the cache and thus missing the actual body of the file? |
| is_cache_skeleton: bool |
| # Does this represent an __init__.pyi stub with a module __getattr__ |
| # (i.e. a partial stub package), for such packages we suppress any missing |
| # module errors in addition to missing attribute errors. |
| is_partial_stub_package: bool |
| # Plugin-created dependencies |
| plugin_deps: dict[str, set[str]] |
| # Future imports defined in this file. Populated during semantic analysis. |
| future_import_flags: set[str] |
| |
| def __init__( |
| self, |
| defs: list[Statement], |
| imports: list[ImportBase], |
| is_bom: bool = False, |
| ignored_lines: dict[int, list[str]] | None = None, |
| ) -> None: |
| super().__init__() |
| self.defs = defs |
| self.line = 1 # Dummy line number |
| self.column = 0 # Dummy column |
| self.imports = imports |
| self.is_bom = is_bom |
| self.alias_deps = defaultdict(set) |
| self.plugin_deps = {} |
| if ignored_lines: |
| self.ignored_lines = ignored_lines |
| else: |
| self.ignored_lines = {} |
| self.skipped_lines = set() |
| |
| self.path = "" |
| self.is_stub = False |
| self.is_cache_skeleton = False |
| self.is_partial_stub_package = False |
| self.future_import_flags = set() |
| |
| def local_definitions(self) -> Iterator[Definition]: |
| """Return all definitions within the module (including nested). |
| |
| This doesn't include imported definitions. |
| """ |
| return local_definitions(self.names, self.fullname) |
| |
| @property |
| def name(self) -> str: |
| return "" if not self._fullname else self._fullname.split(".")[-1] |
| |
| @property |
| def fullname(self) -> str: |
| return self._fullname |
| |
| def accept(self, visitor: NodeVisitor[T]) -> T: |
| return visitor.visit_mypy_file(self) |
| |
| def is_package_init_file(self) -> bool: |
| return len(self.path) != 0 and os.path.basename(self.path).startswith("__init__.") |
| |
| def is_future_flag_set(self, flag: str) -> bool: |
| return flag in self.future_import_flags |
| |
| def serialize(self) -> JsonDict: |
| return { |
| ".class": "MypyFile", |
| "_fullname": self._fullname, |
| "names": self.names.serialize(self._fullname), |
| "is_stub": self.is_stub, |
| "path": self.path, |
| "is_partial_stub_package": self.is_partial_stub_package, |
| "future_import_flags": list(self.future_import_flags), |
| } |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> MypyFile: |
| assert data[".class"] == "MypyFile", data |
| tree = MypyFile([], []) |
| tree._fullname = data["_fullname"] |
| tree.names = SymbolTable.deserialize(data["names"]) |
| tree.is_stub = data["is_stub"] |
| tree.path = data["path"] |
| tree.is_partial_stub_package = data["is_partial_stub_package"] |
| tree.is_cache_skeleton = True |
| tree.future_import_flags = set(data["future_import_flags"]) |
| return tree |
| |
| |
| class ImportBase(Statement): |
| """Base class for all import statements.""" |
| |
| __slots__ = ("is_unreachable", "is_top_level", "is_mypy_only", "assignments") |
| |
| is_unreachable: bool # Set by semanal.SemanticAnalyzerPass1 if inside `if False` etc. |
| is_top_level: bool # Ditto if outside any class or def |
| is_mypy_only: bool # Ditto if inside `if TYPE_CHECKING` or `if MYPY` |
| |
| # If an import replaces existing definitions, we construct dummy assignment |
| # statements that assign the imported names to the names in the current scope, |
| # for type checking purposes. Example: |
| # |
| # x = 1 |
| # from m import x <-- add assignment representing "x = m.x" |
| assignments: list[AssignmentStmt] |
| |
| def __init__(self) -> None: |
| super().__init__() |
| self.assignments = [] |
| self.is_unreachable = False |
| self.is_top_level = False |
| self.is_mypy_only = False |
| |
| |
| class Import(ImportBase): |
| """import m [as n]""" |
| |
| __slots__ = ("ids",) |
| |
| __match_args__ = ("ids",) |
| |
| ids: list[tuple[str, str | None]] # (module id, as id) |
| |
| def __init__(self, ids: list[tuple[str, str | None]]) -> None: |
| super().__init__() |
| self.ids = ids |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_import(self) |
| |
| |
| class ImportFrom(ImportBase): |
| """from m import x [as y], ...""" |
| |
| __slots__ = ("id", "names", "relative") |
| |
| __match_args__ = ("id", "names", "relative") |
| |
| id: str |
| relative: int |
| names: list[tuple[str, str | None]] # Tuples (name, as name) |
| |
| def __init__(self, id: str, relative: int, names: list[tuple[str, str | None]]) -> None: |
| super().__init__() |
| self.id = id |
| self.names = names |
| self.relative = relative |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_import_from(self) |
| |
| |
| class ImportAll(ImportBase): |
| """from m import *""" |
| |
| __slots__ = ("id", "relative") |
| |
| __match_args__ = ("id", "relative") |
| |
| id: str |
| relative: int |
| |
| def __init__(self, id: str, relative: int) -> None: |
| super().__init__() |
| self.id = id |
| self.relative = relative |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_import_all(self) |
| |
| |
| FUNCBASE_FLAGS: Final = ["is_property", "is_class", "is_static", "is_final"] |
| |
| |
| class FuncBase(Node): |
| """Abstract base class for function-like nodes. |
| |
| N.B: Although this has SymbolNode subclasses (FuncDef, |
| OverloadedFuncDef), avoid calling isinstance(..., FuncBase) on |
| something that is typed as SymbolNode. This is to work around |
| mypy bug #3603, in which mypy doesn't understand multiple |
| inheritance very well, and will assume that a SymbolNode |
| cannot be a FuncBase. |
| |
| Instead, test against SYMBOL_FUNCBASE_TYPES, which enumerates |
| SymbolNode subclasses that are also FuncBase subclasses. |
| """ |
| |
| __slots__ = ( |
| "type", |
| "unanalyzed_type", |
| "info", |
| "is_property", |
| "is_class", # Uses "@classmethod" (explicit or implicit) |
| "is_static", # Uses "@staticmethod" (explicit or implicit) |
| "is_final", # Uses "@final" |
| "is_explicit_override", # Uses "@override" |
| "_fullname", |
| ) |
| |
| def __init__(self) -> None: |
| super().__init__() |
| # Type signature. This is usually CallableType or Overloaded, but it can be |
| # something else for decorated functions. |
| self.type: mypy.types.ProperType | None = None |
| # Original, not semantically analyzed type (used for reprocessing) |
| self.unanalyzed_type: mypy.types.ProperType | None = None |
| # If method, reference to TypeInfo |
| # TODO: Type should be Optional[TypeInfo] |
| self.info = FUNC_NO_INFO |
| self.is_property = False |
| self.is_class = False |
| self.is_static = False |
| self.is_final = False |
| self.is_explicit_override = False |
| # Name with module prefix |
| self._fullname = "" |
| |
| @property |
| @abstractmethod |
| def name(self) -> str: |
| pass |
| |
| @property |
| def fullname(self) -> str: |
| return self._fullname |
| |
| |
| OverloadPart: _TypeAlias = Union["FuncDef", "Decorator"] |
| |
| |
| class OverloadedFuncDef(FuncBase, SymbolNode, Statement): |
| """A logical node representing all the variants of a multi-declaration function. |
| |
| A multi-declaration function is often an @overload, but can also be a |
| @property with a setter and a/or a deleter. |
| |
| This node has no explicit representation in the source program. |
| Overloaded variants must be consecutive in the source file. |
| """ |
| |
| __slots__ = ("items", "unanalyzed_items", "impl") |
| |
| items: list[OverloadPart] |
| unanalyzed_items: list[OverloadPart] |
| impl: OverloadPart | None |
| |
| def __init__(self, items: list[OverloadPart]) -> None: |
| super().__init__() |
| self.items = items |
| self.unanalyzed_items = items.copy() |
| self.impl = None |
| if items: |
| # TODO: figure out how to reliably set end position (we don't know the impl here). |
| self.set_line(items[0].line, items[0].column) |
| self.is_final = False |
| |
| @property |
| def name(self) -> str: |
| if self.items: |
| return self.items[0].name |
| else: |
| # This may happen for malformed overload |
| assert self.impl is not None |
| return self.impl.name |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_overloaded_func_def(self) |
| |
| def serialize(self) -> JsonDict: |
| return { |
| ".class": "OverloadedFuncDef", |
| "items": [i.serialize() for i in self.items], |
| "type": None if self.type is None else self.type.serialize(), |
| "fullname": self._fullname, |
| "impl": None if self.impl is None else self.impl.serialize(), |
| "flags": get_flags(self, FUNCBASE_FLAGS), |
| } |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> OverloadedFuncDef: |
| assert data[".class"] == "OverloadedFuncDef" |
| res = OverloadedFuncDef( |
| [cast(OverloadPart, SymbolNode.deserialize(d)) for d in data["items"]] |
| ) |
| if data.get("impl") is not None: |
| res.impl = cast(OverloadPart, SymbolNode.deserialize(data["impl"])) |
| # set line for empty overload items, as not set in __init__ |
| if len(res.items) > 0: |
| res.set_line(res.impl.line) |
| if data.get("type") is not None: |
| typ = mypy.types.deserialize_type(data["type"]) |
| assert isinstance(typ, mypy.types.ProperType) |
| res.type = typ |
| res._fullname = data["fullname"] |
| set_flags(res, data["flags"]) |
| # NOTE: res.info will be set in the fixup phase. |
| return res |
| |
| |
| class Argument(Node): |
| """A single argument in a FuncItem.""" |
| |
| __slots__ = ("variable", "type_annotation", "initializer", "kind", "pos_only") |
| |
| __match_args__ = ("variable", "type_annotation", "initializer", "kind", "pos_only") |
| |
| def __init__( |
| self, |
| variable: Var, |
| type_annotation: mypy.types.Type | None, |
| initializer: Expression | None, |
| kind: ArgKind, |
| pos_only: bool = False, |
| ) -> None: |
| super().__init__() |
| self.variable = variable |
| self.type_annotation = type_annotation |
| self.initializer = initializer |
| self.kind = kind # must be an ARG_* constant |
| self.pos_only = pos_only |
| |
| def set_line( |
| self, |
| target: Context | int, |
| column: int | None = None, |
| end_line: int | None = None, |
| end_column: int | None = None, |
| ) -> None: |
| super().set_line(target, column, end_line, end_column) |
| |
| if self.initializer and self.initializer.line < 0: |
| self.initializer.set_line(self.line, self.column, self.end_line, self.end_column) |
| |
| self.variable.set_line(self.line, self.column, self.end_line, self.end_column) |
| |
| |
| FUNCITEM_FLAGS: Final = FUNCBASE_FLAGS + [ |
| "is_overload", |
| "is_generator", |
| "is_coroutine", |
| "is_async_generator", |
| "is_awaitable_coroutine", |
| ] |
| |
| |
| class FuncItem(FuncBase): |
| """Base class for nodes usable as overloaded function items.""" |
| |
| __slots__ = ( |
| "arguments", # Note that can be unset if deserialized (type is a lie!) |
| "arg_names", # Names of arguments |
| "arg_kinds", # Kinds of arguments |
| "min_args", # Minimum number of arguments |
| "max_pos", # Maximum number of positional arguments, -1 if no explicit |
| # limit (*args not included) |
| "body", # Body of the function |
| "is_overload", # Is this an overload variant of function with more than |
| # one overload variant? |
| "is_generator", # Contains a yield statement? |
| "is_coroutine", # Defined using 'async def' syntax? |
| "is_async_generator", # Is an async def generator? |
| "is_awaitable_coroutine", # Decorated with '@{typing,asyncio}.coroutine'? |
| "expanded", # Variants of function with type variables with values expanded |
| ) |
| |
| __deletable__ = ("arguments", "max_pos", "min_args") |
| |
| def __init__( |
| self, |
| arguments: list[Argument] | None = None, |
| body: Block | None = None, |
| typ: mypy.types.FunctionLike | None = None, |
| ) -> None: |
| super().__init__() |
| self.arguments = arguments or [] |
| self.arg_names = [None if arg.pos_only else arg.variable.name for arg in self.arguments] |
| self.arg_kinds: list[ArgKind] = [arg.kind for arg in self.arguments] |
| self.max_pos: int = self.arg_kinds.count(ARG_POS) + self.arg_kinds.count(ARG_OPT) |
| self.body: Block = body or Block([]) |
| self.type = typ |
| self.unanalyzed_type = typ |
| self.is_overload: bool = False |
| self.is_generator: bool = False |
| self.is_coroutine: bool = False |
| self.is_async_generator: bool = False |
| self.is_awaitable_coroutine: bool = False |
| self.expanded: list[FuncItem] = [] |
| |
| self.min_args = 0 |
| for i in range(len(self.arguments)): |
| if self.arguments[i] is None and i < self.max_fixed_argc(): |
| self.min_args = i + 1 |
| |
| def max_fixed_argc(self) -> int: |
| return self.max_pos |
| |
| def is_dynamic(self) -> bool: |
| return self.type is None |
| |
| |
| FUNCDEF_FLAGS: Final = FUNCITEM_FLAGS + [ |
| "is_decorated", |
| "is_conditional", |
| "is_trivial_body", |
| "is_mypy_only", |
| ] |
| |
| # Abstract status of a function |
| NOT_ABSTRACT: Final = 0 |
| # Explicitly abstract (with @abstractmethod or overload without implementation) |
| IS_ABSTRACT: Final = 1 |
| # Implicitly abstract: used for functions with trivial bodies defined in Protocols |
| IMPLICITLY_ABSTRACT: Final = 2 |
| |
| |
| class FuncDef(FuncItem, SymbolNode, Statement): |
| """Function definition. |
| |
| This is a non-lambda function defined using 'def'. |
| """ |
| |
| __slots__ = ( |
| "_name", |
| "is_decorated", |
| "is_conditional", |
| "abstract_status", |
| "original_def", |
| "deco_line", |
| "is_trivial_body", |
| "is_mypy_only", |
| # Present only when a function is decorated with @typing.datasclass_transform or similar |
| "dataclass_transform_spec", |
| ) |
| |
| __match_args__ = ("name", "arguments", "type", "body") |
| |
| # Note that all __init__ args must have default values |
| def __init__( |
| self, |
| name: str = "", # Function name |
| arguments: list[Argument] | None = None, |
| body: Block | None = None, |
| typ: mypy.types.FunctionLike | None = None, |
| ) -> None: |
| super().__init__(arguments, body, typ) |
| self._name = name |
| self.is_decorated = False |
| self.is_conditional = False # Defined conditionally (within block)? |
| self.abstract_status = NOT_ABSTRACT |
| # Is this an abstract method with trivial body? |
| # Such methods can't be called via super(). |
| self.is_trivial_body = False |
| self.is_final = False |
| # Original conditional definition |
| self.original_def: None | FuncDef | Var | Decorator = None |
| # Used for error reporting (to keep backward compatibility with pre-3.8) |
| self.deco_line: int | None = None |
| # Definitions that appear in if TYPE_CHECKING are marked with this flag. |
| self.is_mypy_only = False |
| self.dataclass_transform_spec: DataclassTransformSpec | None = None |
| |
| @property |
| def name(self) -> str: |
| return self._name |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_func_def(self) |
| |
| def serialize(self) -> JsonDict: |
| # We're deliberating omitting arguments and storing only arg_names and |
| # arg_kinds for space-saving reasons (arguments is not used in later |
| # stages of mypy). |
| # TODO: After a FuncDef is deserialized, the only time we use `arg_names` |
| # and `arg_kinds` is when `type` is None and we need to infer a type. Can |
| # we store the inferred type ahead of time? |
| return { |
| ".class": "FuncDef", |
| "name": self._name, |
| "fullname": self._fullname, |
| "arg_names": self.arg_names, |
| "arg_kinds": [int(x.value) for x in self.arg_kinds], |
| "type": None if self.type is None else self.type.serialize(), |
| "flags": get_flags(self, FUNCDEF_FLAGS), |
| "abstract_status": self.abstract_status, |
| # TODO: Do we need expanded, original_def? |
| "dataclass_transform_spec": ( |
| None |
| if self.dataclass_transform_spec is None |
| else self.dataclass_transform_spec.serialize() |
| ), |
| } |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> FuncDef: |
| assert data[".class"] == "FuncDef" |
| body = Block([]) |
| ret = FuncDef( |
| data["name"], |
| [], |
| body, |
| ( |
| None |
| if data["type"] is None |
| else cast(mypy.types.FunctionLike, mypy.types.deserialize_type(data["type"])) |
| ), |
| ) |
| ret._fullname = data["fullname"] |
| set_flags(ret, data["flags"]) |
| # NOTE: ret.info is set in the fixup phase. |
| ret.arg_names = data["arg_names"] |
| ret.arg_kinds = [ArgKind(x) for x in data["arg_kinds"]] |
| ret.abstract_status = data["abstract_status"] |
| ret.dataclass_transform_spec = ( |
| DataclassTransformSpec.deserialize(data["dataclass_transform_spec"]) |
| if data["dataclass_transform_spec"] is not None |
| else None |
| ) |
| # Leave these uninitialized so that future uses will trigger an error |
| del ret.arguments |
| del ret.max_pos |
| del ret.min_args |
| return ret |
| |
| |
| # All types that are both SymbolNodes and FuncBases. See the FuncBase |
| # docstring for the rationale. |
| SYMBOL_FUNCBASE_TYPES = (OverloadedFuncDef, FuncDef) |
| |
| |
| class Decorator(SymbolNode, Statement): |
| """A decorated function. |
| |
| A single Decorator object can include any number of function decorators. |
| """ |
| |
| __slots__ = ("func", "decorators", "original_decorators", "var", "is_overload") |
| |
| __match_args__ = ("decorators", "var", "func") |
| |
| func: FuncDef # Decorated function |
| decorators: list[Expression] # Decorators (may be empty) |
| # Some decorators are removed by semanal, keep the original here. |
| original_decorators: list[Expression] |
| # TODO: This is mostly used for the type; consider replacing with a 'type' attribute |
| var: Var # Represents the decorated function obj |
| is_overload: bool |
| |
| def __init__(self, func: FuncDef, decorators: list[Expression], var: Var) -> None: |
| super().__init__() |
| self.func = func |
| self.decorators = decorators |
| self.original_decorators = decorators.copy() |
| self.var = var |
| self.is_overload = False |
| |
| @property |
| def name(self) -> str: |
| return self.func.name |
| |
| @property |
| def fullname(self) -> str: |
| return self.func.fullname |
| |
| @property |
| def is_final(self) -> bool: |
| return self.func.is_final |
| |
| @property |
| def info(self) -> TypeInfo: |
| return self.func.info |
| |
| @property |
| def type(self) -> mypy.types.Type | None: |
| return self.var.type |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_decorator(self) |
| |
| def serialize(self) -> JsonDict: |
| return { |
| ".class": "Decorator", |
| "func": self.func.serialize(), |
| "var": self.var.serialize(), |
| "is_overload": self.is_overload, |
| } |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> Decorator: |
| assert data[".class"] == "Decorator" |
| dec = Decorator(FuncDef.deserialize(data["func"]), [], Var.deserialize(data["var"])) |
| dec.is_overload = data["is_overload"] |
| return dec |
| |
| |
| VAR_FLAGS: Final = [ |
| "is_self", |
| "is_cls", |
| "is_initialized_in_class", |
| "is_staticmethod", |
| "is_classmethod", |
| "is_property", |
| "is_settable_property", |
| "is_suppressed_import", |
| "is_classvar", |
| "is_abstract_var", |
| "is_final", |
| "final_unset_in_class", |
| "final_set_in_init", |
| "explicit_self_type", |
| "is_ready", |
| "is_inferred", |
| "invalid_partial_type", |
| "from_module_getattr", |
| "has_explicit_value", |
| "allow_incompatible_override", |
| ] |
| |
| |
| class Var(SymbolNode): |
| """A variable. |
| |
| It can refer to global/local variable or a data attribute. |
| """ |
| |
| __slots__ = ( |
| "_name", |
| "_fullname", |
| "info", |
| "type", |
| "final_value", |
| "is_self", |
| "is_cls", |
| "is_ready", |
| "is_inferred", |
| "is_initialized_in_class", |
| "is_staticmethod", |
| "is_classmethod", |
| "is_property", |
| "is_settable_property", |
| "is_classvar", |
| "is_abstract_var", |
| "is_final", |
| "final_unset_in_class", |
| "final_set_in_init", |
| "is_suppressed_import", |
| "explicit_self_type", |
| "from_module_getattr", |
| "has_explicit_value", |
| "allow_incompatible_override", |
| "invalid_partial_type", |
| ) |
| |
| __match_args__ = ("name", "type", "final_value") |
| |
| def __init__(self, name: str, type: mypy.types.Type | None = None) -> None: |
| super().__init__() |
| self._name = name # Name without module prefix |
| # TODO: Should be Optional[str] |
| self._fullname = "" # Name with module prefix |
| # TODO: Should be Optional[TypeInfo] |
| self.info = VAR_NO_INFO |
| self.type: mypy.types.Type | None = type # Declared or inferred type, or None |
| # Is this the first argument to an ordinary method (usually "self")? |
| self.is_self = False |
| # Is this the first argument to a classmethod (typically "cls")? |
| self.is_cls = False |
| self.is_ready = True # If inferred, is the inferred type available? |
| self.is_inferred = self.type is None |
| # Is this initialized explicitly to a non-None value in class body? |
| self.is_initialized_in_class = False |
| self.is_staticmethod = False |
| self.is_classmethod = False |
| self.is_property = False |
| self.is_settable_property = False |
| self.is_classvar = False |
| self.is_abstract_var = False |
| # Set to true when this variable refers to a module we were unable to |
| # parse for some reason (eg a silenced module) |
| self.is_suppressed_import = False |
| # Was this "variable" (rather a constant) defined as Final[...]? |
| self.is_final = False |
| # If constant value is a simple literal, |
| # store the literal value (unboxed) for the benefit of |
| # tools like mypyc. |
| self.final_value: int | float | complex | bool | str | None = None |
| # Where the value was set (only for class attributes) |
| self.final_unset_in_class = False |
| self.final_set_in_init = False |
| # This is True for a variable that was declared on self with an explicit type: |
| # class C: |
| # def __init__(self) -> None: |
| # self.x: int |
| # This case is important because this defines a new Var, even if there is one |
| # present in a superclass (without explicit type this doesn't create a new Var). |
| # See SemanticAnalyzer.analyze_member_lvalue() for details. |
| self.explicit_self_type = False |
| # If True, this is an implicit Var created due to module-level __getattr__. |
| self.from_module_getattr = False |
| # Var can be created with an explicit value `a = 1` or without one `a: int`, |
| # we need a way to tell which one is which. |
| self.has_explicit_value = False |
| # If True, subclasses can override this with an incompatible type. |
| self.allow_incompatible_override = False |
| # If True, this means we didn't manage to infer full type and fall back to |
| # something like list[Any]. We may decide to not use such types as context. |
| self.invalid_partial_type = False |
| |
| @property |
| def name(self) -> str: |
| return self._name |
| |
| @property |
| def fullname(self) -> str: |
| return self._fullname |
| |
| def accept(self, visitor: NodeVisitor[T]) -> T: |
| return visitor.visit_var(self) |
| |
| def serialize(self) -> JsonDict: |
| # TODO: Leave default values out? |
| # NOTE: Sometimes self.is_ready is False here, but we don't care. |
| data: JsonDict = { |
| ".class": "Var", |
| "name": self._name, |
| "fullname": self._fullname, |
| "type": None if self.type is None else self.type.serialize(), |
| "flags": get_flags(self, VAR_FLAGS), |
| } |
| if self.final_value is not None: |
| data["final_value"] = self.final_value |
| return data |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> Var: |
| assert data[".class"] == "Var" |
| name = data["name"] |
| type = None if data["type"] is None else mypy.types.deserialize_type(data["type"]) |
| v = Var(name, type) |
| v.is_ready = False # Override True default set in __init__ |
| v._fullname = data["fullname"] |
| set_flags(v, data["flags"]) |
| v.final_value = data.get("final_value") |
| return v |
| |
| |
| class ClassDef(Statement): |
| """Class definition""" |
| |
| __slots__ = ( |
| "name", |
| "_fullname", |
| "defs", |
| "type_vars", |
| "base_type_exprs", |
| "removed_base_type_exprs", |
| "info", |
| "metaclass", |
| "decorators", |
| "keywords", |
| "analyzed", |
| "has_incompatible_baseclass", |
| "deco_line", |
| "removed_statements", |
| ) |
| |
| __match_args__ = ("name", "defs") |
| |
| name: str # Name of the class without module prefix |
| _fullname: str # Fully qualified name of the class |
| defs: Block |
| type_vars: list[mypy.types.TypeVarLikeType] |
| # Base class expressions (not semantically analyzed -- can be arbitrary expressions) |
| base_type_exprs: list[Expression] |
| # Special base classes like Generic[...] get moved here during semantic analysis |
| removed_base_type_exprs: list[Expression] |
| info: TypeInfo # Related TypeInfo |
| metaclass: Expression | None |
| decorators: list[Expression] |
| keywords: dict[str, Expression] |
| analyzed: Expression | None |
| has_incompatible_baseclass: bool |
| # Used by special forms like NamedTuple and TypedDict to store invalid statements |
| removed_statements: list[Statement] |
| |
| def __init__( |
| self, |
| name: str, |
| defs: Block, |
| type_vars: list[mypy.types.TypeVarLikeType] | None = None, |
| base_type_exprs: list[Expression] | None = None, |
| metaclass: Expression | None = None, |
| keywords: list[tuple[str, Expression]] | None = None, |
| ) -> None: |
| super().__init__() |
| self.name = name |
| self._fullname = "" |
| self.defs = defs |
| self.type_vars = type_vars or [] |
| self.base_type_exprs = base_type_exprs or [] |
| self.removed_base_type_exprs = [] |
| self.info = CLASSDEF_NO_INFO |
| self.metaclass = metaclass |
| self.decorators = [] |
| self.keywords = dict(keywords) if keywords else {} |
| self.analyzed = None |
| self.has_incompatible_baseclass = False |
| # Used for error reporting (to keep backwad compatibility with pre-3.8) |
| self.deco_line: int | None = None |
| self.removed_statements = [] |
| |
| @property |
| def fullname(self) -> str: |
| return self._fullname |
| |
| @fullname.setter |
| def fullname(self, v: str) -> None: |
| self._fullname = v |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_class_def(self) |
| |
| def is_generic(self) -> bool: |
| return self.info.is_generic() |
| |
| def serialize(self) -> JsonDict: |
| # Not serialized: defs, base_type_exprs, metaclass, decorators, |
| # analyzed (for named tuples etc.) |
| return { |
| ".class": "ClassDef", |
| "name": self.name, |
| "fullname": self.fullname, |
| "type_vars": [v.serialize() for v in self.type_vars], |
| } |
| |
| @classmethod |
| def deserialize(self, data: JsonDict) -> ClassDef: |
| assert data[".class"] == "ClassDef" |
| res = ClassDef( |
| data["name"], |
| Block([]), |
| # https://github.com/python/mypy/issues/12257 |
| [ |
| cast(mypy.types.TypeVarLikeType, mypy.types.deserialize_type(v)) |
| for v in data["type_vars"] |
| ], |
| ) |
| res.fullname = data["fullname"] |
| return res |
| |
| |
| class GlobalDecl(Statement): |
| """Declaration global x, y, ...""" |
| |
| __slots__ = ("names",) |
| |
| __match_args__ = ("names",) |
| |
| names: list[str] |
| |
| def __init__(self, names: list[str]) -> None: |
| super().__init__() |
| self.names = names |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_global_decl(self) |
| |
| |
| class NonlocalDecl(Statement): |
| """Declaration nonlocal x, y, ...""" |
| |
| __slots__ = ("names",) |
| |
| __match_args__ = ("names",) |
| |
| names: list[str] |
| |
| def __init__(self, names: list[str]) -> None: |
| super().__init__() |
| self.names = names |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_nonlocal_decl(self) |
| |
| |
| class Block(Statement): |
| __slots__ = ("body", "is_unreachable") |
| |
| __match_args__ = ("body", "is_unreachable") |
| |
| def __init__(self, body: list[Statement]) -> None: |
| super().__init__() |
| self.body = body |
| # True if we can determine that this block is not executed during semantic |
| # analysis. For example, this applies to blocks that are protected by |
| # something like "if PY3:" when using Python 2. However, some code is |
| # only considered unreachable during type checking and this is not true |
| # in those cases. |
| self.is_unreachable = False |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_block(self) |
| |
| |
| # Statements |
| |
| |
| class ExpressionStmt(Statement): |
| """An expression as a statement, such as print(s).""" |
| |
| __slots__ = ("expr",) |
| |
| __match_args__ = ("expr",) |
| |
| expr: Expression |
| |
| def __init__(self, expr: Expression) -> None: |
| super().__init__() |
| self.expr = expr |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_expression_stmt(self) |
| |
| |
| class AssignmentStmt(Statement): |
| """Assignment statement. |
| |
| The same node class is used for single assignment, multiple assignment |
| (e.g. x, y = z) and chained assignment (e.g. x = y = z), assignments |
| that define new names, and assignments with explicit types ("# type: t" |
| or "x: t [= ...]"). |
| |
| An lvalue can be NameExpr, TupleExpr, ListExpr, MemberExpr, or IndexExpr. |
| """ |
| |
| __slots__ = ( |
| "lvalues", |
| "rvalue", |
| "type", |
| "unanalyzed_type", |
| "new_syntax", |
| "is_alias_def", |
| "is_final_def", |
| "invalid_recursive_alias", |
| ) |
| |
| __match_args__ = ("lvalues", "rvalues", "type") |
| |
| lvalues: list[Lvalue] |
| # This is a TempNode if and only if no rvalue (x: t). |
| rvalue: Expression |
| # Declared type in a comment, may be None. |
| type: mypy.types.Type | None |
| # Original, not semantically analyzed type in annotation (used for reprocessing) |
| unanalyzed_type: mypy.types.Type | None |
| # This indicates usage of PEP 526 type annotation syntax in assignment. |
| new_syntax: bool |
| # Does this assignment define a type alias? |
| is_alias_def: bool |
| # Is this a final definition? |
| # Final attributes can't be re-assigned once set, and can't be overridden |
| # in a subclass. This flag is not set if an attempted declaration was found to |
| # be invalid during semantic analysis. It is still set to `True` if |
| # a final declaration overrides another final declaration (this is checked |
| # during type checking when MROs are known). |
| is_final_def: bool |
| # Stop further processing of this assignment, to prevent flipping back and forth |
| # during semantic analysis passes. |
| invalid_recursive_alias: bool |
| |
| def __init__( |
| self, |
| lvalues: list[Lvalue], |
| rvalue: Expression, |
| type: mypy.types.Type | None = None, |
| new_syntax: bool = False, |
| ) -> None: |
| super().__init__() |
| self.lvalues = lvalues |
| self.rvalue = rvalue |
| self.type = type |
| self.unanalyzed_type = type |
| self.new_syntax = new_syntax |
| self.is_alias_def = False |
| self.is_final_def = False |
| self.invalid_recursive_alias = False |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_assignment_stmt(self) |
| |
| |
| class OperatorAssignmentStmt(Statement): |
| """Operator assignment statement such as x += 1""" |
| |
| __slots__ = ("op", "lvalue", "rvalue") |
| |
| __match_args__ = ("lvalue", "op", "rvalue") |
| |
| op: str # TODO: Enum? |
| lvalue: Lvalue |
| rvalue: Expression |
| |
| def __init__(self, op: str, lvalue: Lvalue, rvalue: Expression) -> None: |
| super().__init__() |
| self.op = op |
| self.lvalue = lvalue |
| self.rvalue = rvalue |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_operator_assignment_stmt(self) |
| |
| |
| class WhileStmt(Statement): |
| __slots__ = ("expr", "body", "else_body") |
| |
| __match_args__ = ("expr", "body", "else_body") |
| |
| expr: Expression |
| body: Block |
| else_body: Block | None |
| |
| def __init__(self, expr: Expression, body: Block, else_body: Block | None) -> None: |
| super().__init__() |
| self.expr = expr |
| self.body = body |
| self.else_body = else_body |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_while_stmt(self) |
| |
| |
| class ForStmt(Statement): |
| __slots__ = ( |
| "index", |
| "index_type", |
| "unanalyzed_index_type", |
| "inferred_item_type", |
| "inferred_iterator_type", |
| "expr", |
| "body", |
| "else_body", |
| "is_async", |
| ) |
| |
| __match_args__ = ("index", "index_type", "expr", "body", "else_body") |
| |
| # Index variables |
| index: Lvalue |
| # Type given by type comments for index, can be None |
| index_type: mypy.types.Type | None |
| # Original, not semantically analyzed type in annotation (used for reprocessing) |
| unanalyzed_index_type: mypy.types.Type | None |
| # Inferred iterable item type |
| inferred_item_type: mypy.types.Type | None |
| # Inferred iterator type |
| inferred_iterator_type: mypy.types.Type | None |
| # Expression to iterate |
| expr: Expression |
| body: Block |
| else_body: Block | None |
| is_async: bool # True if `async for ...` (PEP 492, Python 3.5) |
| |
| def __init__( |
| self, |
| index: Lvalue, |
| expr: Expression, |
| body: Block, |
| else_body: Block | None, |
| index_type: mypy.types.Type | None = None, |
| ) -> None: |
| super().__init__() |
| self.index = index |
| self.index_type = index_type |
| self.unanalyzed_index_type = index_type |
| self.inferred_item_type = None |
| self.inferred_iterator_type = None |
| self.expr = expr |
| self.body = body |
| self.else_body = else_body |
| self.is_async = False |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_for_stmt(self) |
| |
| |
| class ReturnStmt(Statement): |
| __slots__ = ("expr",) |
| |
| __match_args__ = ("expr",) |
| |
| expr: Expression | None |
| |
| def __init__(self, expr: Expression | None) -> None: |
| super().__init__() |
| self.expr = expr |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_return_stmt(self) |
| |
| |
| class AssertStmt(Statement): |
| __slots__ = ("expr", "msg") |
| |
| __match_args__ = ("expr", "msg") |
| |
| expr: Expression |
| msg: Expression | None |
| |
| def __init__(self, expr: Expression, msg: Expression | None = None) -> None: |
| super().__init__() |
| self.expr = expr |
| self.msg = msg |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_assert_stmt(self) |
| |
| |
| class DelStmt(Statement): |
| __slots__ = ("expr",) |
| |
| __match_args__ = ("expr",) |
| |
| expr: Lvalue |
| |
| def __init__(self, expr: Lvalue) -> None: |
| super().__init__() |
| self.expr = expr |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_del_stmt(self) |
| |
| |
| class BreakStmt(Statement): |
| __slots__ = () |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_break_stmt(self) |
| |
| |
| class ContinueStmt(Statement): |
| __slots__ = () |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_continue_stmt(self) |
| |
| |
| class PassStmt(Statement): |
| __slots__ = () |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_pass_stmt(self) |
| |
| |
| class IfStmt(Statement): |
| __slots__ = ("expr", "body", "else_body") |
| |
| __match_args__ = ("expr", "body", "else_body") |
| |
| expr: list[Expression] |
| body: list[Block] |
| else_body: Block | None |
| |
| def __init__(self, expr: list[Expression], body: list[Block], else_body: Block | None) -> None: |
| super().__init__() |
| self.expr = expr |
| self.body = body |
| self.else_body = else_body |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_if_stmt(self) |
| |
| |
| class RaiseStmt(Statement): |
| __slots__ = ("expr", "from_expr") |
| |
| __match_args__ = ("expr", "from_expr") |
| |
| # Plain 'raise' is a valid statement. |
| expr: Expression | None |
| from_expr: Expression | None |
| |
| def __init__(self, expr: Expression | None, from_expr: Expression | None) -> None: |
| super().__init__() |
| self.expr = expr |
| self.from_expr = from_expr |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_raise_stmt(self) |
| |
| |
| class TryStmt(Statement): |
| __slots__ = ("body", "types", "vars", "handlers", "else_body", "finally_body", "is_star") |
| |
| __match_args__ = ("body", "types", "vars", "handlers", "else_body", "finally_body", "is_star") |
| |
| body: Block # Try body |
| # Plain 'except:' also possible |
| types: list[Expression | None] # Except type expressions |
| vars: list[NameExpr | None] # Except variable names |
| handlers: list[Block] # Except bodies |
| else_body: Block | None |
| finally_body: Block | None |
| # Whether this is try ... except* (added in Python 3.11) |
| is_star: bool |
| |
| def __init__( |
| self, |
| body: Block, |
| vars: list[NameExpr | None], |
| types: list[Expression | None], |
| handlers: list[Block], |
| else_body: Block | None, |
| finally_body: Block | None, |
| ) -> None: |
| super().__init__() |
| self.body = body |
| self.vars = vars |
| self.types = types |
| self.handlers = handlers |
| self.else_body = else_body |
| self.finally_body = finally_body |
| self.is_star = False |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_try_stmt(self) |
| |
| |
| class WithStmt(Statement): |
| __slots__ = ("expr", "target", "unanalyzed_type", "analyzed_types", "body", "is_async") |
| |
| __match_args__ = ("expr", "target", "body") |
| |
| expr: list[Expression] |
| target: list[Lvalue | None] |
| # Type given by type comments for target, can be None |
| unanalyzed_type: mypy.types.Type | None |
| # Semantically analyzed types from type comment (TypeList type expanded) |
| analyzed_types: list[mypy.types.Type] |
| body: Block |
| is_async: bool # True if `async with ...` (PEP 492, Python 3.5) |
| |
| def __init__( |
| self, |
| expr: list[Expression], |
| target: list[Lvalue | None], |
| body: Block, |
| target_type: mypy.types.Type | None = None, |
| ) -> None: |
| super().__init__() |
| self.expr = expr |
| self.target = target |
| self.unanalyzed_type = target_type |
| self.analyzed_types = [] |
| self.body = body |
| self.is_async = False |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_with_stmt(self) |
| |
| |
| class MatchStmt(Statement): |
| __slots__ = ("subject", "patterns", "guards", "bodies") |
| |
| __match_args__ = ("subject", "patterns", "guards", "bodies") |
| |
| subject: Expression |
| patterns: list[Pattern] |
| guards: list[Expression | None] |
| bodies: list[Block] |
| |
| def __init__( |
| self, |
| subject: Expression, |
| patterns: list[Pattern], |
| guards: list[Expression | None], |
| bodies: list[Block], |
| ) -> None: |
| super().__init__() |
| assert len(patterns) == len(guards) == len(bodies) |
| self.subject = subject |
| self.patterns = patterns |
| self.guards = guards |
| self.bodies = bodies |
| |
| def accept(self, visitor: StatementVisitor[T]) -> T: |
| return visitor.visit_match_stmt(self) |
| |
| |
| # Expressions |
| |
| |
| class IntExpr(Expression): |
| """Integer literal""" |
| |
| __slots__ = ("value",) |
| |
| __match_args__ = ("value",) |
| |
| value: int # 0 by default |
| |
| def __init__(self, value: int) -> None: |
| super().__init__() |
| self.value = value |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_int_expr(self) |
| |
| |
| # How mypy uses StrExpr and BytesExpr: |
| # |
| # b'x' -> BytesExpr |
| # 'x', u'x' -> StrExpr |
| |
| |
| class StrExpr(Expression): |
| """String literal""" |
| |
| __slots__ = ("value",) |
| |
| __match_args__ = ("value",) |
| |
| value: str # '' by default |
| |
| def __init__(self, value: str) -> None: |
| super().__init__() |
| self.value = value |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_str_expr(self) |
| |
| |
| def is_StrExpr_list(seq: list[Expression]) -> TypeGuard[list[StrExpr]]: |
| return all(isinstance(item, StrExpr) for item in seq) |
| |
| |
| class BytesExpr(Expression): |
| """Bytes literal""" |
| |
| __slots__ = ("value",) |
| |
| __match_args__ = ("value",) |
| |
| # Note: we deliberately do NOT use bytes here because it ends up |
| # unnecessarily complicating a lot of the result logic. For example, |
| # we'd have to worry about converting the bytes into a format we can |
| # easily serialize/deserialize to and from JSON, would have to worry |
| # about turning the bytes into a human-readable representation in |
| # error messages... |
| # |
| # It's more convenient to just store the human-readable representation |
| # from the very start. |
| value: str |
| |
| def __init__(self, value: str) -> None: |
| super().__init__() |
| self.value = value |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_bytes_expr(self) |
| |
| |
| class FloatExpr(Expression): |
| """Float literal""" |
| |
| __slots__ = ("value",) |
| |
| __match_args__ = ("value",) |
| |
| value: float # 0.0 by default |
| |
| def __init__(self, value: float) -> None: |
| super().__init__() |
| self.value = value |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_float_expr(self) |
| |
| |
| class ComplexExpr(Expression): |
| """Complex literal""" |
| |
| __slots__ = ("value",) |
| |
| __match_args__ = ("value",) |
| |
| value: complex |
| |
| def __init__(self, value: complex) -> None: |
| super().__init__() |
| self.value = value |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_complex_expr(self) |
| |
| |
| class EllipsisExpr(Expression): |
| """Ellipsis (...)""" |
| |
| __slots__ = () |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_ellipsis(self) |
| |
| |
| class StarExpr(Expression): |
| """Star expression""" |
| |
| __slots__ = ("expr", "valid") |
| |
| __match_args__ = ("expr", "valid") |
| |
| expr: Expression |
| valid: bool |
| |
| def __init__(self, expr: Expression) -> None: |
| super().__init__() |
| self.expr = expr |
| |
| # Whether this starred expression is used in a tuple/list and as lvalue |
| self.valid = False |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_star_expr(self) |
| |
| |
| class RefExpr(Expression): |
| """Abstract base class for name-like constructs""" |
| |
| __slots__ = ( |
| "kind", |
| "node", |
| "_fullname", |
| "is_new_def", |
| "is_inferred_def", |
| "is_alias_rvalue", |
| "type_guard", |
| ) |
| |
| def __init__(self) -> None: |
| super().__init__() |
| # LDEF/GDEF/MDEF/... (None if not available) |
| self.kind: int | None = None |
| # Var, FuncDef or TypeInfo that describes this |
| self.node: SymbolNode | None = None |
| # Fully qualified name (or name if not global) |
| self._fullname = "" |
| # Does this define a new name? |
| self.is_new_def = False |
| # Does this define a new name with inferred type? |
| # |
| # For members, after semantic analysis, this does not take base |
| # classes into consideration at all; the type checker deals with these. |
| self.is_inferred_def = False |
| # Is this expression appears as an rvalue of a valid type alias definition? |
| self.is_alias_rvalue = False |
| # Cache type guard from callable_type.type_guard |
| self.type_guard: mypy.types.Type | None = None |
| |
| @property |
| def fullname(self) -> str: |
| return self._fullname |
| |
| @fullname.setter |
| def fullname(self, v: str) -> None: |
| self._fullname = v |
| |
| |
| class NameExpr(RefExpr): |
| """Name expression |
| |
| This refers to a local name, global name or a module. |
| """ |
| |
| __slots__ = ("name", "is_special_form") |
| |
| __match_args__ = ("name", "node") |
| |
| def __init__(self, name: str) -> None: |
| super().__init__() |
| self.name = name # Name referred to |
| # Is this a l.h.s. of a special form assignment like typed dict or type variable? |
| self.is_special_form = False |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_name_expr(self) |
| |
| def serialize(self) -> JsonDict: |
| assert False, f"Serializing NameExpr: {self}" |
| |
| |
| class MemberExpr(RefExpr): |
| """Member access expression x.y""" |
| |
| __slots__ = ("expr", "name", "def_var") |
| |
| __match_args__ = ("expr", "name", "node") |
| |
| def __init__(self, expr: Expression, name: str) -> None: |
| super().__init__() |
| self.expr = expr |
| self.name = name |
| # The variable node related to a definition through 'self.x = <initializer>'. |
| # The nodes of other kinds of member expressions are resolved during type checking. |
| self.def_var: Var | None = None |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_member_expr(self) |
| |
| |
| # Kinds of arguments |
| @unique |
| class ArgKind(Enum): |
| # Positional argument |
| ARG_POS = 0 |
| # Positional, optional argument (functions only, not calls) |
| ARG_OPT = 1 |
| # *arg argument |
| ARG_STAR = 2 |
| # Keyword argument x=y in call, or keyword-only function arg |
| ARG_NAMED = 3 |
| # **arg argument |
| ARG_STAR2 = 4 |
| # In an argument list, keyword-only and also optional |
| ARG_NAMED_OPT = 5 |
| |
| def is_positional(self, star: bool = False) -> bool: |
| return self == ARG_POS or self == ARG_OPT or (star and self == ARG_STAR) |
| |
| def is_named(self, star: bool = False) -> bool: |
| return self == ARG_NAMED or self == ARG_NAMED_OPT or (star and self == ARG_STAR2) |
| |
| def is_required(self) -> bool: |
| return self == ARG_POS or self == ARG_NAMED |
| |
| def is_optional(self) -> bool: |
| return self == ARG_OPT or self == ARG_NAMED_OPT |
| |
| def is_star(self) -> bool: |
| return self == ARG_STAR or self == ARG_STAR2 |
| |
| |
| ARG_POS: Final = ArgKind.ARG_POS |
| ARG_OPT: Final = ArgKind.ARG_OPT |
| ARG_STAR: Final = ArgKind.ARG_STAR |
| ARG_NAMED: Final = ArgKind.ARG_NAMED |
| ARG_STAR2: Final = ArgKind.ARG_STAR2 |
| ARG_NAMED_OPT: Final = ArgKind.ARG_NAMED_OPT |
| |
| |
| class CallExpr(Expression): |
| """Call expression. |
| |
| This can also represent several special forms that are syntactically calls |
| such as cast(...) and None # type: .... |
| """ |
| |
| __slots__ = ("callee", "args", "arg_kinds", "arg_names", "analyzed") |
| |
| __match_args__ = ("callee", "args", "arg_kinds", "arg_names") |
| |
| def __init__( |
| self, |
| callee: Expression, |
| args: list[Expression], |
| arg_kinds: list[ArgKind], |
| arg_names: list[str | None], |
| analyzed: Expression | None = None, |
| ) -> None: |
| super().__init__() |
| if not arg_names: |
| arg_names = [None] * len(args) |
| |
| self.callee = callee |
| self.args = args |
| self.arg_kinds = arg_kinds # ARG_ constants |
| # Each name can be None if not a keyword argument. |
| self.arg_names: list[str | None] = arg_names |
| # If not None, the node that represents the meaning of the CallExpr. For |
| # cast(...) this is a CastExpr. |
| self.analyzed = analyzed |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_call_expr(self) |
| |
| |
| class YieldFromExpr(Expression): |
| __slots__ = ("expr",) |
| |
| __match_args__ = ("expr",) |
| |
| expr: Expression |
| |
| def __init__(self, expr: Expression) -> None: |
| super().__init__() |
| self.expr = expr |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_yield_from_expr(self) |
| |
| |
| class YieldExpr(Expression): |
| __slots__ = ("expr",) |
| |
| __match_args__ = ("expr",) |
| |
| expr: Expression | None |
| |
| def __init__(self, expr: Expression | None) -> None: |
| super().__init__() |
| self.expr = expr |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_yield_expr(self) |
| |
| |
| class IndexExpr(Expression): |
| """Index expression x[y]. |
| |
| Also wraps type application such as List[int] as a special form. |
| """ |
| |
| __slots__ = ("base", "index", "method_type", "analyzed") |
| |
| __match_args__ = ("base", "index") |
| |
| base: Expression |
| index: Expression |
| # Inferred __getitem__ method type |
| method_type: mypy.types.Type | None |
| # If not None, this is actually semantically a type application |
| # Class[type, ...] or a type alias initializer. |
| analyzed: TypeApplication | TypeAliasExpr | None |
| |
| def __init__(self, base: Expression, index: Expression) -> None: |
| super().__init__() |
| self.base = base |
| self.index = index |
| self.method_type = None |
| self.analyzed = None |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_index_expr(self) |
| |
| |
| class UnaryExpr(Expression): |
| """Unary operation""" |
| |
| __slots__ = ("op", "expr", "method_type") |
| |
| __match_args__ = ("op", "expr") |
| |
| op: str # TODO: Enum? |
| expr: Expression |
| # Inferred operator method type |
| method_type: mypy.types.Type | None |
| |
| def __init__(self, op: str, expr: Expression) -> None: |
| super().__init__() |
| self.op = op |
| self.expr = expr |
| self.method_type = None |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_unary_expr(self) |
| |
| |
| class AssignmentExpr(Expression): |
| """Assignment expressions in Python 3.8+, like "a := 2".""" |
| |
| __slots__ = ("target", "value") |
| |
| __match_args__ = ("target", "value") |
| |
| def __init__(self, target: Expression, value: Expression) -> None: |
| super().__init__() |
| self.target = target |
| self.value = value |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_assignment_expr(self) |
| |
| |
| class OpExpr(Expression): |
| """Binary operation. |
| |
| The dot (.), [] and comparison operators have more specific nodes. |
| """ |
| |
| __slots__ = ( |
| "op", |
| "left", |
| "right", |
| "method_type", |
| "right_always", |
| "right_unreachable", |
| "analyzed", |
| ) |
| |
| __match_args__ = ("left", "op", "right") |
| |
| op: str # TODO: Enum? |
| left: Expression |
| right: Expression |
| # Inferred type for the operator method type (when relevant). |
| method_type: mypy.types.Type | None |
| # Per static analysis only: Is the right side going to be evaluated every time? |
| right_always: bool |
| # Per static analysis only: Is the right side unreachable? |
| right_unreachable: bool |
| # Used for expressions that represent a type "X | Y" in some contexts |
| analyzed: TypeAliasExpr | None |
| |
| def __init__( |
| self, op: str, left: Expression, right: Expression, analyzed: TypeAliasExpr | None = None |
| ) -> None: |
| super().__init__() |
| self.op = op |
| self.left = left |
| self.right = right |
| self.method_type = None |
| self.right_always = False |
| self.right_unreachable = False |
| self.analyzed = analyzed |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_op_expr(self) |
| |
| |
| class ComparisonExpr(Expression): |
| """Comparison expression (e.g. a < b > c < d).""" |
| |
| __slots__ = ("operators", "operands", "method_types") |
| |
| __match_args__ = ("operands", "operators") |
| |
| operators: list[str] |
| operands: list[Expression] |
| # Inferred type for the operator methods (when relevant; None for 'is'). |
| method_types: list[mypy.types.Type | None] |
| |
| def __init__(self, operators: list[str], operands: list[Expression]) -> None: |
| super().__init__() |
| self.operators = operators |
| self.operands = operands |
| self.method_types = [] |
| |
| def pairwise(self) -> Iterator[tuple[str, Expression, Expression]]: |
| """If this comparison expr is "a < b is c == d", yields the sequence |
| ("<", a, b), ("is", b, c), ("==", c, d) |
| """ |
| for i, operator in enumerate(self.operators): |
| yield operator, self.operands[i], self.operands[i + 1] |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_comparison_expr(self) |
| |
| |
| class SliceExpr(Expression): |
| """Slice expression (e.g. 'x:y', 'x:', '::2' or ':'). |
| |
| This is only valid as index in index expressions. |
| """ |
| |
| __slots__ = ("begin_index", "end_index", "stride") |
| |
| __match_args__ = ("begin_index", "end_index", "stride") |
| |
| begin_index: Expression | None |
| end_index: Expression | None |
| stride: Expression | None |
| |
| def __init__( |
| self, |
| begin_index: Expression | None, |
| end_index: Expression | None, |
| stride: Expression | None, |
| ) -> None: |
| super().__init__() |
| self.begin_index = begin_index |
| self.end_index = end_index |
| self.stride = stride |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_slice_expr(self) |
| |
| |
| class CastExpr(Expression): |
| """Cast expression cast(type, expr).""" |
| |
| __slots__ = ("expr", "type") |
| |
| __match_args__ = ("expr", "type") |
| |
| expr: Expression |
| type: mypy.types.Type |
| |
| def __init__(self, expr: Expression, typ: mypy.types.Type) -> None: |
| super().__init__() |
| self.expr = expr |
| self.type = typ |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_cast_expr(self) |
| |
| |
| class AssertTypeExpr(Expression): |
| """Represents a typing.assert_type(expr, type) call.""" |
| |
| __slots__ = ("expr", "type") |
| |
| __match_args__ = ("expr", "type") |
| |
| expr: Expression |
| type: mypy.types.Type |
| |
| def __init__(self, expr: Expression, typ: mypy.types.Type) -> None: |
| super().__init__() |
| self.expr = expr |
| self.type = typ |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_assert_type_expr(self) |
| |
| |
| class RevealExpr(Expression): |
| """Reveal type expression reveal_type(expr) or reveal_locals() expression.""" |
| |
| __slots__ = ("expr", "kind", "local_nodes") |
| |
| __match_args__ = ("expr", "kind", "local_nodes") |
| |
| expr: Expression | None |
| kind: int |
| local_nodes: list[Var] | None |
| |
| def __init__( |
| self, kind: int, expr: Expression | None = None, local_nodes: list[Var] | None = None |
| ) -> None: |
| super().__init__() |
| self.expr = expr |
| self.kind = kind |
| self.local_nodes = local_nodes |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_reveal_expr(self) |
| |
| |
| class SuperExpr(Expression): |
| """Expression super().name""" |
| |
| __slots__ = ("name", "info", "call") |
| |
| __match_args__ = ("name", "call", "info") |
| |
| name: str |
| info: TypeInfo | None # Type that contains this super expression |
| call: CallExpr # The expression super(...) |
| |
| def __init__(self, name: str, call: CallExpr) -> None: |
| super().__init__() |
| self.name = name |
| self.call = call |
| self.info = None |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_super_expr(self) |
| |
| |
| class LambdaExpr(FuncItem, Expression): |
| """Lambda expression""" |
| |
| __match_args__ = ("arguments", "arg_names", "arg_kinds", "body") |
| |
| @property |
| def name(self) -> str: |
| return "<lambda>" |
| |
| def expr(self) -> Expression: |
| """Return the expression (the body) of the lambda.""" |
| ret = self.body.body[-1] |
| assert isinstance(ret, ReturnStmt) |
| expr = ret.expr |
| assert expr is not None # lambda can't have empty body |
| return expr |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_lambda_expr(self) |
| |
| def is_dynamic(self) -> bool: |
| return False |
| |
| |
| class ListExpr(Expression): |
| """List literal expression [...].""" |
| |
| __slots__ = ("items",) |
| |
| __match_args__ = ("items",) |
| |
| items: list[Expression] |
| |
| def __init__(self, items: list[Expression]) -> None: |
| super().__init__() |
| self.items = items |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_list_expr(self) |
| |
| |
| class DictExpr(Expression): |
| """Dictionary literal expression {key: value, ...}.""" |
| |
| __slots__ = ("items",) |
| |
| __match_args__ = ("items",) |
| |
| items: list[tuple[Expression | None, Expression]] |
| |
| def __init__(self, items: list[tuple[Expression | None, Expression]]) -> None: |
| super().__init__() |
| self.items = items |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_dict_expr(self) |
| |
| |
| class TupleExpr(Expression): |
| """Tuple literal expression (..., ...) |
| |
| Also lvalue sequences (..., ...) and [..., ...]""" |
| |
| __slots__ = ("items",) |
| |
| __match_args__ = ("items",) |
| |
| items: list[Expression] |
| |
| def __init__(self, items: list[Expression]) -> None: |
| super().__init__() |
| self.items = items |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_tuple_expr(self) |
| |
| |
| class SetExpr(Expression): |
| """Set literal expression {value, ...}.""" |
| |
| __slots__ = ("items",) |
| |
| __match_args__ = ("items",) |
| |
| items: list[Expression] |
| |
| def __init__(self, items: list[Expression]) -> None: |
| super().__init__() |
| self.items = items |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_set_expr(self) |
| |
| |
| class GeneratorExpr(Expression): |
| """Generator expression ... for ... in ... [ for ... in ... ] [ if ... ].""" |
| |
| __slots__ = ("left_expr", "sequences", "condlists", "is_async", "indices") |
| |
| __match_args__ = ("left_expr", "indices", "sequences", "condlists") |
| |
| left_expr: Expression |
| sequences: list[Expression] |
| condlists: list[list[Expression]] |
| is_async: list[bool] |
| indices: list[Lvalue] |
| |
| def __init__( |
| self, |
| left_expr: Expression, |
| indices: list[Lvalue], |
| sequences: list[Expression], |
| condlists: list[list[Expression]], |
| is_async: list[bool], |
| ) -> None: |
| super().__init__() |
| self.left_expr = left_expr |
| self.sequences = sequences |
| self.condlists = condlists |
| self.indices = indices |
| self.is_async = is_async |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_generator_expr(self) |
| |
| |
| class ListComprehension(Expression): |
| """List comprehension (e.g. [x + 1 for x in a])""" |
| |
| __slots__ = ("generator",) |
| |
| __match_args__ = ("generator",) |
| |
| generator: GeneratorExpr |
| |
| def __init__(self, generator: GeneratorExpr) -> None: |
| super().__init__() |
| self.generator = generator |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_list_comprehension(self) |
| |
| |
| class SetComprehension(Expression): |
| """Set comprehension (e.g. {x + 1 for x in a})""" |
| |
| __slots__ = ("generator",) |
| |
| __match_args__ = ("generator",) |
| |
| generator: GeneratorExpr |
| |
| def __init__(self, generator: GeneratorExpr) -> None: |
| super().__init__() |
| self.generator = generator |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_set_comprehension(self) |
| |
| |
| class DictionaryComprehension(Expression): |
| """Dictionary comprehension (e.g. {k: v for k, v in a}""" |
| |
| __slots__ = ("key", "value", "sequences", "condlists", "is_async", "indices") |
| |
| __match_args__ = ("key", "value", "indices", "sequences", "condlists") |
| |
| key: Expression |
| value: Expression |
| sequences: list[Expression] |
| condlists: list[list[Expression]] |
| is_async: list[bool] |
| indices: list[Lvalue] |
| |
| def __init__( |
| self, |
| key: Expression, |
| value: Expression, |
| indices: list[Lvalue], |
| sequences: list[Expression], |
| condlists: list[list[Expression]], |
| is_async: list[bool], |
| ) -> None: |
| super().__init__() |
| self.key = key |
| self.value = value |
| self.sequences = sequences |
| self.condlists = condlists |
| self.indices = indices |
| self.is_async = is_async |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_dictionary_comprehension(self) |
| |
| |
| class ConditionalExpr(Expression): |
| """Conditional expression (e.g. x if y else z)""" |
| |
| __slots__ = ("cond", "if_expr", "else_expr") |
| |
| __match_args__ = ("if_expr", "cond", "else_expr") |
| |
| cond: Expression |
| if_expr: Expression |
| else_expr: Expression |
| |
| def __init__(self, cond: Expression, if_expr: Expression, else_expr: Expression) -> None: |
| super().__init__() |
| self.cond = cond |
| self.if_expr = if_expr |
| self.else_expr = else_expr |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_conditional_expr(self) |
| |
| |
| class TypeApplication(Expression): |
| """Type application expr[type, ...]""" |
| |
| __slots__ = ("expr", "types") |
| |
| __match_args__ = ("expr", "types") |
| |
| expr: Expression |
| types: list[mypy.types.Type] |
| |
| def __init__(self, expr: Expression, types: list[mypy.types.Type]) -> None: |
| super().__init__() |
| self.expr = expr |
| self.types = types |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_type_application(self) |
| |
| |
| # Variance of a type variable. For example, T in the definition of |
| # List[T] is invariant, so List[int] is not a subtype of List[object], |
| # and also List[object] is not a subtype of List[int]. |
| # |
| # The T in Iterable[T] is covariant, so Iterable[int] is a subtype of |
| # Iterable[object], but not vice versa. |
| # |
| # If T is contravariant in Foo[T], Foo[object] is a subtype of |
| # Foo[int], but not vice versa. |
| INVARIANT: Final = 0 |
| COVARIANT: Final = 1 |
| CONTRAVARIANT: Final = 2 |
| |
| |
| class TypeVarLikeExpr(SymbolNode, Expression): |
| """Base class for TypeVarExpr, ParamSpecExpr and TypeVarTupleExpr. |
| |
| Note that they are constructed by the semantic analyzer. |
| """ |
| |
| __slots__ = ("_name", "_fullname", "upper_bound", "default", "variance") |
| |
| _name: str |
| _fullname: str |
| # Upper bound: only subtypes of upper_bound are valid as values. By default |
| # this is 'object', meaning no restriction. |
| upper_bound: mypy.types.Type |
| # Default: used to resolve the TypeVar if the default is not explicitly given. |
| # By default this is 'AnyType(TypeOfAny.from_omitted_generics)'. See PEP 696. |
| default: mypy.types.Type |
| # Variance of the type variable. Invariant is the default. |
| # TypeVar(..., covariant=True) defines a covariant type variable. |
| # TypeVar(..., contravariant=True) defines a contravariant type |
| # variable. |
| variance: int |
| |
| def __init__( |
| self, |
| name: str, |
| fullname: str, |
| upper_bound: mypy.types.Type, |
| default: mypy.types.Type, |
| variance: int = INVARIANT, |
| ) -> None: |
| super().__init__() |
| self._name = name |
| self._fullname = fullname |
| self.upper_bound = upper_bound |
| self.default = default |
| self.variance = variance |
| |
| @property |
| def name(self) -> str: |
| return self._name |
| |
| @property |
| def fullname(self) -> str: |
| return self._fullname |
| |
| |
| class TypeVarExpr(TypeVarLikeExpr): |
| """Type variable expression TypeVar(...). |
| |
| This is also used to represent type variables in symbol tables. |
| |
| A type variable is not valid as a type unless bound in a TypeVarLikeScope. |
| That happens within: |
| |
| 1. a generic class that uses the type variable as a type argument or |
| 2. a generic function that refers to the type variable in its signature. |
| """ |
| |
| __slots__ = ("values",) |
| |
| __match_args__ = ("name", "values", "upper_bound", "default") |
| |
| # Value restriction: only types in the list are valid as values. If the |
| # list is empty, there is no restriction. |
| values: list[mypy.types.Type] |
| |
| def __init__( |
| self, |
| name: str, |
| fullname: str, |
| values: list[mypy.types.Type], |
| upper_bound: mypy.types.Type, |
| default: mypy.types.Type, |
| variance: int = INVARIANT, |
| ) -> None: |
| super().__init__(name, fullname, upper_bound, default, variance) |
| self.values = values |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_type_var_expr(self) |
| |
| def serialize(self) -> JsonDict: |
| return { |
| ".class": "TypeVarExpr", |
| "name": self._name, |
| "fullname": self._fullname, |
| "values": [t.serialize() for t in self.values], |
| "upper_bound": self.upper_bound.serialize(), |
| "default": self.default.serialize(), |
| "variance": self.variance, |
| } |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> TypeVarExpr: |
| assert data[".class"] == "TypeVarExpr" |
| return TypeVarExpr( |
| data["name"], |
| data["fullname"], |
| [mypy.types.deserialize_type(v) for v in data["values"]], |
| mypy.types.deserialize_type(data["upper_bound"]), |
| mypy.types.deserialize_type(data["default"]), |
| data["variance"], |
| ) |
| |
| |
| class ParamSpecExpr(TypeVarLikeExpr): |
| __slots__ = () |
| |
| __match_args__ = ("name", "upper_bound", "default") |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_paramspec_expr(self) |
| |
| def serialize(self) -> JsonDict: |
| return { |
| ".class": "ParamSpecExpr", |
| "name": self._name, |
| "fullname": self._fullname, |
| "upper_bound": self.upper_bound.serialize(), |
| "default": self.default.serialize(), |
| "variance": self.variance, |
| } |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> ParamSpecExpr: |
| assert data[".class"] == "ParamSpecExpr" |
| return ParamSpecExpr( |
| data["name"], |
| data["fullname"], |
| mypy.types.deserialize_type(data["upper_bound"]), |
| mypy.types.deserialize_type(data["default"]), |
| data["variance"], |
| ) |
| |
| |
| class TypeVarTupleExpr(TypeVarLikeExpr): |
| """Type variable tuple expression TypeVarTuple(...).""" |
| |
| __slots__ = "tuple_fallback" |
| |
| tuple_fallback: mypy.types.Instance |
| |
| __match_args__ = ("name", "upper_bound", "default") |
| |
| def __init__( |
| self, |
| name: str, |
| fullname: str, |
| upper_bound: mypy.types.Type, |
| tuple_fallback: mypy.types.Instance, |
| default: mypy.types.Type, |
| variance: int = INVARIANT, |
| ) -> None: |
| super().__init__(name, fullname, upper_bound, default, variance) |
| self.tuple_fallback = tuple_fallback |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_type_var_tuple_expr(self) |
| |
| def serialize(self) -> JsonDict: |
| return { |
| ".class": "TypeVarTupleExpr", |
| "name": self._name, |
| "fullname": self._fullname, |
| "upper_bound": self.upper_bound.serialize(), |
| "tuple_fallback": self.tuple_fallback.serialize(), |
| "default": self.default.serialize(), |
| "variance": self.variance, |
| } |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> TypeVarTupleExpr: |
| assert data[".class"] == "TypeVarTupleExpr" |
| return TypeVarTupleExpr( |
| data["name"], |
| data["fullname"], |
| mypy.types.deserialize_type(data["upper_bound"]), |
| mypy.types.Instance.deserialize(data["tuple_fallback"]), |
| mypy.types.deserialize_type(data["default"]), |
| data["variance"], |
| ) |
| |
| |
| class TypeAliasExpr(Expression): |
| """Type alias expression (rvalue).""" |
| |
| __slots__ = ("type", "tvars", "no_args", "node") |
| |
| __match_args__ = ("type", "tvars", "no_args", "node") |
| |
| # The target type. |
| type: mypy.types.Type |
| # Names of type variables used to define the alias |
| tvars: list[str] |
| # Whether this alias was defined in bare form. Used to distinguish |
| # between |
| # A = List |
| # and |
| # A = List[Any] |
| no_args: bool |
| node: TypeAlias |
| |
| def __init__(self, node: TypeAlias) -> None: |
| super().__init__() |
| self.type = node.target |
| self.tvars = [v.name for v in node.alias_tvars] |
| self.no_args = node.no_args |
| self.node = node |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_type_alias_expr(self) |
| |
| |
| class NamedTupleExpr(Expression): |
| """Named tuple expression namedtuple(...) or NamedTuple(...).""" |
| |
| __slots__ = ("info", "is_typed") |
| |
| __match_args__ = ("info",) |
| |
| # The class representation of this named tuple (its tuple_type attribute contains |
| # the tuple item types) |
| info: TypeInfo |
| is_typed: bool # whether this class was created with typing(_extensions).NamedTuple |
| |
| def __init__(self, info: TypeInfo, is_typed: bool = False) -> None: |
| super().__init__() |
| self.info = info |
| self.is_typed = is_typed |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_namedtuple_expr(self) |
| |
| |
| class TypedDictExpr(Expression): |
| """Typed dict expression TypedDict(...).""" |
| |
| __slots__ = ("info",) |
| |
| __match_args__ = ("info",) |
| |
| # The class representation of this typed dict |
| info: TypeInfo |
| |
| def __init__(self, info: TypeInfo) -> None: |
| super().__init__() |
| self.info = info |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_typeddict_expr(self) |
| |
| |
| class EnumCallExpr(Expression): |
| """Named tuple expression Enum('name', 'val1 val2 ...').""" |
| |
| __slots__ = ("info", "items", "values") |
| |
| __match_args__ = ("info", "items", "values") |
| |
| # The class representation of this enumerated type |
| info: TypeInfo |
| # The item names (for debugging) |
| items: list[str] |
| values: list[Expression | None] |
| |
| def __init__(self, info: TypeInfo, items: list[str], values: list[Expression | None]) -> None: |
| super().__init__() |
| self.info = info |
| self.items = items |
| self.values = values |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_enum_call_expr(self) |
| |
| |
| class PromoteExpr(Expression): |
| """Ducktype class decorator expression _promote(...).""" |
| |
| __slots__ = ("type",) |
| |
| type: mypy.types.ProperType |
| |
| def __init__(self, type: mypy.types.ProperType) -> None: |
| super().__init__() |
| self.type = type |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit__promote_expr(self) |
| |
| |
| class NewTypeExpr(Expression): |
| """NewType expression NewType(...).""" |
| |
| __slots__ = ("name", "old_type", "info") |
| |
| __match_args__ = ("name", "old_type", "info") |
| |
| name: str |
| # The base type (the second argument to NewType) |
| old_type: mypy.types.Type | None |
| # The synthesized class representing the new type (inherits old_type) |
| info: TypeInfo | None |
| |
| def __init__( |
| self, name: str, old_type: mypy.types.Type | None, line: int, column: int |
| ) -> None: |
| super().__init__(line=line, column=column) |
| self.name = name |
| self.old_type = old_type |
| self.info = None |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_newtype_expr(self) |
| |
| |
| class AwaitExpr(Expression): |
| """Await expression (await ...).""" |
| |
| __slots__ = ("expr",) |
| |
| __match_args__ = ("expr",) |
| |
| expr: Expression |
| |
| def __init__(self, expr: Expression) -> None: |
| super().__init__() |
| self.expr = expr |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_await_expr(self) |
| |
| |
| # Constants |
| |
| |
| class TempNode(Expression): |
| """Temporary dummy node used during type checking. |
| |
| This node is not present in the original program; it is just an artifact |
| of the type checker implementation. It only represents an opaque node with |
| some fixed type. |
| """ |
| |
| __slots__ = ("type", "no_rhs") |
| |
| type: mypy.types.Type |
| # Is this TempNode used to indicate absence of a right hand side in an annotated assignment? |
| # (e.g. for 'x: int' the rvalue is TempNode(AnyType(TypeOfAny.special_form), no_rhs=True)) |
| no_rhs: bool |
| |
| def __init__( |
| self, typ: mypy.types.Type, no_rhs: bool = False, *, context: Context | None = None |
| ) -> None: |
| """Construct a dummy node; optionally borrow line/column from context object.""" |
| super().__init__() |
| self.type = typ |
| self.no_rhs = no_rhs |
| if context is not None: |
| self.line = context.line |
| self.column = context.column |
| |
| def __repr__(self) -> str: |
| return "TempNode:%d(%s)" % (self.line, str(self.type)) |
| |
| def accept(self, visitor: ExpressionVisitor[T]) -> T: |
| return visitor.visit_temp_node(self) |
| |
| |
| # Special attributes not collected as protocol members by Python 3.12 |
| # See typing._SPECIAL_NAMES |
| EXCLUDED_PROTOCOL_ATTRIBUTES: Final = frozenset( |
| { |
| "__abstractmethods__", |
| "__annotations__", |
| "__dict__", |
| "__doc__", |
| "__init__", |
| "__module__", |
| "__new__", |
| "__slots__", |
| "__subclasshook__", |
| "__weakref__", |
| "__class_getitem__", # Since Python 3.9 |
| } |
| ) |
| |
| |
| class TypeInfo(SymbolNode): |
| """The type structure of a single class. |
| |
| Each TypeInfo corresponds one-to-one to a ClassDef, which |
| represents the AST of the class. |
| |
| In type-theory terms, this is a "type constructor", and if the |
| class is generic then it will be a type constructor of higher kind. |
| Where the class is used in an actual type, it's in the form of an |
| Instance, which amounts to a type application of the tycon to |
| the appropriate number of arguments. |
| """ |
| |
| __slots__ = ( |
| "_fullname", |
| "module_name", |
| "defn", |
| "mro", |
| "_mro_refs", |
| "bad_mro", |
| "is_final", |
| "declared_metaclass", |
| "metaclass_type", |
| "names", |
| "is_abstract", |
| "is_protocol", |
| "runtime_protocol", |
| "abstract_attributes", |
| "deletable_attributes", |
| "slots", |
| "assuming", |
| "assuming_proper", |
| "inferring", |
| "is_enum", |
| "fallback_to_any", |
| "meta_fallback_to_any", |
| "type_vars", |
| "has_param_spec_type", |
| "bases", |
| "_promote", |
| "tuple_type", |
| "special_alias", |
| "is_named_tuple", |
| "typeddict_type", |
| "is_newtype", |
| "is_intersection", |
| "metadata", |
| "alt_promote", |
| "has_type_var_tuple_type", |
| "type_var_tuple_prefix", |
| "type_var_tuple_suffix", |
| "self_type", |
| "dataclass_transform_spec", |
| ) |
| |
| _fullname: str # Fully qualified name |
| # Fully qualified name for the module this type was defined in. This |
| # information is also in the fullname, but is harder to extract in the |
| # case of nested class definitions. |
| module_name: str |
| defn: ClassDef # Corresponding ClassDef |
| # Method Resolution Order: the order of looking up attributes. The first |
| # value always to refers to this class. |
| mro: list[TypeInfo] |
| # Used to stash the names of the mro classes temporarily between |
| # deserialization and fixup. See deserialize() for why. |
| _mro_refs: list[str] | None |
| bad_mro: bool # Could not construct full MRO |
| is_final: bool |
| |
| declared_metaclass: mypy.types.Instance | None |
| metaclass_type: mypy.types.Instance | None |
| |
| names: SymbolTable # Names defined directly in this type |
| is_abstract: bool # Does the class have any abstract attributes? |
| is_protocol: bool # Is this a protocol class? |
| runtime_protocol: bool # Does this protocol support isinstance checks? |
| # List of names of abstract attributes together with their abstract status. |
| # The abstract status must be one of `NOT_ABSTRACT`, `IS_ABSTRACT`, `IMPLICITLY_ABSTRACT`. |
| abstract_attributes: list[tuple[str, int]] |
| deletable_attributes: list[str] # Used by mypyc only |
| # Does this type have concrete `__slots__` defined? |
| # If class does not have `__slots__` defined then it is `None`, |
| # if it has empty `__slots__` then it is an empty set. |
| slots: set[str] | None |
| |
| # The attributes 'assuming' and 'assuming_proper' represent structural subtype matrices. |
| # |
| # In languages with structural subtyping, one can keep a global subtype matrix like this: |
| # . A B C . |
| # A 1 0 0 |
| # B 1 1 1 |
| # C 1 0 1 |
| # . |
| # where 1 indicates that the type in corresponding row is a subtype of the type |
| # in corresponding column. This matrix typically starts filled with all 1's and |
| # a typechecker tries to "disprove" every subtyping relation using atomic (or nominal) types. |
| # However, we don't want to keep this huge global state. Instead, we keep the subtype |
| # information in the form of list of pairs (subtype, supertype) shared by all Instances |
| # with given supertype's TypeInfo. When we enter a subtype check we push a pair in this list |
| # thus assuming that we started with 1 in corresponding matrix element. Such algorithm allows |
| # to treat recursive and mutually recursive protocols and other kinds of complex situations. |
| # |
| # If concurrent/parallel type checking will be added in future, |
| # then there should be one matrix per thread/process to avoid false negatives |
| # during the type checking phase. |
| assuming: list[tuple[mypy.types.Instance, mypy.types.Instance]] |
| assuming_proper: list[tuple[mypy.types.Instance, mypy.types.Instance]] |
| # Ditto for temporary 'inferring' stack of recursive constraint inference. |
| # It contains Instances of protocol types that appeared as an argument to |
| # constraints.infer_constraints(). We need 'inferring' to avoid infinite recursion for |
| # recursive and mutually recursive protocols. |
| # |
| # We make 'assuming' and 'inferring' attributes here instead of passing they as kwargs, |
| # since this would require to pass them in many dozens of calls. In particular, |
| # there is a dependency infer_constraint -> is_subtype -> is_callable_subtype -> |
| # -> infer_constraints. |
| inferring: list[mypy.types.Instance] |
| # 'inferring' and 'assuming' can't be made sets, since we need to use |
| # is_same_type to correctly treat unions. |
| |
| # Classes inheriting from Enum shadow their true members with a __getattr__, so we |
| # have to treat them as a special case. |
| is_enum: bool |
| # If true, any unknown attributes should have type 'Any' instead |
| # of generating a type error. This would be true if there is a |
| # base class with type 'Any', but other use cases may be |
| # possible. This is similar to having __getattr__ that returns Any |
| # (and __setattr__), but without the __getattr__ method. |
| fallback_to_any: bool |
| |
| # Same as above but for cases where metaclass has type Any. This will suppress |
| # all attribute errors only for *class object* access. |
| meta_fallback_to_any: bool |
| |
| # Information related to type annotations. |
| |
| # Generic type variable names (full names) |
| type_vars: list[str] |
| |
| # Whether this class has a ParamSpec type variable |
| has_param_spec_type: bool |
| |
| # Direct base classes. |
| bases: list[mypy.types.Instance] |
| |
| # Another type which this type will be treated as a subtype of, |
| # even though it's not a subclass in Python. The non-standard |
| # `@_promote` decorator introduces this, and there are also |
| # several builtin examples, in particular `int` -> `float`. |
| _promote: list[mypy.types.ProperType] |
| |
| # This is used for promoting native integer types such as 'i64' to |
| # 'int'. (_promote is used for the other direction.) This only |
| # supports one-step promotions (e.g., i64 -> int, not |
| # i64 -> int -> float, and this isn't used to promote in joins. |
| # |
| # This results in some unintuitive results, such as that even |
| # though i64 is compatible with int and int is compatible with |
| # float, i64 is *not* compatible with float. |
| alt_promote: mypy.types.Instance | None |
| |
| # Representation of a Tuple[...] base class, if the class has any |
| # (e.g., for named tuples). If this is not None, the actual Type |
| # object used for this class is not an Instance but a TupleType; |
| # the corresponding Instance is set as the fallback type of the |
| # tuple type. |
| tuple_type: mypy.types.TupleType | None |
| |
| # Is this a named tuple type? |
| is_named_tuple: bool |
| |
| # If this class is defined by the TypedDict type constructor, |
| # then this is not None. |
| typeddict_type: mypy.types.TypedDictType | None |
| |
| # Is this a newtype type? |
| is_newtype: bool |
| |
| # Is this a synthesized intersection type? |
| is_intersection: bool |
| |
| # This is a dictionary that will be serialized and un-serialized as is. |
| # It is useful for plugins to add their data to save in the cache. |
| metadata: dict[str, JsonDict] |
| |
| # Store type alias representing this type (for named tuples and TypedDicts). |
| # Although definitions of these types are stored in symbol tables as TypeInfo, |
| # when a type analyzer will find them, it should construct a TupleType, or |
| # a TypedDict type. However, we can't use the plain types, since if the definition |
| # is recursive, this will create an actual recursive structure of types (i.e. as |
| # internal Python objects) causing infinite recursions everywhere during type checking. |
| # To overcome this, we create a TypeAlias node, that will point to these types. |
| # We store this node in the `special_alias` attribute, because it must be the same node |
| # in case we are doing multiple semantic analysis passes. |
| special_alias: TypeAlias | None |
| |
| # Shared type variable for typing.Self in this class (if used, otherwise None). |
| self_type: mypy.types.TypeVarType | None |
| |
| # Added if the corresponding class is directly decorated with `typing.dataclass_transform` |
| dataclass_transform_spec: DataclassTransformSpec | None |
| |
| FLAGS: Final = [ |
| "is_abstract", |
| "is_enum", |
| "fallback_to_any", |
| "meta_fallback_to_any", |
| "is_named_tuple", |
| "is_newtype", |
| "is_protocol", |
| "runtime_protocol", |
| "is_final", |
| "is_intersection", |
| ] |
| |
| def __init__(self, names: SymbolTable, defn: ClassDef, module_name: str) -> None: |
| """Initialize a TypeInfo.""" |
| super().__init__() |
| self._fullname = defn.fullname |
| self.names = names |
| self.defn = defn |
| self.module_name = module_name |
| self.type_vars = [] |
| self.has_param_spec_type = False |
| self.has_type_var_tuple_type = False |
| self.bases = [] |
| self.mro = [] |
| self._mro_refs = None |
| self.bad_mro = False |
| self.declared_metaclass = None |
| self.metaclass_type = None |
| self.is_abstract = False |
| self.abstract_attributes = [] |
| self.deletable_attributes = [] |
| self.slots = None |
| self.assuming = [] |
| self.assuming_proper = [] |
| self.inferring = [] |
| self.is_protocol = False |
| self.runtime_protocol = False |
| self.type_var_tuple_prefix: int | None = None |
| self.type_var_tuple_suffix: int | None = None |
| self.add_type_vars() |
| self.is_final = False |
| self.is_enum = False |
| self.fallback_to_any = False |
| self.meta_fallback_to_any = False |
| self._promote = [] |
| self.alt_promote = None |
| self.tuple_type = None |
| self.special_alias = None |
| self.is_named_tuple = False |
| self.typeddict_type = None |
| self.is_newtype = False |
| self.is_intersection = False |
| self.metadata = {} |
| self.self_type = None |
| self.dataclass_transform_spec = None |
| |
| def add_type_vars(self) -> None: |
| self.has_type_var_tuple_type = False |
| if self.defn.type_vars: |
| for i, vd in enumerate(self.defn.type_vars): |
| if isinstance(vd, mypy.types.ParamSpecType): |
| self.has_param_spec_type = True |
| if isinstance(vd, mypy.types.TypeVarTupleType): |
| assert not self.has_type_var_tuple_type |
| self.has_type_var_tuple_type = True |
| self.type_var_tuple_prefix = i |
| self.type_var_tuple_suffix = len(self.defn.type_vars) - i - 1 |
| self.type_vars.append(vd.name) |
| assert not ( |
| self.has_param_spec_type and self.has_type_var_tuple_type |
| ), "Mixing type var tuples and param specs not supported yet" |
| |
| @property |
| def name(self) -> str: |
| """Short name.""" |
| return self.defn.name |
| |
| @property |
| def fullname(self) -> str: |
| return self._fullname |
| |
| def is_generic(self) -> bool: |
| """Is the type generic (i.e. does it have type variables)?""" |
| return len(self.type_vars) > 0 |
| |
| def get(self, name: str) -> SymbolTableNode | None: |
| for cls in self.mro: |
| n = cls.names.get(name) |
| if n: |
| return n |
| return None |
| |
| def get_containing_type_info(self, name: str) -> TypeInfo | None: |
| for cls in self.mro: |
| if name in cls.names: |
| return cls |
| return None |
| |
| @property |
| def protocol_members(self) -> list[str]: |
| # Protocol members are names of all attributes/methods defined in a protocol |
| # and in all its supertypes (except for 'object'). |
| members: set[str] = set() |
| assert self.mro, "This property can be only accessed after MRO is (re-)calculated" |
| for base in self.mro[:-1]: # we skip "object" since everyone implements it |
| if base.is_protocol: |
| for name, node in base.names.items(): |
| if isinstance(node.node, (TypeAlias, TypeVarExpr, MypyFile)): |
| # These are auxiliary definitions (and type aliases are prohibited). |
| continue |
| if name in EXCLUDED_PROTOCOL_ATTRIBUTES: |
| continue |
| members.add(name) |
| return sorted(list(members)) |
| |
| def __getitem__(self, name: str) -> SymbolTableNode: |
| n = self.get(name) |
| if n: |
| return n |
| else: |
| raise KeyError(name) |
| |
| def __repr__(self) -> str: |
| return f"<TypeInfo {self.fullname}>" |
| |
| def __bool__(self) -> bool: |
| # We defined this here instead of just overriding it in |
| # FakeInfo so that mypyc can generate a direct call instead of |
| # using the generic bool handling. |
| return not isinstance(self, FakeInfo) |
| |
| def has_readable_member(self, name: str) -> bool: |
| return self.get(name) is not None |
| |
| def get_method(self, name: str) -> FuncBase | Decorator | None: |
| for cls in self.mro: |
| if name in cls.names: |
| node = cls.names[name].node |
| if isinstance(node, FuncBase): |
| return node |
| elif isinstance(node, Decorator): # Two `if`s make `mypyc` happy |
| return node |
| else: |
| return None |
| return None |
| |
| def calculate_metaclass_type(self) -> mypy.types.Instance | None: |
| declared = self.declared_metaclass |
| if declared is not None and not declared.type.has_base("builtins.type"): |
| return declared |
| if self._fullname == "builtins.type": |
| return mypy.types.Instance(self, []) |
| candidates = [ |
| s.declared_metaclass |
| for s in self.mro |
| if s.declared_metaclass is not None and s.declared_metaclass.type is not None |
| ] |
| for c in candidates: |
| if all(other.type in c.type.mro for other in candidates): |
| return c |
| return None |
| |
| def is_metaclass(self) -> bool: |
| return ( |
| self.has_base("builtins.type") |
| or self.fullname == "abc.ABCMeta" |
| or self.fallback_to_any |
| ) |
| |
| def has_base(self, fullname: str) -> bool: |
| """Return True if type has a base type with the specified name. |
| |
| This can be either via extension or via implementation. |
| """ |
| for cls in self.mro: |
| if cls.fullname == fullname: |
| return True |
| return False |
| |
| def direct_base_classes(self) -> list[TypeInfo]: |
| """Return a direct base classes. |
| |
| Omit base classes of other base classes. |
| """ |
| return [base.type for base in self.bases] |
| |
| def update_tuple_type(self, typ: mypy.types.TupleType) -> None: |
| """Update tuple_type and special_alias as needed.""" |
| self.tuple_type = typ |
| alias = TypeAlias.from_tuple_type(self) |
| if not self.special_alias: |
| self.special_alias = alias |
| else: |
| self.special_alias.target = alias.target |
| |
| def update_typeddict_type(self, typ: mypy.types.TypedDictType) -> None: |
| """Update typeddict_type and special_alias as needed.""" |
| self.typeddict_type = typ |
| alias = TypeAlias.from_typeddict_type(self) |
| if not self.special_alias: |
| self.special_alias = alias |
| else: |
| self.special_alias.target = alias.target |
| |
| def __str__(self) -> str: |
| """Return a string representation of the type. |
| |
| This includes the most important information about the type. |
| """ |
| options = Options() |
| return self.dump( |
| str_conv=mypy.strconv.StrConv(options=options), |
| type_str_conv=mypy.types.TypeStrVisitor(options=options), |
| ) |
| |
| def dump( |
| self, str_conv: mypy.strconv.StrConv, type_str_conv: mypy.types.TypeStrVisitor |
| ) -> str: |
| """Return a string dump of the contents of the TypeInfo.""" |
| |
| base: str = "" |
| |
| def type_str(typ: mypy.types.Type) -> str: |
| return typ.accept(type_str_conv) |
| |
| head = "TypeInfo" + str_conv.format_id(self) |
| if self.bases: |
| base = f"Bases({', '.join(type_str(base) for base in self.bases)})" |
| mro = "Mro({})".format( |
| ", ".join(item.fullname + str_conv.format_id(item) for item in self.mro) |
| ) |
| names = [] |
| for name in sorted(self.names): |
| description = name + str_conv.format_id(self.names[name].node) |
| node = self.names[name].node |
| if isinstance(node, Var) and node.type: |
| description += f" ({type_str(node.type)})" |
| names.append(description) |
| items = [f"Name({self.fullname})", base, mro, ("Names", names)] |
| if self.declared_metaclass: |
| items.append(f"DeclaredMetaclass({type_str(self.declared_metaclass)})") |
| if self.metaclass_type: |
| items.append(f"MetaclassType({type_str(self.metaclass_type)})") |
| return mypy.strconv.dump_tagged(items, head, str_conv=str_conv) |
| |
| def serialize(self) -> JsonDict: |
| # NOTE: This is where all ClassDefs originate, so there shouldn't be duplicates. |
| data = { |
| ".class": "TypeInfo", |
| "module_name": self.module_name, |
| "fullname": self.fullname, |
| "names": self.names.serialize(self.fullname), |
| "defn": self.defn.serialize(), |
| "abstract_attributes": self.abstract_attributes, |
| "type_vars": self.type_vars, |
| "has_param_spec_type": self.has_param_spec_type, |
| "bases": [b.serialize() for b in self.bases], |
| "mro": [c.fullname for c in self.mro], |
| "_promote": [p.serialize() for p in self._promote], |
| "alt_promote": None if self.alt_promote is None else self.alt_promote.serialize(), |
| "declared_metaclass": ( |
| None if self.declared_metaclass is None else self.declared_metaclass.serialize() |
| ), |
| "metaclass_type": None |
| if self.metaclass_type is None |
| else self.metaclass_type.serialize(), |
| "tuple_type": None if self.tuple_type is None else self.tuple_type.serialize(), |
| "typeddict_type": None |
| if self.typeddict_type is None |
| else self.typeddict_type.serialize(), |
| "flags": get_flags(self, TypeInfo.FLAGS), |
| "metadata": self.metadata, |
| "slots": list(sorted(self.slots)) if self.slots is not None else None, |
| "deletable_attributes": self.deletable_attributes, |
| "self_type": self.self_type.serialize() if self.self_type is not None else None, |
| "dataclass_transform_spec": ( |
| self.dataclass_transform_spec.serialize() |
| if self.dataclass_transform_spec is not None |
| else None |
| ), |
| } |
| return data |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> TypeInfo: |
| names = SymbolTable.deserialize(data["names"]) |
| defn = ClassDef.deserialize(data["defn"]) |
| module_name = data["module_name"] |
| ti = TypeInfo(names, defn, module_name) |
| ti._fullname = data["fullname"] |
| # TODO: Is there a reason to reconstruct ti.subtypes? |
| ti.abstract_attributes = [(attr[0], attr[1]) for attr in data["abstract_attributes"]] |
| ti.type_vars = data["type_vars"] |
| ti.has_param_spec_type = data["has_param_spec_type"] |
| ti.bases = [mypy.types.Instance.deserialize(b) for b in data["bases"]] |
| _promote = [] |
| for p in data["_promote"]: |
| t = mypy.types.deserialize_type(p) |
| assert isinstance(t, mypy.types.ProperType) |
| _promote.append(t) |
| ti._promote = _promote |
| ti.alt_promote = ( |
| None |
| if data["alt_promote"] is None |
| else mypy.types.Instance.deserialize(data["alt_promote"]) |
| ) |
| ti.declared_metaclass = ( |
| None |
| if data["declared_metaclass"] is None |
| else mypy.types.Instance.deserialize(data["declared_metaclass"]) |
| ) |
| ti.metaclass_type = ( |
| None |
| if data["metaclass_type"] is None |
| else mypy.types.Instance.deserialize(data["metaclass_type"]) |
| ) |
| # NOTE: ti.mro will be set in the fixup phase based on these |
| # names. The reason we need to store the mro instead of just |
| # recomputing it from base classes has to do with a subtle |
| # point about fine-grained incremental: the cache files might |
| # not be loaded until after a class in the mro has changed its |
| # bases, which causes the mro to change. If we recomputed our |
| # mro, we would compute the *new* mro, which leaves us with no |
| # way to detect that the mro has changed! Thus we need to make |
| # sure to load the original mro so that once the class is |
| # rechecked, it can tell that the mro has changed. |
| ti._mro_refs = data["mro"] |
| ti.tuple_type = ( |
| None |
| if data["tuple_type"] is None |
| else mypy.types.TupleType.deserialize(data["tuple_type"]) |
| ) |
| ti.typeddict_type = ( |
| None |
| if data["typeddict_type"] is None |
| else mypy.types.TypedDictType.deserialize(data["typeddict_type"]) |
| ) |
| ti.metadata = data["metadata"] |
| ti.slots = set(data["slots"]) if data["slots"] is not None else None |
| ti.deletable_attributes = data["deletable_attributes"] |
| set_flags(ti, data["flags"]) |
| st = data["self_type"] |
| ti.self_type = mypy.types.TypeVarType.deserialize(st) if st is not None else None |
| if data.get("dataclass_transform_spec") is not None: |
| ti.dataclass_transform_spec = DataclassTransformSpec.deserialize( |
| data["dataclass_transform_spec"] |
| ) |
| return ti |
| |
| |
| class FakeInfo(TypeInfo): |
| __slots__ = ("msg",) |
| |
| # types.py defines a single instance of this class, called types.NOT_READY. |
| # This instance is used as a temporary placeholder in the process of de-serialization |
| # of 'Instance' types. The de-serialization happens in two steps: In the first step, |
| # Instance.type is set to NOT_READY. In the second step (in fixup.py) it is replaced by |
| # an actual TypeInfo. If you see the assertion error below, then most probably something |
| # went wrong during the second step and an 'Instance' that raised this error was not fixed. |
| # Note: |
| # 'None' is not used as a dummy value for two reasons: |
| # 1. This will require around 80-100 asserts to make 'mypy --strict-optional mypy' |
| # pass cleanly. |
| # 2. If NOT_READY value is accidentally used somewhere, it will be obvious where the value |
| # is from, whereas a 'None' value could come from anywhere. |
| # |
| # Additionally, this serves as a more general-purpose placeholder |
| # for missing TypeInfos in a number of places where the excuses |
| # for not being Optional are a little weaker. |
| # |
| # TypeInfo defines a __bool__ method that returns False for FakeInfo |
| # so that it can be conveniently tested against in the same way that it |
| # would be if things were properly optional. |
| def __init__(self, msg: str) -> None: |
| self.msg = msg |
| |
| def __getattribute__(self, attr: str) -> type: |
| # Handle __class__ so that isinstance still works... |
| if attr == "__class__": |
| return object.__getattribute__(self, attr) # type: ignore[no-any-return] |
| raise AssertionError(object.__getattribute__(self, "msg")) |
| |
| |
| VAR_NO_INFO: Final[TypeInfo] = FakeInfo("Var is lacking info") |
| CLASSDEF_NO_INFO: Final[TypeInfo] = FakeInfo("ClassDef is lacking info") |
| FUNC_NO_INFO: Final[TypeInfo] = FakeInfo("FuncBase for non-methods lack info") |
| |
| |
| class TypeAlias(SymbolNode): |
| """ |
| A symbol node representing a type alias. |
| |
| Type alias is a static concept, in contrast to variables with types |
| like Type[...]. Namely: |
| * type aliases |
| - can be used in type context (annotations) |
| - cannot be re-assigned |
| * variables with type Type[...] |
| - cannot be used in type context |
| - but can be re-assigned |
| |
| An alias can be defined only by an assignment to a name (not any other lvalues). |
| |
| Such assignment defines an alias by default. To define a variable, |
| an explicit Type[...] annotation is required. As an exception, |
| at non-global scope non-subscripted rvalue creates a variable even without |
| an annotation. This exception exists to accommodate the common use case of |
| class-valued attributes. See SemanticAnalyzerPass2.check_and_set_up_type_alias |
| for details. |
| |
| Aliases can be generic. We use bound type variables for generic aliases, similar |
| to classes. Essentially, type aliases work as macros that expand textually. |
| The definition and expansion rules are following: |
| |
| 1. An alias targeting a generic class without explicit variables act as |
| the given class (this doesn't apply to TypedDict, Tuple and Callable, which |
| are not proper classes but special type constructors): |
| |
| A = List |
| AA = List[Any] |
| |
| x: A # same as List[Any] |
| x: A[int] # same as List[int] |
| |
| x: AA # same as List[Any] |
| x: AA[int] # Error! |
| |
| C = Callable # Same as Callable[..., Any] |
| T = Tuple # Same as Tuple[Any, ...] |
| |
| 2. An alias using explicit type variables in its rvalue expects |
| replacements (type arguments) for these variables. If missing, they |
| are treated as Any, like for other generics: |
| |
| B = List[Tuple[T, T]] |
| |
| x: B # same as List[Tuple[Any, Any]] |
| x: B[int] # same as List[Tuple[int, int]] |
| |
| def f(x: B[T]) -> T: ... # without T, Any would be used here |
| |
| 3. An alias can be defined using another aliases. In the definition |
| rvalue the Any substitution doesn't happen for top level unsubscripted |
| generic classes: |
| |
| A = List |
| B = A # here A is expanded to List, _not_ List[Any], |
| # to match the Python runtime behaviour |
| x: B[int] # same as List[int] |
| C = List[A] # this expands to List[List[Any]] |
| |
| AA = List[T] |
| D = AA # here AA expands to List[Any] |
| x: D[int] # Error! |
| |
| Note: the fact that we support aliases like `A = List` means that the target |
| type will be initially an instance type with wrong number of type arguments. |
| Such instances are all fixed either during or after main semantic analysis passes. |
| We therefore store the difference between `List` and `List[Any]` rvalues (targets) |
| using the `no_args` flag. See also TypeAliasExpr.no_args. |
| |
| Meaning of other fields: |
| |
| target: The target type. For generic aliases contains bound type variables |
| as nested types (currently TypeVar and ParamSpec are supported). |
| _fullname: Qualified name of this type alias. This is used in particular |
| to track fine grained dependencies from aliases. |
| alias_tvars: Type variables used to define this alias. |
| normalized: Used to distinguish between `A = List`, and `A = list`. Both |
| are internally stored using `builtins.list` (because `typing.List` is |
| itself an alias), while the second cannot be subscripted because of |
| Python runtime limitation. |
| line and column: Line and column on the original alias definition. |
| eager: If True, immediately expand alias when referred to (useful for aliases |
| within functions that can't be looked up from the symbol table) |
| """ |
| |
| __slots__ = ( |
| "target", |
| "_fullname", |
| "alias_tvars", |
| "no_args", |
| "normalized", |
| "_is_recursive", |
| "eager", |
| "tvar_tuple_index", |
| ) |
| |
| __match_args__ = ("name", "target", "alias_tvars", "no_args") |
| |
| def __init__( |
| self, |
| target: mypy.types.Type, |
| fullname: str, |
| line: int, |
| column: int, |
| *, |
| alias_tvars: list[mypy.types.TypeVarLikeType] | None = None, |
| no_args: bool = False, |
| normalized: bool = False, |
| eager: bool = False, |
| ) -> None: |
| self._fullname = fullname |
| self.target = target |
| if alias_tvars is None: |
| alias_tvars = [] |
| self.alias_tvars = alias_tvars |
| self.no_args = no_args |
| self.normalized = normalized |
| # This attribute is manipulated by TypeAliasType. If non-None, |
| # it is the cached value. |
| self._is_recursive: bool | None = None |
| self.eager = eager |
| self.tvar_tuple_index = None |
| for i, t in enumerate(alias_tvars): |
| if isinstance(t, mypy.types.TypeVarTupleType): |
| self.tvar_tuple_index = i |
| super().__init__(line, column) |
| |
| @classmethod |
| def from_tuple_type(cls, info: TypeInfo) -> TypeAlias: |
| """Generate an alias to the tuple type described by a given TypeInfo. |
| |
| NOTE: this doesn't set type alias type variables (for generic tuple types), |
| they must be set by the caller (when fully analyzed). |
| """ |
| assert info.tuple_type |
| # TODO: is it possible to refactor this to set the correct type vars here? |
| return TypeAlias( |
| info.tuple_type.copy_modified(fallback=mypy.types.Instance(info, info.defn.type_vars)), |
| info.fullname, |
| info.line, |
| info.column, |
| ) |
| |
| @classmethod |
| def from_typeddict_type(cls, info: TypeInfo) -> TypeAlias: |
| """Generate an alias to the TypedDict type described by a given TypeInfo. |
| |
| NOTE: this doesn't set type alias type variables (for generic TypedDicts), |
| they must be set by the caller (when fully analyzed). |
| """ |
| assert info.typeddict_type |
| # TODO: is it possible to refactor this to set the correct type vars here? |
| return TypeAlias( |
| info.typeddict_type.copy_modified( |
| fallback=mypy.types.Instance(info, info.defn.type_vars) |
| ), |
| info.fullname, |
| info.line, |
| info.column, |
| ) |
| |
| @property |
| def name(self) -> str: |
| return self._fullname.split(".")[-1] |
| |
| @property |
| def fullname(self) -> str: |
| return self._fullname |
| |
| @property |
| def has_param_spec_type(self) -> bool: |
| return any(isinstance(v, mypy.types.ParamSpecType) for v in self.alias_tvars) |
| |
| def serialize(self) -> JsonDict: |
| data: JsonDict = { |
| ".class": "TypeAlias", |
| "fullname": self._fullname, |
| "target": self.target.serialize(), |
| "alias_tvars": [v.serialize() for v in self.alias_tvars], |
| "no_args": self.no_args, |
| "normalized": self.normalized, |
| "line": self.line, |
| "column": self.column, |
| } |
| return data |
| |
| def accept(self, visitor: NodeVisitor[T]) -> T: |
| return visitor.visit_type_alias(self) |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> TypeAlias: |
| assert data[".class"] == "TypeAlias" |
| fullname = data["fullname"] |
| alias_tvars = [mypy.types.deserialize_type(v) for v in data["alias_tvars"]] |
| assert all(isinstance(t, mypy.types.TypeVarLikeType) for t in alias_tvars) |
| target = mypy.types.deserialize_type(data["target"]) |
| no_args = data["no_args"] |
| normalized = data["normalized"] |
| line = data["line"] |
| column = data["column"] |
| return cls( |
| target, |
| fullname, |
| line, |
| column, |
| alias_tvars=cast(List[mypy.types.TypeVarLikeType], alias_tvars), |
| no_args=no_args, |
| normalized=normalized, |
| ) |
| |
| |
| class PlaceholderNode(SymbolNode): |
| """Temporary symbol node that will later become a real SymbolNode. |
| |
| These are only present during semantic analysis when using the new |
| semantic analyzer. These are created if some essential dependencies |
| of a definition are not yet complete. |
| |
| A typical use is for names imported from a module which is still |
| incomplete (within an import cycle): |
| |
| from m import f # Initially may create PlaceholderNode |
| |
| This is particularly important if the imported shadows a name from |
| an enclosing scope or builtins: |
| |
| from m import int # Placeholder avoids mixups with builtins.int |
| |
| Another case where this is useful is when there is another definition |
| or assignment: |
| |
| from m import f |
| def f() -> None: ... |
| |
| In the above example, the presence of PlaceholderNode allows us to |
| handle the second definition as a redefinition. |
| |
| They are also used to create PlaceholderType instances for types |
| that refer to incomplete types. Example: |
| |
| class C(Sequence[C]): ... |
| |
| We create a PlaceholderNode (with becomes_typeinfo=True) for C so |
| that the type C in Sequence[C] can be bound. |
| |
| Attributes: |
| |
| fullname: Full name of the PlaceholderNode. |
| node: AST node that contains the definition that caused this to |
| be created. This is useful for tracking order of incomplete definitions |
| and for debugging. |
| becomes_typeinfo: If True, this refers something that could later |
| become a TypeInfo. It can't be used with type variables, in |
| particular, as this would cause issues with class type variable |
| detection. |
| |
| The long-term purpose of placeholder nodes/types is to evolve into |
| something that can support general recursive types. |
| """ |
| |
| __slots__ = ("_fullname", "node", "becomes_typeinfo") |
| |
| def __init__( |
| self, fullname: str, node: Node, line: int, *, becomes_typeinfo: bool = False |
| ) -> None: |
| self._fullname = fullname |
| self.node = node |
| self.becomes_typeinfo = becomes_typeinfo |
| self.line = line |
| |
| @property |
| def name(self) -> str: |
| return self._fullname.split(".")[-1] |
| |
| @property |
| def fullname(self) -> str: |
| return self._fullname |
| |
| def serialize(self) -> JsonDict: |
| assert False, "PlaceholderNode can't be serialized" |
| |
| def accept(self, visitor: NodeVisitor[T]) -> T: |
| return visitor.visit_placeholder_node(self) |
| |
| |
| class SymbolTableNode: |
| """Description of a name binding in a symbol table. |
| |
| These are only used as values in module (global), function (local) |
| and class symbol tables (see SymbolTable). The name that is bound is |
| the key in SymbolTable. |
| |
| Symbol tables don't contain direct references to AST nodes primarily |
| because there can be multiple symbol table references to a single |
| AST node (due to imports and aliases), and different references can |
| behave differently. This class describes the unique properties of |
| each reference. |
| |
| The most fundamental attribute is 'node', which is the AST node that |
| the name refers to. |
| |
| The kind is usually one of LDEF, GDEF or MDEF, depending on the scope |
| of the definition. These three kinds can usually be used |
| interchangeably and the difference between local, global and class |
| scopes is mostly descriptive, with no semantic significance. |
| However, some tools that consume mypy ASTs may care about these so |
| they should be correct. |
| |
| Attributes: |
| node: AST node of definition. Among others, this can be one of |
| FuncDef, Var, TypeInfo, TypeVarExpr or MypyFile -- or None |
| for cross_ref that hasn't been fixed up yet. |
| kind: Kind of node. Possible values: |
| - LDEF: local definition |
| - GDEF: global (module-level) definition |
| - MDEF: class member definition |
| - UNBOUND_IMPORTED: temporary kind for imported names (we |
| don't know the final kind yet) |
| module_public: If False, this name won't be imported via |
| 'from <module> import *'. This has no effect on names within |
| classes. |
| module_hidden: If True, the name will be never exported (needed for |
| stub files) |
| cross_ref: For deserialized MypyFile nodes, the referenced module |
| name; for other nodes, optionally the name of the referenced object. |
| implicit: Was this defined by assignment to self attribute? |
| plugin_generated: Was this symbol generated by a plugin? |
| (And therefore needs to be removed in aststrip.) |
| no_serialize: Do not serialize this node if True. This is used to prevent |
| keys in the cache that refer to modules on which this file does not |
| depend. Currently this can happen if there is a module not in build |
| used e.g. like this: |
| import a.b.c # type: ignore |
| This will add a submodule symbol to parent module `a` symbol table, |
| but `a.b` is _not_ added as its dependency. Therefore, we should |
| not serialize these symbols as they may not be found during fixup |
| phase, instead they will be re-added during subsequent patch parents |
| phase. |
| TODO: Refactor build.py to make dependency tracking more transparent |
| and/or refactor look-up functions to not require parent patching. |
| |
| NOTE: No other attributes should be added to this class unless they |
| are shared by all node kinds. |
| """ |
| |
| __slots__ = ( |
| "kind", |
| "node", |
| "module_public", |
| "module_hidden", |
| "cross_ref", |
| "implicit", |
| "plugin_generated", |
| "no_serialize", |
| ) |
| |
| def __init__( |
| self, |
| kind: int, |
| node: SymbolNode | None, |
| module_public: bool = True, |
| implicit: bool = False, |
| module_hidden: bool = False, |
| *, |
| plugin_generated: bool = False, |
| no_serialize: bool = False, |
| ) -> None: |
| self.kind = kind |
| self.node = node |
| self.module_public = module_public |
| self.implicit = implicit |
| self.module_hidden = module_hidden |
| self.cross_ref: str | None = None |
| self.plugin_generated = plugin_generated |
| self.no_serialize = no_serialize |
| |
| @property |
| def fullname(self) -> str | None: |
| if self.node is not None: |
| return self.node.fullname |
| else: |
| return None |
| |
| @property |
| def type(self) -> mypy.types.Type | None: |
| node = self.node |
| if isinstance(node, (Var, SYMBOL_FUNCBASE_TYPES)) and node.type is not None: |
| return node.type |
| elif isinstance(node, Decorator): |
| return node.var.type |
| else: |
| return None |
| |
| def copy(self) -> SymbolTableNode: |
| new = SymbolTableNode( |
| self.kind, self.node, self.module_public, self.implicit, self.module_hidden |
| ) |
| new.cross_ref = self.cross_ref |
| return new |
| |
| def __str__(self) -> str: |
| s = f"{node_kinds[self.kind]}/{short_type(self.node)}" |
| if isinstance(self.node, SymbolNode): |
| s += f" ({self.node.fullname})" |
| # Include declared type of variables and functions. |
| if self.type is not None: |
| s += f" : {self.type}" |
| return s |
| |
| def serialize(self, prefix: str, name: str) -> JsonDict: |
| """Serialize a SymbolTableNode. |
| |
| Args: |
| prefix: full name of the containing module or class; or None |
| name: name of this object relative to the containing object |
| """ |
| data: JsonDict = {".class": "SymbolTableNode", "kind": node_kinds[self.kind]} |
| if self.module_hidden: |
| data["module_hidden"] = True |
| if not self.module_public: |
| data["module_public"] = False |
| if self.implicit: |
| data["implicit"] = True |
| if self.plugin_generated: |
| data["plugin_generated"] = True |
| if isinstance(self.node, MypyFile): |
| data["cross_ref"] = self.node.fullname |
| else: |
| assert self.node is not None, f"{prefix}:{name}" |
| if prefix is not None: |
| fullname = self.node.fullname |
| if ( |
| "." in fullname |
| and fullname != prefix + "." + name |
| and not (isinstance(self.node, Var) and self.node.from_module_getattr) |
| ): |
| assert not isinstance( |
| self.node, PlaceholderNode |
| ), f"Definition of {fullname} is unexpectedly incomplete" |
| data["cross_ref"] = fullname |
| return data |
| data["node"] = self.node.serialize() |
| return data |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> SymbolTableNode: |
| assert data[".class"] == "SymbolTableNode" |
| kind = inverse_node_kinds[data["kind"]] |
| if "cross_ref" in data: |
| # This will be fixed up later. |
| stnode = SymbolTableNode(kind, None) |
| stnode.cross_ref = data["cross_ref"] |
| else: |
| assert "node" in data, data |
| node = SymbolNode.deserialize(data["node"]) |
| stnode = SymbolTableNode(kind, node) |
| if "module_hidden" in data: |
| stnode.module_hidden = data["module_hidden"] |
| if "module_public" in data: |
| stnode.module_public = data["module_public"] |
| if "implicit" in data: |
| stnode.implicit = data["implicit"] |
| if "plugin_generated" in data: |
| stnode.plugin_generated = data["plugin_generated"] |
| return stnode |
| |
| |
| class SymbolTable(Dict[str, SymbolTableNode]): |
| """Static representation of a namespace dictionary. |
| |
| This is used for module, class and function namespaces. |
| """ |
| |
| __slots__ = () |
| |
| def __str__(self) -> str: |
| a: list[str] = [] |
| for key, value in self.items(): |
| # Filter out the implicit import of builtins. |
| if isinstance(value, SymbolTableNode): |
| if ( |
| value.fullname != "builtins" |
| and (value.fullname or "").split(".")[-1] not in implicit_module_attrs |
| ): |
| a.append(" " + str(key) + " : " + str(value)) |
| else: |
| a.append(" <invalid item>") |
| a = sorted(a) |
| a.insert(0, "SymbolTable(") |
| a[-1] += ")" |
| return "\n".join(a) |
| |
| def copy(self) -> SymbolTable: |
| return SymbolTable([(key, node.copy()) for key, node in self.items()]) |
| |
| def serialize(self, fullname: str) -> JsonDict: |
| data: JsonDict = {".class": "SymbolTable"} |
| for key, value in self.items(): |
| # Skip __builtins__: it's a reference to the builtins |
| # module that gets added to every module by |
| # SemanticAnalyzerPass2.visit_file(), but it shouldn't be |
| # accessed by users of the module. |
| if key == "__builtins__" or value.no_serialize: |
| continue |
| data[key] = value.serialize(fullname, key) |
| return data |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> SymbolTable: |
| assert data[".class"] == "SymbolTable" |
| st = SymbolTable() |
| for key, value in data.items(): |
| if key != ".class": |
| st[key] = SymbolTableNode.deserialize(value) |
| return st |
| |
| |
| class DataclassTransformSpec: |
| """Specifies how a dataclass-like transform should be applied. The fields here are based on the |
| parameters accepted by `typing.dataclass_transform`.""" |
| |
| __slots__ = ( |
| "eq_default", |
| "order_default", |
| "kw_only_default", |
| "frozen_default", |
| "field_specifiers", |
| ) |
| |
| def __init__( |
| self, |
| *, |
| eq_default: bool | None = None, |
| order_default: bool | None = None, |
| kw_only_default: bool | None = None, |
| field_specifiers: tuple[str, ...] | None = None, |
| # Specified outside of PEP 681: |
| # frozen_default was added to CPythonin https://github.com/python/cpython/pull/99958 citing |
| # positive discussion in typing-sig |
| frozen_default: bool | None = None, |
| ): |
| self.eq_default = eq_default if eq_default is not None else True |
| self.order_default = order_default if order_default is not None else False |
| self.kw_only_default = kw_only_default if kw_only_default is not None else False |
| self.frozen_default = frozen_default if frozen_default is not None else False |
| self.field_specifiers = field_specifiers if field_specifiers is not None else () |
| |
| def serialize(self) -> JsonDict: |
| return { |
| "eq_default": self.eq_default, |
| "order_default": self.order_default, |
| "kw_only_default": self.kw_only_default, |
| "frozen_default": self.frozen_default, |
| "field_specifiers": list(self.field_specifiers), |
| } |
| |
| @classmethod |
| def deserialize(cls, data: JsonDict) -> DataclassTransformSpec: |
| return DataclassTransformSpec( |
| eq_default=data.get("eq_default"), |
| order_default=data.get("order_default"), |
| kw_only_default=data.get("kw_only_default"), |
| frozen_default=data.get("frozen_default"), |
| field_specifiers=tuple(data.get("field_specifiers", [])), |
| ) |
| |
| |
| def get_flags(node: Node, names: list[str]) -> list[str]: |
| return [name for name in names if getattr(node, name)] |
| |
| |
| def set_flags(node: Node, flags: list[str]) -> None: |
| for name in flags: |
| setattr(node, name, True) |
| |
| |
| def get_member_expr_fullname(expr: MemberExpr) -> str | None: |
| """Return the qualified name representation of a member expression. |
| |
| Return a string of form foo.bar, foo.bar.baz, or similar, or None if the |
| argument cannot be represented in this form. |
| """ |
| initial: str | None = None |
| if isinstance(expr.expr, NameExpr): |
| initial = expr.expr.name |
| elif isinstance(expr.expr, MemberExpr): |
| initial = get_member_expr_fullname(expr.expr) |
| else: |
| return None |
| return f"{initial}.{expr.name}" |
| |
| |
| deserialize_map: Final = { |
| key: obj.deserialize |
| for key, obj in globals().items() |
| if type(obj) is not FakeInfo |
| and isinstance(obj, type) |
| and issubclass(obj, SymbolNode) |
| and obj is not SymbolNode |
| } |
| |
| |
| def check_arg_kinds( |
| arg_kinds: list[ArgKind], nodes: list[T], fail: Callable[[str, T], None] |
| ) -> None: |
| is_var_arg = False |
| is_kw_arg = False |
| seen_named = False |
| seen_opt = False |
| for kind, node in zip(arg_kinds, nodes): |
| if kind == ARG_POS: |
| if is_var_arg or is_kw_arg or seen_named or seen_opt: |
| fail( |
| "Required positional args may not appear after default, named or var args", |
| node, |
| ) |
| break |
| elif kind == ARG_OPT: |
| if is_var_arg or is_kw_arg or seen_named: |
| fail("Positional default args may not appear after named or var args", node) |
| break |
| seen_opt = True |
| elif kind == ARG_STAR: |
| if is_var_arg or is_kw_arg or seen_named: |
| fail("Var args may not appear after named or var args", node) |
| break |
| is_var_arg = True |
| elif kind == ARG_NAMED or kind == ARG_NAMED_OPT: |
| seen_named = True |
| if is_kw_arg: |
| fail("A **kwargs argument must be the last argument", node) |
| break |
| elif kind == ARG_STAR2: |
| if is_kw_arg: |
| fail("You may only have one **kwargs argument", node) |
| break |
| is_kw_arg = True |
| |
| |
| def check_arg_names( |
| names: Sequence[str | None], |
| nodes: list[T], |
| fail: Callable[[str, T], None], |
| description: str = "function definition", |
| ) -> None: |
| seen_names: set[str | None] = set() |
| for name, node in zip(names, nodes): |
| if name is not None and name in seen_names: |
| fail(f'Duplicate argument "{name}" in {description}', node) |
| break |
| seen_names.add(name) |
| |
| |
| def is_class_var(expr: NameExpr) -> bool: |
| """Return whether the expression is ClassVar[...]""" |
| if isinstance(expr.node, Var): |
| return expr.node.is_classvar |
| return False |
| |
| |
| def is_final_node(node: SymbolNode | None) -> bool: |
| """Check whether `node` corresponds to a final attribute.""" |
| return isinstance(node, (Var, FuncDef, OverloadedFuncDef, Decorator)) and node.is_final |
| |
| |
| def local_definitions( |
| names: SymbolTable, name_prefix: str, info: TypeInfo | None = None |
| ) -> Iterator[Definition]: |
| """Iterate over local definitions (not imported) in a symbol table. |
| |
| Recursively iterate over class members and nested classes. |
| """ |
| # TODO: What should the name be? Or maybe remove it? |
| for name, symnode in names.items(): |
| shortname = name |
| if "-redef" in name: |
| # Restore original name from mangled name of multiply defined function |
| shortname = name.split("-redef")[0] |
| fullname = name_prefix + "." + shortname |
| node = symnode.node |
| if node and node.fullname == fullname: |
| yield fullname, symnode, info |
| if isinstance(node, TypeInfo): |
| yield from local_definitions(node.names, fullname, node) |