aststrip.py 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. """Strip/reset AST in-place to match state after semantic analyzer pre-analysis.
  2. Fine-grained incremental mode reruns semantic analysis main pass
  3. and type checking for *existing* AST nodes (targets) when changes are
  4. propagated using fine-grained dependencies. AST nodes attributes are
  5. sometimes changed during semantic analysis main pass, and running
  6. semantic analysis again on those nodes would produce incorrect
  7. results, since this pass isn't idempotent. This pass resets AST
  8. nodes to reflect the state after semantic pre-analysis, so that we
  9. can rerun semantic analysis.
  10. (The above is in contrast to behavior with modules that have source code
  11. changes, for which we re-parse the entire module and reconstruct a fresh
  12. AST. No stripping is required in this case. Both modes of operation should
  13. have the same outcome.)
  14. Notes:
  15. * This is currently pretty fragile, as we must carefully undo whatever
  16. changes can be made in semantic analysis main pass, including changes
  17. to symbol tables.
  18. * We reuse existing AST nodes because it makes it relatively straightforward
  19. to reprocess only a single target within a module efficiently. If there
  20. was a way to parse a single target within a file, in time proportional to
  21. the size of the target, we'd rather create fresh AST nodes than strip them.
  22. (This is possible only in Python 3.8+)
  23. * Currently we don't actually reset all changes, but only those known to affect
  24. non-idempotent semantic analysis behavior.
  25. TODO: It would be more principled and less fragile to reset everything
  26. changed in semantic analysis main pass and later.
  27. * Reprocessing may recreate AST nodes (such as Var nodes, and TypeInfo nodes
  28. created with assignment statements) that will get different identities from
  29. the original AST. Thus running an AST merge is necessary after stripping,
  30. even though some identities are preserved.
  31. """
  32. from __future__ import annotations
  33. from contextlib import contextmanager, nullcontext
  34. from typing import Dict, Iterator, Tuple
  35. from typing_extensions import TypeAlias as _TypeAlias
  36. from mypy.nodes import (
  37. CLASSDEF_NO_INFO,
  38. AssignmentStmt,
  39. Block,
  40. CallExpr,
  41. ClassDef,
  42. Decorator,
  43. ForStmt,
  44. FuncDef,
  45. ImportAll,
  46. ImportFrom,
  47. IndexExpr,
  48. ListExpr,
  49. MemberExpr,
  50. MypyFile,
  51. NameExpr,
  52. Node,
  53. OpExpr,
  54. OverloadedFuncDef,
  55. RefExpr,
  56. StarExpr,
  57. SuperExpr,
  58. SymbolTableNode,
  59. TupleExpr,
  60. TypeInfo,
  61. Var,
  62. )
  63. from mypy.traverser import TraverserVisitor
  64. from mypy.types import CallableType
  65. from mypy.typestate import type_state
  66. SavedAttributes: _TypeAlias = Dict[Tuple[ClassDef, str], SymbolTableNode]
  67. def strip_target(
  68. node: MypyFile | FuncDef | OverloadedFuncDef, saved_attrs: SavedAttributes
  69. ) -> None:
  70. """Reset a fine-grained incremental target to state before semantic analysis.
  71. All TypeInfos are killed. Therefore we need to preserve the variables
  72. defined as attributes on self. This is done by patches (callbacks)
  73. returned from this function that re-add these variables when called.
  74. Args:
  75. node: node to strip
  76. saved_attrs: collect attributes here that may need to be re-added to
  77. classes afterwards if stripping a class body (this dict is mutated)
  78. """
  79. visitor = NodeStripVisitor(saved_attrs)
  80. if isinstance(node, MypyFile):
  81. visitor.strip_file_top_level(node)
  82. else:
  83. node.accept(visitor)
  84. class NodeStripVisitor(TraverserVisitor):
  85. def __init__(self, saved_class_attrs: SavedAttributes) -> None:
  86. # The current active class.
  87. self.type: TypeInfo | None = None
  88. # This is True at class scope, but not in methods.
  89. self.is_class_body = False
  90. # By default, process function definitions. If False, don't -- this is used for
  91. # processing module top levels.
  92. self.recurse_into_functions = True
  93. # These attributes were removed from top-level classes during strip and
  94. # will be added afterwards (if no existing definition is found). These
  95. # must be added back before semantically analyzing any methods.
  96. self.saved_class_attrs = saved_class_attrs
  97. def strip_file_top_level(self, file_node: MypyFile) -> None:
  98. """Strip a module top-level (don't recursive into functions)."""
  99. self.recurse_into_functions = False
  100. file_node.plugin_deps.clear()
  101. file_node.accept(self)
  102. for name in file_node.names.copy():
  103. # TODO: this is a hot fix, we should delete all names,
  104. # see https://github.com/python/mypy/issues/6422.
  105. if "@" not in name:
  106. del file_node.names[name]
  107. def visit_block(self, b: Block) -> None:
  108. if b.is_unreachable:
  109. return
  110. super().visit_block(b)
  111. def visit_class_def(self, node: ClassDef) -> None:
  112. """Strip class body and type info, but don't strip methods."""
  113. # We need to save the implicitly defined instance variables,
  114. # i.e. those defined as attributes on self. Otherwise, they would
  115. # be lost if we only reprocess top-levels (this kills TypeInfos)
  116. # but not the methods that defined those variables.
  117. if not self.recurse_into_functions:
  118. self.save_implicit_attributes(node)
  119. # We need to delete any entries that were generated by plugins,
  120. # since they will get regenerated.
  121. to_delete = {v.node for v in node.info.names.values() if v.plugin_generated}
  122. node.type_vars = []
  123. node.base_type_exprs.extend(node.removed_base_type_exprs)
  124. node.removed_base_type_exprs = []
  125. node.defs.body = [
  126. s for s in node.defs.body if s not in to_delete # type: ignore[comparison-overlap]
  127. ]
  128. with self.enter_class(node.info):
  129. super().visit_class_def(node)
  130. node.defs.body.extend(node.removed_statements)
  131. node.removed_statements = []
  132. type_state.reset_subtype_caches_for(node.info)
  133. # Kill the TypeInfo, since there is none before semantic analysis.
  134. node.info = CLASSDEF_NO_INFO
  135. node.analyzed = None
  136. def save_implicit_attributes(self, node: ClassDef) -> None:
  137. """Produce callbacks that re-add attributes defined on self."""
  138. for name, sym in node.info.names.items():
  139. if isinstance(sym.node, Var) and sym.implicit:
  140. self.saved_class_attrs[node, name] = sym
  141. def visit_func_def(self, node: FuncDef) -> None:
  142. if not self.recurse_into_functions:
  143. return
  144. node.expanded = []
  145. node.type = node.unanalyzed_type
  146. if node.type:
  147. # Type variable binder binds type variables before the type is analyzed,
  148. # this causes unanalyzed_type to be modified in place. We needed to revert this
  149. # in order to get the state exactly as it was before semantic analysis.
  150. # See also #4814.
  151. assert isinstance(node.type, CallableType)
  152. node.type.variables = []
  153. with self.enter_method(node.info) if node.info else nullcontext():
  154. super().visit_func_def(node)
  155. def visit_decorator(self, node: Decorator) -> None:
  156. node.var.type = None
  157. for expr in node.decorators:
  158. expr.accept(self)
  159. if self.recurse_into_functions:
  160. node.func.accept(self)
  161. else:
  162. # Only touch the final status if we re-process
  163. # the top level, since decorators are processed there.
  164. node.var.is_final = False
  165. node.func.is_final = False
  166. def visit_overloaded_func_def(self, node: OverloadedFuncDef) -> None:
  167. if not self.recurse_into_functions:
  168. return
  169. # Revert change made during semantic analysis main pass.
  170. node.items = node.unanalyzed_items.copy()
  171. node.impl = None
  172. node.is_final = False
  173. super().visit_overloaded_func_def(node)
  174. def visit_assignment_stmt(self, node: AssignmentStmt) -> None:
  175. node.type = node.unanalyzed_type
  176. node.is_final_def = False
  177. node.is_alias_def = False
  178. if self.type and not self.is_class_body:
  179. for lvalue in node.lvalues:
  180. # Revert assignments made via self attributes.
  181. self.process_lvalue_in_method(lvalue)
  182. super().visit_assignment_stmt(node)
  183. def visit_import_from(self, node: ImportFrom) -> None:
  184. node.assignments = []
  185. def visit_import_all(self, node: ImportAll) -> None:
  186. node.assignments = []
  187. def visit_for_stmt(self, node: ForStmt) -> None:
  188. node.index_type = node.unanalyzed_index_type
  189. node.inferred_item_type = None
  190. node.inferred_iterator_type = None
  191. super().visit_for_stmt(node)
  192. def visit_name_expr(self, node: NameExpr) -> None:
  193. self.strip_ref_expr(node)
  194. def visit_member_expr(self, node: MemberExpr) -> None:
  195. self.strip_ref_expr(node)
  196. super().visit_member_expr(node)
  197. def visit_index_expr(self, node: IndexExpr) -> None:
  198. node.analyzed = None # May have been an alias or type application.
  199. super().visit_index_expr(node)
  200. def visit_op_expr(self, node: OpExpr) -> None:
  201. node.analyzed = None # May have been an alias
  202. super().visit_op_expr(node)
  203. def strip_ref_expr(self, node: RefExpr) -> None:
  204. node.kind = None
  205. node.node = None
  206. node.fullname = ""
  207. node.is_new_def = False
  208. node.is_inferred_def = False
  209. def visit_call_expr(self, node: CallExpr) -> None:
  210. node.analyzed = None
  211. super().visit_call_expr(node)
  212. def visit_super_expr(self, node: SuperExpr) -> None:
  213. node.info = None
  214. super().visit_super_expr(node)
  215. def process_lvalue_in_method(self, lvalue: Node) -> None:
  216. if isinstance(lvalue, MemberExpr):
  217. if lvalue.is_new_def:
  218. # Remove defined attribute from the class symbol table. If is_new_def is
  219. # true for a MemberExpr, we know that it must be an assignment through
  220. # self, since only those can define new attributes.
  221. assert self.type is not None
  222. if lvalue.name in self.type.names:
  223. del self.type.names[lvalue.name]
  224. key = (self.type.defn, lvalue.name)
  225. if key in self.saved_class_attrs:
  226. del self.saved_class_attrs[key]
  227. elif isinstance(lvalue, (TupleExpr, ListExpr)):
  228. for item in lvalue.items:
  229. self.process_lvalue_in_method(item)
  230. elif isinstance(lvalue, StarExpr):
  231. self.process_lvalue_in_method(lvalue.expr)
  232. @contextmanager
  233. def enter_class(self, info: TypeInfo) -> Iterator[None]:
  234. old_type = self.type
  235. old_is_class_body = self.is_class_body
  236. self.type = info
  237. self.is_class_body = True
  238. yield
  239. self.type = old_type
  240. self.is_class_body = old_is_class_body
  241. @contextmanager
  242. def enter_method(self, info: TypeInfo) -> Iterator[None]:
  243. old_type = self.type
  244. old_is_class_body = self.is_class_body
  245. self.type = info
  246. self.is_class_body = False
  247. yield
  248. self.type = old_type
  249. self.is_class_body = old_is_class_body