Major fixes and new features
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
Binary file not shown.
Binary file not shown.
516
venv/lib/python3.12/site-packages/mypy/server/astdiff.py
Normal file
516
venv/lib/python3.12/site-packages/mypy/server/astdiff.py
Normal file
@@ -0,0 +1,516 @@
|
||||
"""Utilities for comparing two versions of a module symbol table.
|
||||
|
||||
The goal is to find which AST nodes have externally visible changes, so
|
||||
that we can fire triggers and re-process other parts of the program
|
||||
that are stale because of the changes.
|
||||
|
||||
Only look at detail at definitions at the current module -- don't
|
||||
recurse into other modules.
|
||||
|
||||
A summary of the module contents:
|
||||
|
||||
* snapshot_symbol_table(...) creates an opaque snapshot description of a
|
||||
module/class symbol table (recursing into nested class symbol tables).
|
||||
|
||||
* compare_symbol_table_snapshots(...) compares two snapshots for the same
|
||||
module id and returns fully qualified names of differences (which act as
|
||||
triggers).
|
||||
|
||||
To compare two versions of a module symbol table, take snapshots of both
|
||||
versions and compare the snapshots. The use of snapshots makes it easy to
|
||||
compare two versions of the *same* symbol table that is being mutated.
|
||||
|
||||
Summary of how this works for certain kinds of differences:
|
||||
|
||||
* If a symbol table node is deleted or added (only present in old/new version
|
||||
of the symbol table), it is considered different, of course.
|
||||
|
||||
* If a symbol table node refers to a different sort of thing in the new version,
|
||||
it is considered different (for example, if a class is replaced with a
|
||||
function).
|
||||
|
||||
* If the signature of a function has changed, it is considered different.
|
||||
|
||||
* If the type of a variable changes, it is considered different.
|
||||
|
||||
* If the MRO of a class changes, or a non-generic class is turned into a
|
||||
generic class, the class is considered different (there are other such "big"
|
||||
differences that cause a class to be considered changed). However, just changes
|
||||
to attributes or methods don't generally constitute a difference at the
|
||||
class level -- these are handled at attribute level (say, 'mod.Cls.method'
|
||||
is different rather than 'mod.Cls' being different).
|
||||
|
||||
* If an imported name targets a different name (say, 'from x import y' is
|
||||
replaced with 'from z import y'), the name in the module is considered
|
||||
different. If the target of an import continues to have the same name,
|
||||
but it's specifics change, this doesn't mean that the imported name is
|
||||
treated as changed. Say, there is 'from x import y' in 'm', and the
|
||||
type of 'x.y' has changed. This doesn't mean that that 'm.y' is considered
|
||||
changed. Instead, processing the difference in 'm' will be handled through
|
||||
fine-grained dependencies.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Sequence, Tuple, Union
|
||||
from typing_extensions import TypeAlias as _TypeAlias
|
||||
|
||||
from mypy.expandtype import expand_type
|
||||
from mypy.nodes import (
|
||||
UNBOUND_IMPORTED,
|
||||
Decorator,
|
||||
FuncBase,
|
||||
FuncDef,
|
||||
FuncItem,
|
||||
MypyFile,
|
||||
OverloadedFuncDef,
|
||||
ParamSpecExpr,
|
||||
SymbolNode,
|
||||
SymbolTable,
|
||||
TypeAlias,
|
||||
TypeInfo,
|
||||
TypeVarExpr,
|
||||
TypeVarTupleExpr,
|
||||
Var,
|
||||
)
|
||||
from mypy.semanal_shared import find_dataclass_transform_spec
|
||||
from mypy.types import (
|
||||
AnyType,
|
||||
CallableType,
|
||||
DeletedType,
|
||||
ErasedType,
|
||||
Instance,
|
||||
LiteralType,
|
||||
NoneType,
|
||||
Overloaded,
|
||||
Parameters,
|
||||
ParamSpecType,
|
||||
PartialType,
|
||||
TupleType,
|
||||
Type,
|
||||
TypeAliasType,
|
||||
TypedDictType,
|
||||
TypeType,
|
||||
TypeVarId,
|
||||
TypeVarLikeType,
|
||||
TypeVarTupleType,
|
||||
TypeVarType,
|
||||
TypeVisitor,
|
||||
UnboundType,
|
||||
UninhabitedType,
|
||||
UnionType,
|
||||
UnpackType,
|
||||
)
|
||||
from mypy.util import get_prefix
|
||||
|
||||
# Snapshot representation of a symbol table node or type. The representation is
|
||||
# opaque -- the only supported operations are comparing for equality and
|
||||
# hashing (latter for type snapshots only). Snapshots can contain primitive
|
||||
# objects, nested tuples, lists and dictionaries and primitive objects (type
|
||||
# snapshots are immutable).
|
||||
#
|
||||
# For example, the snapshot of the 'int' type is ('Instance', 'builtins.int', ()).
|
||||
|
||||
# Type snapshots are strict, they must be hashable and ordered (e.g. for Unions).
|
||||
Primitive: _TypeAlias = Union[str, float, int, bool] # float is for Literal[3.14] support.
|
||||
SnapshotItem: _TypeAlias = Tuple[Union[Primitive, "SnapshotItem"], ...]
|
||||
|
||||
# Symbol snapshots can be more lenient.
|
||||
SymbolSnapshot: _TypeAlias = Tuple[object, ...]
|
||||
|
||||
|
||||
def compare_symbol_table_snapshots(
|
||||
name_prefix: str, snapshot1: dict[str, SymbolSnapshot], snapshot2: dict[str, SymbolSnapshot]
|
||||
) -> set[str]:
|
||||
"""Return names that are different in two snapshots of a symbol table.
|
||||
|
||||
Only shallow (intra-module) differences are considered. References to things defined
|
||||
outside the module are compared based on the name of the target only.
|
||||
|
||||
Recurse into class symbol tables (if the class is defined in the target module).
|
||||
|
||||
Return a set of fully-qualified names (e.g., 'mod.func' or 'mod.Class.method').
|
||||
"""
|
||||
# Find names only defined only in one version.
|
||||
names1 = {f"{name_prefix}.{name}" for name in snapshot1}
|
||||
names2 = {f"{name_prefix}.{name}" for name in snapshot2}
|
||||
triggers = names1 ^ names2
|
||||
|
||||
# Look for names defined in both versions that are different.
|
||||
for name in set(snapshot1.keys()) & set(snapshot2.keys()):
|
||||
item1 = snapshot1[name]
|
||||
item2 = snapshot2[name]
|
||||
kind1 = item1[0]
|
||||
kind2 = item2[0]
|
||||
item_name = f"{name_prefix}.{name}"
|
||||
if kind1 != kind2:
|
||||
# Different kind of node in two snapshots -> trivially different.
|
||||
triggers.add(item_name)
|
||||
elif kind1 == "TypeInfo":
|
||||
if item1[:-1] != item2[:-1]:
|
||||
# Record major difference (outside class symbol tables).
|
||||
triggers.add(item_name)
|
||||
# Look for differences in nested class symbol table entries.
|
||||
assert isinstance(item1[-1], dict)
|
||||
assert isinstance(item2[-1], dict)
|
||||
triggers |= compare_symbol_table_snapshots(item_name, item1[-1], item2[-1])
|
||||
else:
|
||||
# Shallow node (no interesting internal structure). Just use equality.
|
||||
if snapshot1[name] != snapshot2[name]:
|
||||
triggers.add(item_name)
|
||||
|
||||
return triggers
|
||||
|
||||
|
||||
def snapshot_symbol_table(name_prefix: str, table: SymbolTable) -> dict[str, SymbolSnapshot]:
|
||||
"""Create a snapshot description that represents the state of a symbol table.
|
||||
|
||||
The snapshot has a representation based on nested tuples and dicts
|
||||
that makes it easy and fast to find differences.
|
||||
|
||||
Only "shallow" state is included in the snapshot -- references to
|
||||
things defined in other modules are represented just by the names of
|
||||
the targets.
|
||||
"""
|
||||
result: dict[str, SymbolSnapshot] = {}
|
||||
for name, symbol in table.items():
|
||||
node = symbol.node
|
||||
# TODO: cross_ref?
|
||||
fullname = node.fullname if node else None
|
||||
common = (fullname, symbol.kind, symbol.module_public)
|
||||
if isinstance(node, MypyFile):
|
||||
# This is a cross-reference to another module.
|
||||
# If the reference is busted because the other module is missing,
|
||||
# the node will be a "stale_info" TypeInfo produced by fixup,
|
||||
# but that doesn't really matter to us here.
|
||||
result[name] = ("Moduleref", common)
|
||||
elif isinstance(node, TypeVarExpr):
|
||||
result[name] = (
|
||||
"TypeVar",
|
||||
node.variance,
|
||||
[snapshot_type(value) for value in node.values],
|
||||
snapshot_type(node.upper_bound),
|
||||
snapshot_type(node.default),
|
||||
)
|
||||
elif isinstance(node, TypeAlias):
|
||||
result[name] = (
|
||||
"TypeAlias",
|
||||
snapshot_types(node.alias_tvars),
|
||||
node.normalized,
|
||||
node.no_args,
|
||||
snapshot_optional_type(node.target),
|
||||
)
|
||||
elif isinstance(node, ParamSpecExpr):
|
||||
result[name] = (
|
||||
"ParamSpec",
|
||||
node.variance,
|
||||
snapshot_type(node.upper_bound),
|
||||
snapshot_type(node.default),
|
||||
)
|
||||
elif isinstance(node, TypeVarTupleExpr):
|
||||
result[name] = (
|
||||
"TypeVarTuple",
|
||||
node.variance,
|
||||
snapshot_type(node.upper_bound),
|
||||
snapshot_type(node.default),
|
||||
)
|
||||
else:
|
||||
assert symbol.kind != UNBOUND_IMPORTED
|
||||
if node and get_prefix(node.fullname) != name_prefix:
|
||||
# This is a cross-reference to a node defined in another module.
|
||||
result[name] = ("CrossRef", common)
|
||||
else:
|
||||
result[name] = snapshot_definition(node, common)
|
||||
return result
|
||||
|
||||
|
||||
def snapshot_definition(node: SymbolNode | None, common: SymbolSnapshot) -> SymbolSnapshot:
|
||||
"""Create a snapshot description of a symbol table node.
|
||||
|
||||
The representation is nested tuples and dicts. Only externally
|
||||
visible attributes are included.
|
||||
"""
|
||||
if isinstance(node, FuncBase):
|
||||
# TODO: info
|
||||
if node.type:
|
||||
signature = snapshot_type(node.type)
|
||||
else:
|
||||
signature = snapshot_untyped_signature(node)
|
||||
impl: FuncDef | None = None
|
||||
if isinstance(node, FuncDef):
|
||||
impl = node
|
||||
elif isinstance(node, OverloadedFuncDef) and node.impl:
|
||||
impl = node.impl.func if isinstance(node.impl, Decorator) else node.impl
|
||||
is_trivial_body = impl.is_trivial_body if impl else False
|
||||
dataclass_transform_spec = find_dataclass_transform_spec(node)
|
||||
return (
|
||||
"Func",
|
||||
common,
|
||||
node.is_property,
|
||||
node.is_final,
|
||||
node.is_class,
|
||||
node.is_static,
|
||||
signature,
|
||||
is_trivial_body,
|
||||
dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None,
|
||||
)
|
||||
elif isinstance(node, Var):
|
||||
return ("Var", common, snapshot_optional_type(node.type), node.is_final)
|
||||
elif isinstance(node, Decorator):
|
||||
# Note that decorated methods are represented by Decorator instances in
|
||||
# a symbol table since we need to preserve information about the
|
||||
# decorated function (whether it's a class function, for
|
||||
# example). Top-level decorated functions, however, are represented by
|
||||
# the corresponding Var node, since that happens to provide enough
|
||||
# context.
|
||||
return (
|
||||
"Decorator",
|
||||
node.is_overload,
|
||||
snapshot_optional_type(node.var.type),
|
||||
snapshot_definition(node.func, common),
|
||||
)
|
||||
elif isinstance(node, TypeInfo):
|
||||
dataclass_transform_spec = node.dataclass_transform_spec
|
||||
if dataclass_transform_spec is None:
|
||||
dataclass_transform_spec = find_dataclass_transform_spec(node)
|
||||
|
||||
attrs = (
|
||||
node.is_abstract,
|
||||
node.is_enum,
|
||||
node.is_protocol,
|
||||
node.fallback_to_any,
|
||||
node.meta_fallback_to_any,
|
||||
node.is_named_tuple,
|
||||
node.is_newtype,
|
||||
# We need this to e.g. trigger metaclass calculation in subclasses.
|
||||
snapshot_optional_type(node.metaclass_type),
|
||||
snapshot_optional_type(node.tuple_type),
|
||||
snapshot_optional_type(node.typeddict_type),
|
||||
[base.fullname for base in node.mro],
|
||||
# Note that the structure of type variables is a part of the external interface,
|
||||
# since creating instances might fail, for example:
|
||||
# T = TypeVar('T', bound=int)
|
||||
# class C(Generic[T]):
|
||||
# ...
|
||||
# x: C[str] <- this is invalid, and needs to be re-checked if `T` changes.
|
||||
# An alternative would be to create both deps: <...> -> C, and <...> -> <C>,
|
||||
# but this currently seems a bit ad hoc.
|
||||
tuple(snapshot_type(tdef) for tdef in node.defn.type_vars),
|
||||
[snapshot_type(base) for base in node.bases],
|
||||
[snapshot_type(p) for p in node._promote],
|
||||
dataclass_transform_spec.serialize() if dataclass_transform_spec is not None else None,
|
||||
)
|
||||
prefix = node.fullname
|
||||
symbol_table = snapshot_symbol_table(prefix, node.names)
|
||||
# Special dependency for abstract attribute handling.
|
||||
symbol_table["(abstract)"] = ("Abstract", tuple(sorted(node.abstract_attributes)))
|
||||
return ("TypeInfo", common, attrs, symbol_table)
|
||||
else:
|
||||
# Other node types are handled elsewhere.
|
||||
assert False, type(node)
|
||||
|
||||
|
||||
def snapshot_type(typ: Type) -> SnapshotItem:
|
||||
"""Create a snapshot representation of a type using nested tuples."""
|
||||
return typ.accept(SnapshotTypeVisitor())
|
||||
|
||||
|
||||
def snapshot_optional_type(typ: Type | None) -> SnapshotItem:
|
||||
if typ:
|
||||
return snapshot_type(typ)
|
||||
else:
|
||||
return ("<not set>",)
|
||||
|
||||
|
||||
def snapshot_types(types: Sequence[Type]) -> SnapshotItem:
|
||||
return tuple(snapshot_type(item) for item in types)
|
||||
|
||||
|
||||
def snapshot_simple_type(typ: Type) -> SnapshotItem:
|
||||
return (type(typ).__name__,)
|
||||
|
||||
|
||||
def encode_optional_str(s: str | None) -> str:
|
||||
if s is None:
|
||||
return "<None>"
|
||||
else:
|
||||
return s
|
||||
|
||||
|
||||
class SnapshotTypeVisitor(TypeVisitor[SnapshotItem]):
|
||||
"""Creates a read-only, self-contained snapshot of a type object.
|
||||
|
||||
Properties of a snapshot:
|
||||
|
||||
- Contains (nested) tuples and other immutable primitive objects only.
|
||||
- References to AST nodes are replaced with full names of targets.
|
||||
- Has no references to mutable or non-primitive objects.
|
||||
- Two snapshots represent the same object if and only if they are
|
||||
equal.
|
||||
- Results must be sortable. It's important that tuples have
|
||||
consistent types and can't arbitrarily mix str and None values,
|
||||
for example, since they can't be compared.
|
||||
"""
|
||||
|
||||
def visit_unbound_type(self, typ: UnboundType) -> SnapshotItem:
|
||||
return (
|
||||
"UnboundType",
|
||||
typ.name,
|
||||
typ.optional,
|
||||
typ.empty_tuple_index,
|
||||
snapshot_types(typ.args),
|
||||
)
|
||||
|
||||
def visit_any(self, typ: AnyType) -> SnapshotItem:
|
||||
return snapshot_simple_type(typ)
|
||||
|
||||
def visit_none_type(self, typ: NoneType) -> SnapshotItem:
|
||||
return snapshot_simple_type(typ)
|
||||
|
||||
def visit_uninhabited_type(self, typ: UninhabitedType) -> SnapshotItem:
|
||||
return snapshot_simple_type(typ)
|
||||
|
||||
def visit_erased_type(self, typ: ErasedType) -> SnapshotItem:
|
||||
return snapshot_simple_type(typ)
|
||||
|
||||
def visit_deleted_type(self, typ: DeletedType) -> SnapshotItem:
|
||||
return snapshot_simple_type(typ)
|
||||
|
||||
def visit_instance(self, typ: Instance) -> SnapshotItem:
|
||||
return (
|
||||
"Instance",
|
||||
encode_optional_str(typ.type.fullname),
|
||||
snapshot_types(typ.args),
|
||||
("None",) if typ.last_known_value is None else snapshot_type(typ.last_known_value),
|
||||
)
|
||||
|
||||
def visit_type_var(self, typ: TypeVarType) -> SnapshotItem:
|
||||
return (
|
||||
"TypeVar",
|
||||
typ.name,
|
||||
typ.fullname,
|
||||
typ.id.raw_id,
|
||||
typ.id.meta_level,
|
||||
snapshot_types(typ.values),
|
||||
snapshot_type(typ.upper_bound),
|
||||
snapshot_type(typ.default),
|
||||
typ.variance,
|
||||
)
|
||||
|
||||
def visit_param_spec(self, typ: ParamSpecType) -> SnapshotItem:
|
||||
return (
|
||||
"ParamSpec",
|
||||
typ.id.raw_id,
|
||||
typ.id.meta_level,
|
||||
typ.flavor,
|
||||
snapshot_type(typ.upper_bound),
|
||||
snapshot_type(typ.default),
|
||||
)
|
||||
|
||||
def visit_type_var_tuple(self, typ: TypeVarTupleType) -> SnapshotItem:
|
||||
return (
|
||||
"TypeVarTupleType",
|
||||
typ.id.raw_id,
|
||||
typ.id.meta_level,
|
||||
snapshot_type(typ.upper_bound),
|
||||
snapshot_type(typ.default),
|
||||
)
|
||||
|
||||
def visit_unpack_type(self, typ: UnpackType) -> SnapshotItem:
|
||||
return ("UnpackType", snapshot_type(typ.type))
|
||||
|
||||
def visit_parameters(self, typ: Parameters) -> SnapshotItem:
|
||||
return (
|
||||
"Parameters",
|
||||
snapshot_types(typ.arg_types),
|
||||
tuple(encode_optional_str(name) for name in typ.arg_names),
|
||||
tuple(k.value for k in typ.arg_kinds),
|
||||
)
|
||||
|
||||
def visit_callable_type(self, typ: CallableType) -> SnapshotItem:
|
||||
if typ.is_generic():
|
||||
typ = self.normalize_callable_variables(typ)
|
||||
return (
|
||||
"CallableType",
|
||||
snapshot_types(typ.arg_types),
|
||||
snapshot_type(typ.ret_type),
|
||||
tuple(encode_optional_str(name) for name in typ.arg_names),
|
||||
tuple(k.value for k in typ.arg_kinds),
|
||||
typ.is_type_obj(),
|
||||
typ.is_ellipsis_args,
|
||||
snapshot_types(typ.variables),
|
||||
)
|
||||
|
||||
def normalize_callable_variables(self, typ: CallableType) -> CallableType:
|
||||
"""Normalize all type variable ids to run from -1 to -len(variables)."""
|
||||
tvs = []
|
||||
tvmap: dict[TypeVarId, Type] = {}
|
||||
for i, v in enumerate(typ.variables):
|
||||
tid = TypeVarId(-1 - i)
|
||||
if isinstance(v, TypeVarType):
|
||||
tv: TypeVarLikeType = v.copy_modified(id=tid)
|
||||
elif isinstance(v, TypeVarTupleType):
|
||||
tv = v.copy_modified(id=tid)
|
||||
else:
|
||||
assert isinstance(v, ParamSpecType)
|
||||
tv = v.copy_modified(id=tid)
|
||||
tvs.append(tv)
|
||||
tvmap[v.id] = tv
|
||||
return expand_type(typ, tvmap).copy_modified(variables=tvs)
|
||||
|
||||
def visit_tuple_type(self, typ: TupleType) -> SnapshotItem:
|
||||
return ("TupleType", snapshot_types(typ.items))
|
||||
|
||||
def visit_typeddict_type(self, typ: TypedDictType) -> SnapshotItem:
|
||||
items = tuple((key, snapshot_type(item_type)) for key, item_type in typ.items.items())
|
||||
required = tuple(sorted(typ.required_keys))
|
||||
return ("TypedDictType", items, required)
|
||||
|
||||
def visit_literal_type(self, typ: LiteralType) -> SnapshotItem:
|
||||
return ("LiteralType", snapshot_type(typ.fallback), typ.value)
|
||||
|
||||
def visit_union_type(self, typ: UnionType) -> SnapshotItem:
|
||||
# Sort and remove duplicates so that we can use equality to test for
|
||||
# equivalent union type snapshots.
|
||||
items = {snapshot_type(item) for item in typ.items}
|
||||
normalized = tuple(sorted(items))
|
||||
return ("UnionType", normalized)
|
||||
|
||||
def visit_overloaded(self, typ: Overloaded) -> SnapshotItem:
|
||||
return ("Overloaded", snapshot_types(typ.items))
|
||||
|
||||
def visit_partial_type(self, typ: PartialType) -> SnapshotItem:
|
||||
# A partial type is not fully defined, so the result is indeterminate. We shouldn't
|
||||
# get here.
|
||||
raise RuntimeError
|
||||
|
||||
def visit_type_type(self, typ: TypeType) -> SnapshotItem:
|
||||
return ("TypeType", snapshot_type(typ.item))
|
||||
|
||||
def visit_type_alias_type(self, typ: TypeAliasType) -> SnapshotItem:
|
||||
assert typ.alias is not None
|
||||
return ("TypeAliasType", typ.alias.fullname, snapshot_types(typ.args))
|
||||
|
||||
|
||||
def snapshot_untyped_signature(func: OverloadedFuncDef | FuncItem) -> SymbolSnapshot:
|
||||
"""Create a snapshot of the signature of a function that has no explicit signature.
|
||||
|
||||
If the arguments to a function without signature change, it must be
|
||||
considered as different. We have this special casing since we don't store
|
||||
the implicit signature anywhere, and we'd rather not construct new
|
||||
Callable objects in this module (the idea is to only read properties of
|
||||
the AST here).
|
||||
"""
|
||||
if isinstance(func, FuncItem):
|
||||
return (tuple(func.arg_names), tuple(func.arg_kinds))
|
||||
else:
|
||||
result: list[SymbolSnapshot] = []
|
||||
for item in func.items:
|
||||
if isinstance(item, Decorator):
|
||||
if item.var.type:
|
||||
result.append(snapshot_type(item.var.type))
|
||||
else:
|
||||
result.append(("DecoratorWithoutType",))
|
||||
else:
|
||||
result.append(snapshot_untyped_signature(item))
|
||||
return tuple(result)
|
||||
Binary file not shown.
562
venv/lib/python3.12/site-packages/mypy/server/astmerge.py
Normal file
562
venv/lib/python3.12/site-packages/mypy/server/astmerge.py
Normal file
@@ -0,0 +1,562 @@
|
||||
"""Merge a new version of a module AST and symbol table to older versions of those.
|
||||
|
||||
When the source code of a module has a change in fine-grained incremental mode,
|
||||
we build a new AST from the updated source. However, other parts of the program
|
||||
may have direct references to parts of the old AST (namely, those nodes exposed
|
||||
in the module symbol table). The merge operation changes the identities of new
|
||||
AST nodes that have a correspondence in the old AST to the old ones so that
|
||||
existing cross-references in other modules will continue to point to the correct
|
||||
nodes. Also internal cross-references within the new AST are replaced. AST nodes
|
||||
that aren't externally visible will get new, distinct object identities. This
|
||||
applies to most expression and statement nodes, for example.
|
||||
|
||||
We perform this merge operation so that we don't have to update all
|
||||
external references (which would be slow and fragile) or always perform
|
||||
translation when looking up references (which would be hard to retrofit).
|
||||
|
||||
The AST merge operation is performed after semantic analysis. Semantic
|
||||
analysis has to deal with potentially multiple aliases to certain AST
|
||||
nodes (in particular, MypyFile nodes). Type checking assumes that we
|
||||
don't have multiple variants of a single AST node visible to the type
|
||||
checker.
|
||||
|
||||
Discussion of some notable special cases:
|
||||
|
||||
* If a node is replaced with a different kind of node (say, a function is
|
||||
replaced with a class), we don't perform the merge. Fine-grained dependencies
|
||||
will be used to rebind all references to the node.
|
||||
|
||||
* If a function is replaced with another function with an identical signature,
|
||||
call sites continue to point to the same object (by identity) and don't need
|
||||
to be reprocessed. Similarly, if a class is replaced with a class that is
|
||||
sufficiently similar (MRO preserved, etc.), class references don't need any
|
||||
processing. A typical incremental update to a file only changes a few
|
||||
externally visible things in a module, and this means that often only few
|
||||
external references need any processing, even if the modified module is large.
|
||||
|
||||
* A no-op update of a module should not require any processing outside the
|
||||
module, since all relevant object identities are preserved.
|
||||
|
||||
* The AST diff operation (mypy.server.astdiff) and the top-level fine-grained
|
||||
incremental logic (mypy.server.update) handle the cases where the new AST has
|
||||
differences from the old one that may need to be propagated to elsewhere in the
|
||||
program.
|
||||
|
||||
See the main entry point merge_asts for more details.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import TypeVar, cast
|
||||
|
||||
from mypy.nodes import (
|
||||
MDEF,
|
||||
AssertTypeExpr,
|
||||
AssignmentStmt,
|
||||
Block,
|
||||
CallExpr,
|
||||
CastExpr,
|
||||
ClassDef,
|
||||
EnumCallExpr,
|
||||
FuncBase,
|
||||
FuncDef,
|
||||
LambdaExpr,
|
||||
MemberExpr,
|
||||
MypyFile,
|
||||
NamedTupleExpr,
|
||||
NameExpr,
|
||||
NewTypeExpr,
|
||||
OverloadedFuncDef,
|
||||
RefExpr,
|
||||
Statement,
|
||||
SuperExpr,
|
||||
SymbolNode,
|
||||
SymbolTable,
|
||||
TypeAlias,
|
||||
TypedDictExpr,
|
||||
TypeInfo,
|
||||
Var,
|
||||
)
|
||||
from mypy.traverser import TraverserVisitor
|
||||
from mypy.types import (
|
||||
AnyType,
|
||||
CallableArgument,
|
||||
CallableType,
|
||||
DeletedType,
|
||||
EllipsisType,
|
||||
ErasedType,
|
||||
Instance,
|
||||
LiteralType,
|
||||
NoneType,
|
||||
Overloaded,
|
||||
Parameters,
|
||||
ParamSpecType,
|
||||
PartialType,
|
||||
PlaceholderType,
|
||||
RawExpressionType,
|
||||
SyntheticTypeVisitor,
|
||||
TupleType,
|
||||
Type,
|
||||
TypeAliasType,
|
||||
TypedDictType,
|
||||
TypeList,
|
||||
TypeType,
|
||||
TypeVarTupleType,
|
||||
TypeVarType,
|
||||
UnboundType,
|
||||
UninhabitedType,
|
||||
UnionType,
|
||||
UnpackType,
|
||||
)
|
||||
from mypy.typestate import type_state
|
||||
from mypy.util import get_prefix, replace_object_state
|
||||
|
||||
|
||||
def merge_asts(
|
||||
old: MypyFile, old_symbols: SymbolTable, new: MypyFile, new_symbols: SymbolTable
|
||||
) -> None:
|
||||
"""Merge a new version of a module AST to a previous version.
|
||||
|
||||
The main idea is to preserve the identities of externally visible
|
||||
nodes in the old AST (that have a corresponding node in the new AST).
|
||||
All old node state (outside identity) will come from the new AST.
|
||||
|
||||
When this returns, 'old' will refer to the merged AST, but 'new_symbols'
|
||||
will be the new symbol table. 'new' and 'old_symbols' will no longer be
|
||||
valid.
|
||||
"""
|
||||
assert new.fullname == old.fullname
|
||||
# Find the mapping from new to old node identities for all nodes
|
||||
# whose identities should be preserved.
|
||||
replacement_map = replacement_map_from_symbol_table(
|
||||
old_symbols, new_symbols, prefix=old.fullname
|
||||
)
|
||||
# Also replace references to the new MypyFile node.
|
||||
replacement_map[new] = old
|
||||
# Perform replacements to everywhere within the new AST (not including symbol
|
||||
# tables).
|
||||
node = replace_nodes_in_ast(new, replacement_map)
|
||||
assert node is old
|
||||
# Also replace AST node references in the *new* symbol table (we'll
|
||||
# continue to use the new symbol table since it has all the new definitions
|
||||
# that have no correspondence in the old AST).
|
||||
replace_nodes_in_symbol_table(new_symbols, replacement_map)
|
||||
|
||||
|
||||
def replacement_map_from_symbol_table(
|
||||
old: SymbolTable, new: SymbolTable, prefix: str
|
||||
) -> dict[SymbolNode, SymbolNode]:
|
||||
"""Create a new-to-old object identity map by comparing two symbol table revisions.
|
||||
|
||||
Both symbol tables must refer to revisions of the same module id. The symbol tables
|
||||
are compared recursively (recursing into nested class symbol tables), but only within
|
||||
the given module prefix. Don't recurse into other modules accessible through the symbol
|
||||
table.
|
||||
"""
|
||||
replacements: dict[SymbolNode, SymbolNode] = {}
|
||||
for name, node in old.items():
|
||||
if name in new and (
|
||||
node.kind == MDEF or node.node and get_prefix(node.node.fullname) == prefix
|
||||
):
|
||||
new_node = new[name]
|
||||
if (
|
||||
type(new_node.node) == type(node.node) # noqa: E721
|
||||
and new_node.node
|
||||
and node.node
|
||||
and new_node.node.fullname == node.node.fullname
|
||||
and new_node.kind == node.kind
|
||||
):
|
||||
replacements[new_node.node] = node.node
|
||||
if isinstance(node.node, TypeInfo) and isinstance(new_node.node, TypeInfo):
|
||||
type_repl = replacement_map_from_symbol_table(
|
||||
node.node.names, new_node.node.names, prefix
|
||||
)
|
||||
replacements.update(type_repl)
|
||||
if node.node.special_alias and new_node.node.special_alias:
|
||||
replacements[new_node.node.special_alias] = node.node.special_alias
|
||||
return replacements
|
||||
|
||||
|
||||
def replace_nodes_in_ast(
|
||||
node: SymbolNode, replacements: dict[SymbolNode, SymbolNode]
|
||||
) -> SymbolNode:
|
||||
"""Replace all references to replacement map keys within an AST node, recursively.
|
||||
|
||||
Also replace the *identity* of any nodes that have replacements. Return the
|
||||
*replaced* version of the argument node (which may have a different identity, if
|
||||
it's included in the replacement map).
|
||||
"""
|
||||
visitor = NodeReplaceVisitor(replacements)
|
||||
node.accept(visitor)
|
||||
return replacements.get(node, node)
|
||||
|
||||
|
||||
SN = TypeVar("SN", bound=SymbolNode)
|
||||
|
||||
|
||||
class NodeReplaceVisitor(TraverserVisitor):
|
||||
"""Transform some nodes to new identities in an AST.
|
||||
|
||||
Only nodes that live in the symbol table may be
|
||||
replaced, which simplifies the implementation some. Also
|
||||
replace all references to the old identities.
|
||||
"""
|
||||
|
||||
def __init__(self, replacements: dict[SymbolNode, SymbolNode]) -> None:
|
||||
self.replacements = replacements
|
||||
|
||||
def visit_mypy_file(self, node: MypyFile) -> None:
|
||||
node = self.fixup(node)
|
||||
node.defs = self.replace_statements(node.defs)
|
||||
super().visit_mypy_file(node)
|
||||
|
||||
def visit_block(self, node: Block) -> None:
|
||||
node.body = self.replace_statements(node.body)
|
||||
super().visit_block(node)
|
||||
|
||||
def visit_func_def(self, node: FuncDef) -> None:
|
||||
node = self.fixup(node)
|
||||
self.process_base_func(node)
|
||||
super().visit_func_def(node)
|
||||
|
||||
def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> None:
|
||||
self.process_base_func(node)
|
||||
super().visit_overloaded_func_def(node)
|
||||
|
||||
def visit_class_def(self, node: ClassDef) -> None:
|
||||
# TODO additional things?
|
||||
node.info = self.fixup_and_reset_typeinfo(node.info)
|
||||
node.defs.body = self.replace_statements(node.defs.body)
|
||||
info = node.info
|
||||
for tv in node.type_vars:
|
||||
if isinstance(tv, TypeVarType):
|
||||
self.process_type_var_def(tv)
|
||||
if info:
|
||||
if info.is_named_tuple:
|
||||
self.process_synthetic_type_info(info)
|
||||
else:
|
||||
self.process_type_info(info)
|
||||
super().visit_class_def(node)
|
||||
|
||||
def process_base_func(self, node: FuncBase) -> None:
|
||||
self.fixup_type(node.type)
|
||||
node.info = self.fixup(node.info)
|
||||
if node.unanalyzed_type:
|
||||
# Unanalyzed types can have AST node references
|
||||
self.fixup_type(node.unanalyzed_type)
|
||||
|
||||
def process_type_var_def(self, tv: TypeVarType) -> None:
|
||||
for value in tv.values:
|
||||
self.fixup_type(value)
|
||||
self.fixup_type(tv.upper_bound)
|
||||
self.fixup_type(tv.default)
|
||||
|
||||
def process_param_spec_def(self, tv: ParamSpecType) -> None:
|
||||
self.fixup_type(tv.upper_bound)
|
||||
self.fixup_type(tv.default)
|
||||
|
||||
def process_type_var_tuple_def(self, tv: TypeVarTupleType) -> None:
|
||||
self.fixup_type(tv.upper_bound)
|
||||
self.fixup_type(tv.default)
|
||||
|
||||
def visit_assignment_stmt(self, node: AssignmentStmt) -> None:
|
||||
self.fixup_type(node.type)
|
||||
super().visit_assignment_stmt(node)
|
||||
|
||||
# Expressions
|
||||
|
||||
def visit_name_expr(self, node: NameExpr) -> None:
|
||||
self.visit_ref_expr(node)
|
||||
|
||||
def visit_member_expr(self, node: MemberExpr) -> None:
|
||||
if node.def_var:
|
||||
node.def_var = self.fixup(node.def_var)
|
||||
self.visit_ref_expr(node)
|
||||
super().visit_member_expr(node)
|
||||
|
||||
def visit_ref_expr(self, node: RefExpr) -> None:
|
||||
if node.node is not None:
|
||||
node.node = self.fixup(node.node)
|
||||
if isinstance(node.node, Var):
|
||||
# The Var node may be an orphan and won't otherwise be processed.
|
||||
node.node.accept(self)
|
||||
|
||||
def visit_namedtuple_expr(self, node: NamedTupleExpr) -> None:
|
||||
super().visit_namedtuple_expr(node)
|
||||
node.info = self.fixup_and_reset_typeinfo(node.info)
|
||||
self.process_synthetic_type_info(node.info)
|
||||
|
||||
def visit_cast_expr(self, node: CastExpr) -> None:
|
||||
super().visit_cast_expr(node)
|
||||
self.fixup_type(node.type)
|
||||
|
||||
def visit_assert_type_expr(self, node: AssertTypeExpr) -> None:
|
||||
super().visit_assert_type_expr(node)
|
||||
self.fixup_type(node.type)
|
||||
|
||||
def visit_super_expr(self, node: SuperExpr) -> None:
|
||||
super().visit_super_expr(node)
|
||||
if node.info is not None:
|
||||
node.info = self.fixup(node.info)
|
||||
|
||||
def visit_call_expr(self, node: CallExpr) -> None:
|
||||
super().visit_call_expr(node)
|
||||
if isinstance(node.analyzed, SymbolNode):
|
||||
node.analyzed = self.fixup(node.analyzed)
|
||||
|
||||
def visit_newtype_expr(self, node: NewTypeExpr) -> None:
|
||||
if node.info:
|
||||
node.info = self.fixup_and_reset_typeinfo(node.info)
|
||||
self.process_synthetic_type_info(node.info)
|
||||
self.fixup_type(node.old_type)
|
||||
super().visit_newtype_expr(node)
|
||||
|
||||
def visit_lambda_expr(self, node: LambdaExpr) -> None:
|
||||
node.info = self.fixup(node.info)
|
||||
super().visit_lambda_expr(node)
|
||||
|
||||
def visit_typeddict_expr(self, node: TypedDictExpr) -> None:
|
||||
super().visit_typeddict_expr(node)
|
||||
node.info = self.fixup_and_reset_typeinfo(node.info)
|
||||
self.process_synthetic_type_info(node.info)
|
||||
|
||||
def visit_enum_call_expr(self, node: EnumCallExpr) -> None:
|
||||
node.info = self.fixup_and_reset_typeinfo(node.info)
|
||||
self.process_synthetic_type_info(node.info)
|
||||
super().visit_enum_call_expr(node)
|
||||
|
||||
# Others
|
||||
|
||||
def visit_var(self, node: Var) -> None:
|
||||
node.info = self.fixup(node.info)
|
||||
self.fixup_type(node.type)
|
||||
super().visit_var(node)
|
||||
|
||||
def visit_type_alias(self, node: TypeAlias) -> None:
|
||||
self.fixup_type(node.target)
|
||||
for v in node.alias_tvars:
|
||||
self.fixup_type(v)
|
||||
super().visit_type_alias(node)
|
||||
|
||||
# Helpers
|
||||
|
||||
def fixup(self, node: SN) -> SN:
|
||||
if node in self.replacements:
|
||||
new = self.replacements[node]
|
||||
skip_slots: tuple[str, ...] = ()
|
||||
if isinstance(node, TypeInfo) and isinstance(new, TypeInfo):
|
||||
# Special case: special_alias is not exposed in symbol tables, but may appear
|
||||
# in external types (e.g. named tuples), so we need to update it manually.
|
||||
skip_slots = ("special_alias",)
|
||||
replace_object_state(new.special_alias, node.special_alias)
|
||||
replace_object_state(new, node, skip_slots=skip_slots)
|
||||
return cast(SN, new)
|
||||
return node
|
||||
|
||||
def fixup_and_reset_typeinfo(self, node: TypeInfo) -> TypeInfo:
|
||||
"""Fix-up type info and reset subtype caches.
|
||||
|
||||
This needs to be called at least once per each merged TypeInfo, as otherwise we
|
||||
may leak stale caches.
|
||||
"""
|
||||
if node in self.replacements:
|
||||
# The subclass relationships may change, so reset all caches relevant to the
|
||||
# old MRO.
|
||||
new = self.replacements[node]
|
||||
assert isinstance(new, TypeInfo)
|
||||
type_state.reset_all_subtype_caches_for(new)
|
||||
return self.fixup(node)
|
||||
|
||||
def fixup_type(self, typ: Type | None) -> None:
|
||||
if typ is not None:
|
||||
typ.accept(TypeReplaceVisitor(self.replacements))
|
||||
|
||||
def process_type_info(self, info: TypeInfo | None) -> None:
|
||||
if info is None:
|
||||
return
|
||||
self.fixup_type(info.declared_metaclass)
|
||||
self.fixup_type(info.metaclass_type)
|
||||
for target in info._promote:
|
||||
self.fixup_type(target)
|
||||
self.fixup_type(info.tuple_type)
|
||||
self.fixup_type(info.typeddict_type)
|
||||
if info.special_alias:
|
||||
self.fixup_type(info.special_alias.target)
|
||||
info.defn.info = self.fixup(info)
|
||||
replace_nodes_in_symbol_table(info.names, self.replacements)
|
||||
for i, item in enumerate(info.mro):
|
||||
info.mro[i] = self.fixup(info.mro[i])
|
||||
for i, base in enumerate(info.bases):
|
||||
self.fixup_type(info.bases[i])
|
||||
|
||||
def process_synthetic_type_info(self, info: TypeInfo) -> None:
|
||||
# Synthetic types (types not created using a class statement) don't
|
||||
# have bodies in the AST so we need to iterate over their symbol
|
||||
# tables separately, unlike normal classes.
|
||||
self.process_type_info(info)
|
||||
for name, node in info.names.items():
|
||||
if node.node:
|
||||
node.node.accept(self)
|
||||
|
||||
def replace_statements(self, nodes: list[Statement]) -> list[Statement]:
|
||||
result = []
|
||||
for node in nodes:
|
||||
if isinstance(node, SymbolNode):
|
||||
node = self.fixup(node)
|
||||
result.append(node)
|
||||
return result
|
||||
|
||||
|
||||
class TypeReplaceVisitor(SyntheticTypeVisitor[None]):
|
||||
"""Similar to NodeReplaceVisitor, but for type objects.
|
||||
|
||||
Note: this visitor may sometimes visit unanalyzed types
|
||||
such as 'UnboundType' and 'RawExpressionType' For example, see
|
||||
NodeReplaceVisitor.process_base_func.
|
||||
"""
|
||||
|
||||
def __init__(self, replacements: dict[SymbolNode, SymbolNode]) -> None:
|
||||
self.replacements = replacements
|
||||
|
||||
def visit_instance(self, typ: Instance) -> None:
|
||||
typ.type = self.fixup(typ.type)
|
||||
for arg in typ.args:
|
||||
arg.accept(self)
|
||||
if typ.last_known_value:
|
||||
typ.last_known_value.accept(self)
|
||||
|
||||
def visit_type_alias_type(self, typ: TypeAliasType) -> None:
|
||||
assert typ.alias is not None
|
||||
typ.alias = self.fixup(typ.alias)
|
||||
for arg in typ.args:
|
||||
arg.accept(self)
|
||||
|
||||
def visit_any(self, typ: AnyType) -> None:
|
||||
pass
|
||||
|
||||
def visit_none_type(self, typ: NoneType) -> None:
|
||||
pass
|
||||
|
||||
def visit_callable_type(self, typ: CallableType) -> None:
|
||||
for arg in typ.arg_types:
|
||||
arg.accept(self)
|
||||
typ.ret_type.accept(self)
|
||||
if typ.definition:
|
||||
# No need to fixup since this is just a cross-reference.
|
||||
typ.definition = self.replacements.get(typ.definition, typ.definition)
|
||||
# Fallback can be None for callable types that haven't been semantically analyzed.
|
||||
if typ.fallback is not None:
|
||||
typ.fallback.accept(self)
|
||||
for tv in typ.variables:
|
||||
if isinstance(tv, TypeVarType):
|
||||
tv.upper_bound.accept(self)
|
||||
for value in tv.values:
|
||||
value.accept(self)
|
||||
|
||||
def visit_overloaded(self, t: Overloaded) -> None:
|
||||
for item in t.items:
|
||||
item.accept(self)
|
||||
# Fallback can be None for overloaded types that haven't been semantically analyzed.
|
||||
if t.fallback is not None:
|
||||
t.fallback.accept(self)
|
||||
|
||||
def visit_erased_type(self, t: ErasedType) -> None:
|
||||
# This type should exist only temporarily during type inference
|
||||
raise RuntimeError("Cannot handle erased type")
|
||||
|
||||
def visit_deleted_type(self, typ: DeletedType) -> None:
|
||||
pass
|
||||
|
||||
def visit_partial_type(self, typ: PartialType) -> None:
|
||||
raise RuntimeError("Cannot handle partial type")
|
||||
|
||||
def visit_tuple_type(self, typ: TupleType) -> None:
|
||||
for item in typ.items:
|
||||
item.accept(self)
|
||||
# Fallback can be None for implicit tuple types that haven't been semantically analyzed.
|
||||
if typ.partial_fallback is not None:
|
||||
typ.partial_fallback.accept(self)
|
||||
|
||||
def visit_type_type(self, typ: TypeType) -> None:
|
||||
typ.item.accept(self)
|
||||
|
||||
def visit_type_var(self, typ: TypeVarType) -> None:
|
||||
typ.upper_bound.accept(self)
|
||||
typ.default.accept(self)
|
||||
for value in typ.values:
|
||||
value.accept(self)
|
||||
|
||||
def visit_param_spec(self, typ: ParamSpecType) -> None:
|
||||
typ.upper_bound.accept(self)
|
||||
typ.default.accept(self)
|
||||
|
||||
def visit_type_var_tuple(self, typ: TypeVarTupleType) -> None:
|
||||
typ.upper_bound.accept(self)
|
||||
typ.default.accept(self)
|
||||
|
||||
def visit_unpack_type(self, typ: UnpackType) -> None:
|
||||
typ.type.accept(self)
|
||||
|
||||
def visit_parameters(self, typ: Parameters) -> None:
|
||||
for arg in typ.arg_types:
|
||||
arg.accept(self)
|
||||
|
||||
def visit_typeddict_type(self, typ: TypedDictType) -> None:
|
||||
for value_type in typ.items.values():
|
||||
value_type.accept(self)
|
||||
typ.fallback.accept(self)
|
||||
|
||||
def visit_raw_expression_type(self, t: RawExpressionType) -> None:
|
||||
pass
|
||||
|
||||
def visit_literal_type(self, typ: LiteralType) -> None:
|
||||
typ.fallback.accept(self)
|
||||
|
||||
def visit_unbound_type(self, typ: UnboundType) -> None:
|
||||
for arg in typ.args:
|
||||
arg.accept(self)
|
||||
|
||||
def visit_type_list(self, typ: TypeList) -> None:
|
||||
for item in typ.items:
|
||||
item.accept(self)
|
||||
|
||||
def visit_callable_argument(self, typ: CallableArgument) -> None:
|
||||
typ.typ.accept(self)
|
||||
|
||||
def visit_ellipsis_type(self, typ: EllipsisType) -> None:
|
||||
pass
|
||||
|
||||
def visit_uninhabited_type(self, typ: UninhabitedType) -> None:
|
||||
pass
|
||||
|
||||
def visit_union_type(self, typ: UnionType) -> None:
|
||||
for item in typ.items:
|
||||
item.accept(self)
|
||||
|
||||
def visit_placeholder_type(self, t: PlaceholderType) -> None:
|
||||
for item in t.args:
|
||||
item.accept(self)
|
||||
|
||||
# Helpers
|
||||
|
||||
def fixup(self, node: SN) -> SN:
|
||||
if node in self.replacements:
|
||||
new = self.replacements[node]
|
||||
return cast(SN, new)
|
||||
return node
|
||||
|
||||
|
||||
def replace_nodes_in_symbol_table(
|
||||
symbols: SymbolTable, replacements: dict[SymbolNode, SymbolNode]
|
||||
) -> None:
|
||||
for name, node in symbols.items():
|
||||
if node.node:
|
||||
if node.node in replacements:
|
||||
new = replacements[node.node]
|
||||
old = node.node
|
||||
# Needed for TypeInfo, see comment in fixup() above.
|
||||
replace_object_state(new, old, skip_slots=("special_alias",))
|
||||
node.node = new
|
||||
if isinstance(node.node, (Var, TypeAlias)):
|
||||
# Handle them here just in case these aren't exposed through the AST.
|
||||
node.node.accept(NodeReplaceVisitor(replacements))
|
||||
Binary file not shown.
281
venv/lib/python3.12/site-packages/mypy/server/aststrip.py
Normal file
281
venv/lib/python3.12/site-packages/mypy/server/aststrip.py
Normal file
@@ -0,0 +1,281 @@
|
||||
"""Strip/reset AST in-place to match state after semantic analyzer pre-analysis.
|
||||
|
||||
Fine-grained incremental mode reruns semantic analysis main pass
|
||||
and type checking for *existing* AST nodes (targets) when changes are
|
||||
propagated using fine-grained dependencies. AST nodes attributes are
|
||||
sometimes changed during semantic analysis main pass, and running
|
||||
semantic analysis again on those nodes would produce incorrect
|
||||
results, since this pass isn't idempotent. This pass resets AST
|
||||
nodes to reflect the state after semantic pre-analysis, so that we
|
||||
can rerun semantic analysis.
|
||||
(The above is in contrast to behavior with modules that have source code
|
||||
changes, for which we re-parse the entire module and reconstruct a fresh
|
||||
AST. No stripping is required in this case. Both modes of operation should
|
||||
have the same outcome.)
|
||||
Notes:
|
||||
* This is currently pretty fragile, as we must carefully undo whatever
|
||||
changes can be made in semantic analysis main pass, including changes
|
||||
to symbol tables.
|
||||
* We reuse existing AST nodes because it makes it relatively straightforward
|
||||
to reprocess only a single target within a module efficiently. If there
|
||||
was a way to parse a single target within a file, in time proportional to
|
||||
the size of the target, we'd rather create fresh AST nodes than strip them.
|
||||
(This is possible only in Python 3.8+)
|
||||
* Currently we don't actually reset all changes, but only those known to affect
|
||||
non-idempotent semantic analysis behavior.
|
||||
TODO: It would be more principled and less fragile to reset everything
|
||||
changed in semantic analysis main pass and later.
|
||||
* Reprocessing may recreate AST nodes (such as Var nodes, and TypeInfo nodes
|
||||
created with assignment statements) that will get different identities from
|
||||
the original AST. Thus running an AST merge is necessary after stripping,
|
||||
even though some identities are preserved.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from contextlib import contextmanager, nullcontext
|
||||
from typing import Dict, Iterator, Tuple
|
||||
from typing_extensions import TypeAlias as _TypeAlias
|
||||
|
||||
from mypy.nodes import (
|
||||
CLASSDEF_NO_INFO,
|
||||
AssignmentStmt,
|
||||
Block,
|
||||
CallExpr,
|
||||
ClassDef,
|
||||
Decorator,
|
||||
ForStmt,
|
||||
FuncDef,
|
||||
ImportAll,
|
||||
ImportFrom,
|
||||
IndexExpr,
|
||||
ListExpr,
|
||||
MemberExpr,
|
||||
MypyFile,
|
||||
NameExpr,
|
||||
Node,
|
||||
OpExpr,
|
||||
OverloadedFuncDef,
|
||||
RefExpr,
|
||||
StarExpr,
|
||||
SuperExpr,
|
||||
SymbolTableNode,
|
||||
TupleExpr,
|
||||
TypeInfo,
|
||||
Var,
|
||||
)
|
||||
from mypy.traverser import TraverserVisitor
|
||||
from mypy.types import CallableType
|
||||
from mypy.typestate import type_state
|
||||
|
||||
SavedAttributes: _TypeAlias = Dict[Tuple[ClassDef, str], SymbolTableNode]
|
||||
|
||||
|
||||
def strip_target(
|
||||
node: MypyFile | FuncDef | OverloadedFuncDef, saved_attrs: SavedAttributes
|
||||
) -> None:
|
||||
"""Reset a fine-grained incremental target to state before semantic analysis.
|
||||
|
||||
All TypeInfos are killed. Therefore we need to preserve the variables
|
||||
defined as attributes on self. This is done by patches (callbacks)
|
||||
returned from this function that re-add these variables when called.
|
||||
|
||||
Args:
|
||||
node: node to strip
|
||||
saved_attrs: collect attributes here that may need to be re-added to
|
||||
classes afterwards if stripping a class body (this dict is mutated)
|
||||
"""
|
||||
visitor = NodeStripVisitor(saved_attrs)
|
||||
if isinstance(node, MypyFile):
|
||||
visitor.strip_file_top_level(node)
|
||||
else:
|
||||
node.accept(visitor)
|
||||
|
||||
|
||||
class NodeStripVisitor(TraverserVisitor):
|
||||
def __init__(self, saved_class_attrs: SavedAttributes) -> None:
|
||||
# The current active class.
|
||||
self.type: TypeInfo | None = None
|
||||
# This is True at class scope, but not in methods.
|
||||
self.is_class_body = False
|
||||
# By default, process function definitions. If False, don't -- this is used for
|
||||
# processing module top levels.
|
||||
self.recurse_into_functions = True
|
||||
# These attributes were removed from top-level classes during strip and
|
||||
# will be added afterwards (if no existing definition is found). These
|
||||
# must be added back before semantically analyzing any methods.
|
||||
self.saved_class_attrs = saved_class_attrs
|
||||
|
||||
def strip_file_top_level(self, file_node: MypyFile) -> None:
|
||||
"""Strip a module top-level (don't recursive into functions)."""
|
||||
self.recurse_into_functions = False
|
||||
file_node.plugin_deps.clear()
|
||||
file_node.accept(self)
|
||||
for name in file_node.names.copy():
|
||||
# TODO: this is a hot fix, we should delete all names,
|
||||
# see https://github.com/python/mypy/issues/6422.
|
||||
if "@" not in name:
|
||||
del file_node.names[name]
|
||||
|
||||
def visit_block(self, b: Block) -> None:
|
||||
if b.is_unreachable:
|
||||
return
|
||||
super().visit_block(b)
|
||||
|
||||
def visit_class_def(self, node: ClassDef) -> None:
|
||||
"""Strip class body and type info, but don't strip methods."""
|
||||
# We need to save the implicitly defined instance variables,
|
||||
# i.e. those defined as attributes on self. Otherwise, they would
|
||||
# be lost if we only reprocess top-levels (this kills TypeInfos)
|
||||
# but not the methods that defined those variables.
|
||||
if not self.recurse_into_functions:
|
||||
self.save_implicit_attributes(node)
|
||||
# We need to delete any entries that were generated by plugins,
|
||||
# since they will get regenerated.
|
||||
to_delete = {v.node for v in node.info.names.values() if v.plugin_generated}
|
||||
node.type_vars = []
|
||||
node.base_type_exprs.extend(node.removed_base_type_exprs)
|
||||
node.removed_base_type_exprs = []
|
||||
node.defs.body = [
|
||||
s for s in node.defs.body if s not in to_delete # type: ignore[comparison-overlap]
|
||||
]
|
||||
with self.enter_class(node.info):
|
||||
super().visit_class_def(node)
|
||||
node.defs.body.extend(node.removed_statements)
|
||||
node.removed_statements = []
|
||||
type_state.reset_subtype_caches_for(node.info)
|
||||
# Kill the TypeInfo, since there is none before semantic analysis.
|
||||
node.info = CLASSDEF_NO_INFO
|
||||
node.analyzed = None
|
||||
|
||||
def save_implicit_attributes(self, node: ClassDef) -> None:
|
||||
"""Produce callbacks that re-add attributes defined on self."""
|
||||
for name, sym in node.info.names.items():
|
||||
if isinstance(sym.node, Var) and sym.implicit:
|
||||
self.saved_class_attrs[node, name] = sym
|
||||
|
||||
def visit_func_def(self, node: FuncDef) -> None:
|
||||
if not self.recurse_into_functions:
|
||||
return
|
||||
node.expanded = []
|
||||
node.type = node.unanalyzed_type
|
||||
if node.type:
|
||||
# Type variable binder binds type variables before the type is analyzed,
|
||||
# this causes unanalyzed_type to be modified in place. We needed to revert this
|
||||
# in order to get the state exactly as it was before semantic analysis.
|
||||
# See also #4814.
|
||||
assert isinstance(node.type, CallableType)
|
||||
node.type.variables = []
|
||||
with self.enter_method(node.info) if node.info else nullcontext():
|
||||
super().visit_func_def(node)
|
||||
|
||||
def visit_decorator(self, node: Decorator) -> None:
|
||||
node.var.type = None
|
||||
for expr in node.decorators:
|
||||
expr.accept(self)
|
||||
if self.recurse_into_functions:
|
||||
node.func.accept(self)
|
||||
else:
|
||||
# Only touch the final status if we re-process
|
||||
# the top level, since decorators are processed there.
|
||||
node.var.is_final = False
|
||||
node.func.is_final = False
|
||||
|
||||
def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> None:
|
||||
if not self.recurse_into_functions:
|
||||
return
|
||||
# Revert change made during semantic analysis main pass.
|
||||
node.items = node.unanalyzed_items.copy()
|
||||
node.impl = None
|
||||
node.is_final = False
|
||||
super().visit_overloaded_func_def(node)
|
||||
|
||||
def visit_assignment_stmt(self, node: AssignmentStmt) -> None:
|
||||
node.type = node.unanalyzed_type
|
||||
node.is_final_def = False
|
||||
node.is_alias_def = False
|
||||
if self.type and not self.is_class_body:
|
||||
for lvalue in node.lvalues:
|
||||
# Revert assignments made via self attributes.
|
||||
self.process_lvalue_in_method(lvalue)
|
||||
super().visit_assignment_stmt(node)
|
||||
|
||||
def visit_import_from(self, node: ImportFrom) -> None:
|
||||
node.assignments = []
|
||||
|
||||
def visit_import_all(self, node: ImportAll) -> None:
|
||||
node.assignments = []
|
||||
|
||||
def visit_for_stmt(self, node: ForStmt) -> None:
|
||||
node.index_type = node.unanalyzed_index_type
|
||||
node.inferred_item_type = None
|
||||
node.inferred_iterator_type = None
|
||||
super().visit_for_stmt(node)
|
||||
|
||||
def visit_name_expr(self, node: NameExpr) -> None:
|
||||
self.strip_ref_expr(node)
|
||||
|
||||
def visit_member_expr(self, node: MemberExpr) -> None:
|
||||
self.strip_ref_expr(node)
|
||||
super().visit_member_expr(node)
|
||||
|
||||
def visit_index_expr(self, node: IndexExpr) -> None:
|
||||
node.analyzed = None # May have been an alias or type application.
|
||||
super().visit_index_expr(node)
|
||||
|
||||
def visit_op_expr(self, node: OpExpr) -> None:
|
||||
node.analyzed = None # May have been an alias
|
||||
super().visit_op_expr(node)
|
||||
|
||||
def strip_ref_expr(self, node: RefExpr) -> None:
|
||||
node.kind = None
|
||||
node.node = None
|
||||
node.fullname = ""
|
||||
node.is_new_def = False
|
||||
node.is_inferred_def = False
|
||||
|
||||
def visit_call_expr(self, node: CallExpr) -> None:
|
||||
node.analyzed = None
|
||||
super().visit_call_expr(node)
|
||||
|
||||
def visit_super_expr(self, node: SuperExpr) -> None:
|
||||
node.info = None
|
||||
super().visit_super_expr(node)
|
||||
|
||||
def process_lvalue_in_method(self, lvalue: Node) -> None:
|
||||
if isinstance(lvalue, MemberExpr):
|
||||
if lvalue.is_new_def:
|
||||
# Remove defined attribute from the class symbol table. If is_new_def is
|
||||
# true for a MemberExpr, we know that it must be an assignment through
|
||||
# self, since only those can define new attributes.
|
||||
assert self.type is not None
|
||||
if lvalue.name in self.type.names:
|
||||
del self.type.names[lvalue.name]
|
||||
key = (self.type.defn, lvalue.name)
|
||||
if key in self.saved_class_attrs:
|
||||
del self.saved_class_attrs[key]
|
||||
elif isinstance(lvalue, (TupleExpr, ListExpr)):
|
||||
for item in lvalue.items:
|
||||
self.process_lvalue_in_method(item)
|
||||
elif isinstance(lvalue, StarExpr):
|
||||
self.process_lvalue_in_method(lvalue.expr)
|
||||
|
||||
@contextmanager
|
||||
def enter_class(self, info: TypeInfo) -> Iterator[None]:
|
||||
old_type = self.type
|
||||
old_is_class_body = self.is_class_body
|
||||
self.type = info
|
||||
self.is_class_body = True
|
||||
yield
|
||||
self.type = old_type
|
||||
self.is_class_body = old_is_class_body
|
||||
|
||||
@contextmanager
|
||||
def enter_method(self, info: TypeInfo) -> Iterator[None]:
|
||||
old_type = self.type
|
||||
old_is_class_body = self.is_class_body
|
||||
self.type = info
|
||||
self.is_class_body = False
|
||||
yield
|
||||
self.type = old_type
|
||||
self.is_class_body = old_is_class_body
|
||||
Binary file not shown.
1137
venv/lib/python3.12/site-packages/mypy/server/deps.py
Normal file
1137
venv/lib/python3.12/site-packages/mypy/server/deps.py
Normal file
File diff suppressed because it is too large
Load Diff
Binary file not shown.
83
venv/lib/python3.12/site-packages/mypy/server/mergecheck.py
Normal file
83
venv/lib/python3.12/site-packages/mypy/server/mergecheck.py
Normal file
@@ -0,0 +1,83 @@
|
||||
"""Check for duplicate AST nodes after merge."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
from mypy.nodes import Decorator, FakeInfo, FuncDef, SymbolNode, Var
|
||||
from mypy.server.objgraph import get_path, get_reachable_graph
|
||||
|
||||
# If True, print more verbose output on failure.
|
||||
DUMP_MISMATCH_NODES: Final = False
|
||||
|
||||
|
||||
def check_consistency(o: object) -> None:
|
||||
"""Fail if there are two AST nodes with the same fullname reachable from 'o'.
|
||||
|
||||
Raise AssertionError on failure and print some debugging output.
|
||||
"""
|
||||
seen, parents = get_reachable_graph(o)
|
||||
reachable = list(seen.values())
|
||||
syms = [x for x in reachable if isinstance(x, SymbolNode)]
|
||||
|
||||
m: dict[str, SymbolNode] = {}
|
||||
for sym in syms:
|
||||
if isinstance(sym, FakeInfo):
|
||||
continue
|
||||
|
||||
fn = sym.fullname
|
||||
# Skip None names, since they are ambiguous.
|
||||
# TODO: Everything should have a proper full name?
|
||||
if fn is None:
|
||||
continue
|
||||
# Skip stuff that should be expected to have duplicate names
|
||||
if isinstance(sym, (Var, Decorator)):
|
||||
continue
|
||||
if isinstance(sym, FuncDef) and sym.is_overload:
|
||||
continue
|
||||
|
||||
if fn not in m:
|
||||
m[sym.fullname] = sym
|
||||
continue
|
||||
|
||||
# We have trouble and need to decide what to do about it.
|
||||
sym1, sym2 = sym, m[fn]
|
||||
|
||||
# If the type changed, then it shouldn't have been merged.
|
||||
if type(sym1) is not type(sym2):
|
||||
continue
|
||||
|
||||
path1 = get_path(sym1, seen, parents)
|
||||
path2 = get_path(sym2, seen, parents)
|
||||
|
||||
if fn in m:
|
||||
print(f"\nDuplicate {type(sym).__name__!r} nodes with fullname {fn!r} found:")
|
||||
print("[1] %d: %s" % (id(sym1), path_to_str(path1)))
|
||||
print("[2] %d: %s" % (id(sym2), path_to_str(path2)))
|
||||
|
||||
if DUMP_MISMATCH_NODES and fn in m:
|
||||
# Add verbose output with full AST node contents.
|
||||
print("---")
|
||||
print(id(sym1), sym1)
|
||||
print("---")
|
||||
print(id(sym2), sym2)
|
||||
|
||||
assert sym.fullname not in m
|
||||
|
||||
|
||||
def path_to_str(path: list[tuple[object, object]]) -> str:
|
||||
result = "<root>"
|
||||
for attr, obj in path:
|
||||
t = type(obj).__name__
|
||||
if t in ("dict", "tuple", "SymbolTable", "list"):
|
||||
result += f"[{repr(attr)}]"
|
||||
else:
|
||||
if isinstance(obj, Var):
|
||||
result += f".{attr}({t}:{obj.name})"
|
||||
elif t in ("BuildManager", "FineGrainedBuildManager"):
|
||||
# Omit class name for some classes that aren't part of a class
|
||||
# hierarchy since there isn't much ambiguity.
|
||||
result += f".{attr}"
|
||||
else:
|
||||
result += f".{attr}({t})"
|
||||
return result
|
||||
Binary file not shown.
101
venv/lib/python3.12/site-packages/mypy/server/objgraph.py
Normal file
101
venv/lib/python3.12/site-packages/mypy/server/objgraph.py
Normal file
@@ -0,0 +1,101 @@
|
||||
"""Find all objects reachable from a root object."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import types
|
||||
import weakref
|
||||
from collections.abc import Iterable
|
||||
from typing import Final, Iterator, Mapping
|
||||
|
||||
method_descriptor_type: Final = type(object.__dir__)
|
||||
method_wrapper_type: Final = type(object().__ne__)
|
||||
wrapper_descriptor_type: Final = type(object.__ne__)
|
||||
|
||||
FUNCTION_TYPES: Final = (
|
||||
types.BuiltinFunctionType,
|
||||
types.FunctionType,
|
||||
types.MethodType,
|
||||
method_descriptor_type,
|
||||
wrapper_descriptor_type,
|
||||
method_wrapper_type,
|
||||
)
|
||||
|
||||
ATTR_BLACKLIST: Final = {"__doc__", "__name__", "__class__", "__dict__"}
|
||||
|
||||
# Instances of these types can't have references to other objects
|
||||
ATOMIC_TYPE_BLACKLIST: Final = {bool, int, float, str, type(None), object}
|
||||
|
||||
# Don't look at most attributes of these types
|
||||
COLLECTION_TYPE_BLACKLIST: Final = {list, set, dict, tuple}
|
||||
|
||||
# Don't return these objects
|
||||
TYPE_BLACKLIST: Final = {weakref.ReferenceType}
|
||||
|
||||
|
||||
def isproperty(o: object, attr: str) -> bool:
|
||||
return isinstance(getattr(type(o), attr, None), property)
|
||||
|
||||
|
||||
def get_edge_candidates(o: object) -> Iterator[tuple[object, object]]:
|
||||
# use getattr because mypyc expects dict, not mappingproxy
|
||||
if "__getattribute__" in getattr(type(o), "__dict__"): # noqa: B009
|
||||
return
|
||||
if type(o) not in COLLECTION_TYPE_BLACKLIST:
|
||||
for attr in dir(o):
|
||||
try:
|
||||
if attr not in ATTR_BLACKLIST and hasattr(o, attr) and not isproperty(o, attr):
|
||||
e = getattr(o, attr)
|
||||
if type(e) not in ATOMIC_TYPE_BLACKLIST:
|
||||
yield attr, e
|
||||
except AssertionError:
|
||||
pass
|
||||
if isinstance(o, Mapping):
|
||||
yield from o.items()
|
||||
elif isinstance(o, Iterable) and not isinstance(o, str):
|
||||
for i, e in enumerate(o):
|
||||
yield i, e
|
||||
|
||||
|
||||
def get_edges(o: object) -> Iterator[tuple[object, object]]:
|
||||
for s, e in get_edge_candidates(o):
|
||||
if isinstance(e, FUNCTION_TYPES):
|
||||
# We don't want to collect methods, but do want to collect values
|
||||
# in closures and self pointers to other objects
|
||||
|
||||
if hasattr(e, "__closure__"):
|
||||
yield (s, "__closure__"), e.__closure__
|
||||
if hasattr(e, "__self__"):
|
||||
se = e.__self__
|
||||
if se is not o and se is not type(o) and hasattr(s, "__self__"):
|
||||
yield s.__self__, se
|
||||
else:
|
||||
if type(e) not in TYPE_BLACKLIST:
|
||||
yield s, e
|
||||
|
||||
|
||||
def get_reachable_graph(root: object) -> tuple[dict[int, object], dict[int, tuple[int, object]]]:
|
||||
parents = {}
|
||||
seen = {id(root): root}
|
||||
worklist = [root]
|
||||
while worklist:
|
||||
o = worklist.pop()
|
||||
for s, e in get_edges(o):
|
||||
if id(e) in seen:
|
||||
continue
|
||||
parents[id(e)] = (id(o), s)
|
||||
seen[id(e)] = e
|
||||
worklist.append(e)
|
||||
|
||||
return seen, parents
|
||||
|
||||
|
||||
def get_path(
|
||||
o: object, seen: dict[int, object], parents: dict[int, tuple[int, object]]
|
||||
) -> list[tuple[object, object]]:
|
||||
path = []
|
||||
while id(o) in parents:
|
||||
pid, attr = parents[id(o)]
|
||||
o = seen[pid]
|
||||
path.append((attr, o))
|
||||
path.reverse()
|
||||
return path
|
||||
Binary file not shown.
198
venv/lib/python3.12/site-packages/mypy/server/subexpr.py
Normal file
198
venv/lib/python3.12/site-packages/mypy/server/subexpr.py
Normal file
@@ -0,0 +1,198 @@
|
||||
"""Find all subexpressions of an AST node."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from mypy.nodes import (
|
||||
AssertTypeExpr,
|
||||
AssignmentExpr,
|
||||
AwaitExpr,
|
||||
CallExpr,
|
||||
CastExpr,
|
||||
ComparisonExpr,
|
||||
ConditionalExpr,
|
||||
DictExpr,
|
||||
DictionaryComprehension,
|
||||
Expression,
|
||||
GeneratorExpr,
|
||||
IndexExpr,
|
||||
LambdaExpr,
|
||||
ListComprehension,
|
||||
ListExpr,
|
||||
MemberExpr,
|
||||
Node,
|
||||
OpExpr,
|
||||
RevealExpr,
|
||||
SetComprehension,
|
||||
SetExpr,
|
||||
SliceExpr,
|
||||
StarExpr,
|
||||
TupleExpr,
|
||||
TypeApplication,
|
||||
UnaryExpr,
|
||||
YieldExpr,
|
||||
YieldFromExpr,
|
||||
)
|
||||
from mypy.traverser import TraverserVisitor
|
||||
|
||||
|
||||
def get_subexpressions(node: Node) -> list[Expression]:
|
||||
visitor = SubexpressionFinder()
|
||||
node.accept(visitor)
|
||||
return visitor.expressions
|
||||
|
||||
|
||||
class SubexpressionFinder(TraverserVisitor):
|
||||
def __init__(self) -> None:
|
||||
self.expressions: list[Expression] = []
|
||||
|
||||
def visit_int_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_name_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_float_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_str_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_bytes_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_unicode_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_complex_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_ellipsis(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_super_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_type_var_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_type_alias_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_namedtuple_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_typeddict_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit__promote_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_newtype_expr(self, o: Expression) -> None:
|
||||
self.add(o)
|
||||
|
||||
def visit_member_expr(self, e: MemberExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_member_expr(e)
|
||||
|
||||
def visit_yield_from_expr(self, e: YieldFromExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_yield_from_expr(e)
|
||||
|
||||
def visit_yield_expr(self, e: YieldExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_yield_expr(e)
|
||||
|
||||
def visit_call_expr(self, e: CallExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_call_expr(e)
|
||||
|
||||
def visit_op_expr(self, e: OpExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_op_expr(e)
|
||||
|
||||
def visit_comparison_expr(self, e: ComparisonExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_comparison_expr(e)
|
||||
|
||||
def visit_slice_expr(self, e: SliceExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_slice_expr(e)
|
||||
|
||||
def visit_cast_expr(self, e: CastExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_cast_expr(e)
|
||||
|
||||
def visit_assert_type_expr(self, e: AssertTypeExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_assert_type_expr(e)
|
||||
|
||||
def visit_reveal_expr(self, e: RevealExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_reveal_expr(e)
|
||||
|
||||
def visit_assignment_expr(self, e: AssignmentExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_assignment_expr(e)
|
||||
|
||||
def visit_unary_expr(self, e: UnaryExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_unary_expr(e)
|
||||
|
||||
def visit_list_expr(self, e: ListExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_list_expr(e)
|
||||
|
||||
def visit_tuple_expr(self, e: TupleExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_tuple_expr(e)
|
||||
|
||||
def visit_dict_expr(self, e: DictExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_dict_expr(e)
|
||||
|
||||
def visit_set_expr(self, e: SetExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_set_expr(e)
|
||||
|
||||
def visit_index_expr(self, e: IndexExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_index_expr(e)
|
||||
|
||||
def visit_generator_expr(self, e: GeneratorExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_generator_expr(e)
|
||||
|
||||
def visit_dictionary_comprehension(self, e: DictionaryComprehension) -> None:
|
||||
self.add(e)
|
||||
super().visit_dictionary_comprehension(e)
|
||||
|
||||
def visit_list_comprehension(self, e: ListComprehension) -> None:
|
||||
self.add(e)
|
||||
super().visit_list_comprehension(e)
|
||||
|
||||
def visit_set_comprehension(self, e: SetComprehension) -> None:
|
||||
self.add(e)
|
||||
super().visit_set_comprehension(e)
|
||||
|
||||
def visit_conditional_expr(self, e: ConditionalExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_conditional_expr(e)
|
||||
|
||||
def visit_type_application(self, e: TypeApplication) -> None:
|
||||
self.add(e)
|
||||
super().visit_type_application(e)
|
||||
|
||||
def visit_lambda_expr(self, e: LambdaExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_lambda_expr(e)
|
||||
|
||||
def visit_star_expr(self, e: StarExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_star_expr(e)
|
||||
|
||||
def visit_await_expr(self, e: AwaitExpr) -> None:
|
||||
self.add(e)
|
||||
super().visit_await_expr(e)
|
||||
|
||||
def add(self, e: Expression) -> None:
|
||||
self.expressions.append(e)
|
||||
Binary file not shown.
11
venv/lib/python3.12/site-packages/mypy/server/target.py
Normal file
11
venv/lib/python3.12/site-packages/mypy/server/target.py
Normal file
@@ -0,0 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
|
||||
def trigger_to_target(s: str) -> str:
|
||||
assert s[0] == "<"
|
||||
# Strip off the angle brackets
|
||||
s = s[1:-1]
|
||||
# If there is a [wildcard] or similar, strip that off too
|
||||
if s[-1] == "]":
|
||||
s = s.split("[")[0]
|
||||
return s
|
||||
Binary file not shown.
26
venv/lib/python3.12/site-packages/mypy/server/trigger.py
Normal file
26
venv/lib/python3.12/site-packages/mypy/server/trigger.py
Normal file
@@ -0,0 +1,26 @@
|
||||
"""AST triggers that are used for fine-grained dependency handling."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Final
|
||||
|
||||
# Used as a suffix for triggers to handle "from m import *" dependencies (see also
|
||||
# make_wildcard_trigger)
|
||||
|
||||
WILDCARD_TAG: Final = "[wildcard]"
|
||||
|
||||
|
||||
def make_trigger(name: str) -> str:
|
||||
return f"<{name}>"
|
||||
|
||||
|
||||
def make_wildcard_trigger(module: str) -> str:
|
||||
"""Special trigger fired when any top-level name is changed in a module.
|
||||
|
||||
Note that this is different from a module trigger, as module triggers are only
|
||||
fired if the module is created, deleted, or replaced with a non-module, whereas
|
||||
a wildcard trigger is triggered for namespace changes.
|
||||
|
||||
This is used for "from m import *" dependencies.
|
||||
"""
|
||||
return f"<{module}{WILDCARD_TAG}>"
|
||||
Binary file not shown.
1339
venv/lib/python3.12/site-packages/mypy/server/update.py
Normal file
1339
venv/lib/python3.12/site-packages/mypy/server/update.py
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user