renaming.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. from __future__ import annotations
  2. from contextlib import contextmanager
  3. from typing import Iterator
  4. from typing_extensions import Final
  5. from mypy.nodes import (
  6. AssignmentStmt,
  7. Block,
  8. BreakStmt,
  9. ClassDef,
  10. ContinueStmt,
  11. ForStmt,
  12. FuncDef,
  13. Import,
  14. ImportAll,
  15. ImportFrom,
  16. IndexExpr,
  17. ListExpr,
  18. Lvalue,
  19. MatchStmt,
  20. MemberExpr,
  21. MypyFile,
  22. NameExpr,
  23. StarExpr,
  24. TryStmt,
  25. TupleExpr,
  26. WhileStmt,
  27. WithStmt,
  28. )
  29. from mypy.patterns import AsPattern
  30. from mypy.traverser import TraverserVisitor
  31. # Scope kinds
  32. FILE: Final = 0
  33. FUNCTION: Final = 1
  34. CLASS: Final = 2
  35. class VariableRenameVisitor(TraverserVisitor):
  36. """Rename variables to allow redefinition of variables.
  37. For example, consider this code:
  38. x = 0
  39. f(x)
  40. x = "a"
  41. g(x)
  42. It will be transformed like this:
  43. x' = 0
  44. f(x')
  45. x = "a"
  46. g(x)
  47. There will be two independent variables (x' and x) that will have separate
  48. inferred types. The publicly exposed variant will get the non-suffixed name.
  49. This is the last definition at module top level and the first definition
  50. (argument) within a function.
  51. Renaming only happens for assignments within the same block. Renaming is
  52. performed before semantic analysis, immediately after parsing.
  53. The implementation performs a rudimentary static analysis. The analysis is
  54. overly conservative to keep things simple.
  55. """
  56. def __init__(self) -> None:
  57. # Counter for labeling new blocks
  58. self.block_id = 0
  59. # Number of surrounding try statements that disallow variable redefinition
  60. self.disallow_redef_depth = 0
  61. # Number of surrounding loop statements
  62. self.loop_depth = 0
  63. # Map block id to loop depth.
  64. self.block_loop_depth: dict[int, int] = {}
  65. # Stack of block ids being processed.
  66. self.blocks: list[int] = []
  67. # List of scopes; each scope maps short (unqualified) name to block id.
  68. self.var_blocks: list[dict[str, int]] = []
  69. # References to variables that we may need to rename. List of
  70. # scopes; each scope is a mapping from name to list of collections
  71. # of names that refer to the same logical variable.
  72. self.refs: list[dict[str, list[list[NameExpr]]]] = []
  73. # Number of reads of the most recent definition of a variable (per scope)
  74. self.num_reads: list[dict[str, int]] = []
  75. # Kinds of nested scopes (FILE, FUNCTION or CLASS)
  76. self.scope_kinds: list[int] = []
  77. def visit_mypy_file(self, file_node: MypyFile) -> None:
  78. """Rename variables within a file.
  79. This is the main entry point to this class.
  80. """
  81. self.clear()
  82. with self.enter_scope(FILE), self.enter_block():
  83. for d in file_node.defs:
  84. d.accept(self)
  85. def visit_func_def(self, fdef: FuncDef) -> None:
  86. # Conservatively do not allow variable defined before a function to
  87. # be redefined later, since function could refer to either definition.
  88. self.reject_redefinition_of_vars_in_scope()
  89. with self.enter_scope(FUNCTION), self.enter_block():
  90. for arg in fdef.arguments:
  91. name = arg.variable.name
  92. # 'self' can't be redefined since it's special as it allows definition of
  93. # attributes. 'cls' can't be used to define attributes so we can ignore it.
  94. can_be_redefined = name != "self" # TODO: Proper check
  95. self.record_assignment(arg.variable.name, can_be_redefined)
  96. self.handle_arg(name)
  97. for stmt in fdef.body.body:
  98. stmt.accept(self)
  99. def visit_class_def(self, cdef: ClassDef) -> None:
  100. self.reject_redefinition_of_vars_in_scope()
  101. with self.enter_scope(CLASS):
  102. super().visit_class_def(cdef)
  103. def visit_block(self, block: Block) -> None:
  104. with self.enter_block():
  105. super().visit_block(block)
  106. def visit_while_stmt(self, stmt: WhileStmt) -> None:
  107. with self.enter_loop():
  108. super().visit_while_stmt(stmt)
  109. def visit_for_stmt(self, stmt: ForStmt) -> None:
  110. stmt.expr.accept(self)
  111. self.analyze_lvalue(stmt.index, True)
  112. # Also analyze as non-lvalue so that every for loop index variable is assumed to be read.
  113. stmt.index.accept(self)
  114. with self.enter_loop():
  115. stmt.body.accept(self)
  116. if stmt.else_body:
  117. stmt.else_body.accept(self)
  118. def visit_break_stmt(self, stmt: BreakStmt) -> None:
  119. self.reject_redefinition_of_vars_in_loop()
  120. def visit_continue_stmt(self, stmt: ContinueStmt) -> None:
  121. self.reject_redefinition_of_vars_in_loop()
  122. def visit_try_stmt(self, stmt: TryStmt) -> None:
  123. # Variables defined by a try statement get special treatment in the
  124. # type checker which allows them to be always redefined, so no need to
  125. # do renaming here.
  126. with self.enter_try():
  127. super().visit_try_stmt(stmt)
  128. def visit_with_stmt(self, stmt: WithStmt) -> None:
  129. for expr in stmt.expr:
  130. expr.accept(self)
  131. for target in stmt.target:
  132. if target is not None:
  133. self.analyze_lvalue(target)
  134. # We allow redefinitions in the body of a with statement for
  135. # convenience. This is unsafe since with statements can affect control
  136. # flow by catching exceptions, but this is rare except for
  137. # assertRaises() and other similar functions, where the exception is
  138. # raised by the last statement in the body, which usually isn't a
  139. # problem.
  140. stmt.body.accept(self)
  141. def visit_import(self, imp: Import) -> None:
  142. for id, as_id in imp.ids:
  143. self.record_assignment(as_id or id, False)
  144. def visit_import_from(self, imp: ImportFrom) -> None:
  145. for id, as_id in imp.names:
  146. self.record_assignment(as_id or id, False)
  147. def visit_assignment_stmt(self, s: AssignmentStmt) -> None:
  148. s.rvalue.accept(self)
  149. for lvalue in s.lvalues:
  150. self.analyze_lvalue(lvalue)
  151. def visit_match_stmt(self, s: MatchStmt) -> None:
  152. s.subject.accept(self)
  153. for i in range(len(s.patterns)):
  154. with self.enter_block():
  155. s.patterns[i].accept(self)
  156. guard = s.guards[i]
  157. if guard is not None:
  158. guard.accept(self)
  159. # We already entered a block, so visit this block's statements directly
  160. for stmt in s.bodies[i].body:
  161. stmt.accept(self)
  162. def visit_capture_pattern(self, p: AsPattern) -> None:
  163. if p.name is not None:
  164. self.analyze_lvalue(p.name)
  165. def analyze_lvalue(self, lvalue: Lvalue, is_nested: bool = False) -> None:
  166. """Process assignment; in particular, keep track of (re)defined names.
  167. Args:
  168. is_nested: True for non-outermost Lvalue in a multiple assignment such as
  169. "x, y = ..."
  170. """
  171. if isinstance(lvalue, NameExpr):
  172. name = lvalue.name
  173. is_new = self.record_assignment(name, True)
  174. if is_new:
  175. self.handle_def(lvalue)
  176. else:
  177. self.handle_refine(lvalue)
  178. if is_nested:
  179. # This allows these to be redefined freely even if never read. Multiple
  180. # assignment like "x, _ _ = y" defines dummy variables that are never read.
  181. self.handle_ref(lvalue)
  182. elif isinstance(lvalue, (ListExpr, TupleExpr)):
  183. for item in lvalue.items:
  184. self.analyze_lvalue(item, is_nested=True)
  185. elif isinstance(lvalue, MemberExpr):
  186. lvalue.expr.accept(self)
  187. elif isinstance(lvalue, IndexExpr):
  188. lvalue.base.accept(self)
  189. lvalue.index.accept(self)
  190. elif isinstance(lvalue, StarExpr):
  191. # Propagate is_nested since in a typical use case like "x, *rest = ..." 'rest' may
  192. # be freely reused.
  193. self.analyze_lvalue(lvalue.expr, is_nested=is_nested)
  194. def visit_name_expr(self, expr: NameExpr) -> None:
  195. self.handle_ref(expr)
  196. # Helpers for renaming references
  197. def handle_arg(self, name: str) -> None:
  198. """Store function argument."""
  199. self.refs[-1][name] = [[]]
  200. self.num_reads[-1][name] = 0
  201. def handle_def(self, expr: NameExpr) -> None:
  202. """Store new name definition."""
  203. name = expr.name
  204. names = self.refs[-1].setdefault(name, [])
  205. names.append([expr])
  206. self.num_reads[-1][name] = 0
  207. def handle_refine(self, expr: NameExpr) -> None:
  208. """Store assignment to an existing name (that replaces previous value, if any)."""
  209. name = expr.name
  210. if name in self.refs[-1]:
  211. names = self.refs[-1][name]
  212. if not names:
  213. names.append([])
  214. names[-1].append(expr)
  215. def handle_ref(self, expr: NameExpr) -> None:
  216. """Store reference to defined name."""
  217. name = expr.name
  218. if name in self.refs[-1]:
  219. names = self.refs[-1][name]
  220. if not names:
  221. names.append([])
  222. names[-1].append(expr)
  223. num_reads = self.num_reads[-1]
  224. num_reads[name] = num_reads.get(name, 0) + 1
  225. def flush_refs(self) -> None:
  226. """Rename all references within the current scope.
  227. This will be called at the end of a scope.
  228. """
  229. is_func = self.scope_kinds[-1] == FUNCTION
  230. for name, refs in self.refs[-1].items():
  231. if len(refs) == 1:
  232. # Only one definition -- no renaming needed.
  233. continue
  234. if is_func:
  235. # In a function, don't rename the first definition, as it
  236. # may be an argument that must preserve the name.
  237. to_rename = refs[1:]
  238. else:
  239. # At module top level, don't rename the final definition,
  240. # as it will be publicly visible outside the module.
  241. to_rename = refs[:-1]
  242. for i, item in enumerate(to_rename):
  243. rename_refs(item, i)
  244. self.refs.pop()
  245. # Helpers for determining which assignments define new variables
  246. def clear(self) -> None:
  247. self.blocks = []
  248. self.var_blocks = []
  249. @contextmanager
  250. def enter_block(self) -> Iterator[None]:
  251. self.block_id += 1
  252. self.blocks.append(self.block_id)
  253. self.block_loop_depth[self.block_id] = self.loop_depth
  254. try:
  255. yield
  256. finally:
  257. self.blocks.pop()
  258. @contextmanager
  259. def enter_try(self) -> Iterator[None]:
  260. self.disallow_redef_depth += 1
  261. try:
  262. yield
  263. finally:
  264. self.disallow_redef_depth -= 1
  265. @contextmanager
  266. def enter_loop(self) -> Iterator[None]:
  267. self.loop_depth += 1
  268. try:
  269. yield
  270. finally:
  271. self.loop_depth -= 1
  272. def current_block(self) -> int:
  273. return self.blocks[-1]
  274. @contextmanager
  275. def enter_scope(self, kind: int) -> Iterator[None]:
  276. self.var_blocks.append({})
  277. self.refs.append({})
  278. self.num_reads.append({})
  279. self.scope_kinds.append(kind)
  280. try:
  281. yield
  282. finally:
  283. self.flush_refs()
  284. self.var_blocks.pop()
  285. self.num_reads.pop()
  286. self.scope_kinds.pop()
  287. def is_nested(self) -> int:
  288. return len(self.var_blocks) > 1
  289. def reject_redefinition_of_vars_in_scope(self) -> None:
  290. """Make it impossible to redefine defined variables in the current scope.
  291. This is used if we encounter a function definition that
  292. can make it ambiguous which definition is live. Example:
  293. x = 0
  294. def f() -> int:
  295. return x
  296. x = '' # Error -- cannot redefine x across function definition
  297. """
  298. var_blocks = self.var_blocks[-1]
  299. for key in var_blocks:
  300. var_blocks[key] = -1
  301. def reject_redefinition_of_vars_in_loop(self) -> None:
  302. """Reject redefinition of variables in the innermost loop.
  303. If there is an early exit from a loop, there may be ambiguity about which
  304. value may escape the loop. Example where this matters:
  305. while f():
  306. x = 0
  307. if g():
  308. break
  309. x = '' # Error -- not a redefinition
  310. reveal_type(x) # int
  311. This method ensures that the second assignment to 'x' doesn't introduce a new
  312. variable.
  313. """
  314. var_blocks = self.var_blocks[-1]
  315. for key, block in var_blocks.items():
  316. if self.block_loop_depth.get(block) == self.loop_depth:
  317. var_blocks[key] = -1
  318. def record_assignment(self, name: str, can_be_redefined: bool) -> bool:
  319. """Record assignment to given name and return True if it defines a new variable.
  320. Args:
  321. can_be_redefined: If True, allows assignment in the same block to redefine
  322. this name (if this is a new definition)
  323. """
  324. if self.num_reads[-1].get(name, -1) == 0:
  325. # Only set, not read, so no reason to redefine
  326. return False
  327. if self.disallow_redef_depth > 0:
  328. # Can't redefine within try/with a block.
  329. can_be_redefined = False
  330. block = self.current_block()
  331. var_blocks = self.var_blocks[-1]
  332. if name not in var_blocks:
  333. # New definition in this scope.
  334. if can_be_redefined:
  335. # Store the block where this was defined to allow redefinition in
  336. # the same block only.
  337. var_blocks[name] = block
  338. else:
  339. # This doesn't support arbitrary redefinition.
  340. var_blocks[name] = -1
  341. return True
  342. elif var_blocks[name] == block:
  343. # Redefinition -- defines a new variable with the same name.
  344. return True
  345. else:
  346. # Assigns to an existing variable.
  347. return False
  348. class LimitedVariableRenameVisitor(TraverserVisitor):
  349. """Perform some limited variable renaming in with statements.
  350. This allows reusing a variable in multiple with statements with
  351. different types. For example, the two instances of 'x' can have
  352. incompatible types:
  353. with C() as x:
  354. f(x)
  355. with D() as x:
  356. g(x)
  357. The above code gets renamed conceptually into this (not valid Python!):
  358. with C() as x':
  359. f(x')
  360. with D() as x:
  361. g(x)
  362. If there's a reference to a variable defined in 'with' outside the
  363. statement, or if there's any trickiness around variable visibility
  364. (e.g. function definitions), we give up and won't perform renaming.
  365. The main use case is to allow binding both readable and writable
  366. binary files into the same variable. These have different types:
  367. with open(fnam, 'rb') as f: ...
  368. with open(fnam, 'wb') as f: ...
  369. """
  370. def __init__(self) -> None:
  371. # Short names of variables bound in with statements using "as"
  372. # in a surrounding scope
  373. self.bound_vars: list[str] = []
  374. # Stack of names that can't be safely renamed, per scope ('*' means that
  375. # no names can be renamed)
  376. self.skipped: list[set[str]] = []
  377. # References to variables that we may need to rename. Stack of
  378. # scopes; each scope is a mapping from name to list of collections
  379. # of names that refer to the same logical variable.
  380. self.refs: list[dict[str, list[list[NameExpr]]]] = []
  381. def visit_mypy_file(self, file_node: MypyFile) -> None:
  382. """Rename variables within a file.
  383. This is the main entry point to this class.
  384. """
  385. with self.enter_scope():
  386. for d in file_node.defs:
  387. d.accept(self)
  388. def visit_func_def(self, fdef: FuncDef) -> None:
  389. self.reject_redefinition_of_vars_in_scope()
  390. with self.enter_scope():
  391. for arg in fdef.arguments:
  392. self.record_skipped(arg.variable.name)
  393. super().visit_func_def(fdef)
  394. def visit_class_def(self, cdef: ClassDef) -> None:
  395. self.reject_redefinition_of_vars_in_scope()
  396. with self.enter_scope():
  397. super().visit_class_def(cdef)
  398. def visit_with_stmt(self, stmt: WithStmt) -> None:
  399. for expr in stmt.expr:
  400. expr.accept(self)
  401. old_len = len(self.bound_vars)
  402. for target in stmt.target:
  403. if target is not None:
  404. self.analyze_lvalue(target)
  405. for target in stmt.target:
  406. if target:
  407. target.accept(self)
  408. stmt.body.accept(self)
  409. while len(self.bound_vars) > old_len:
  410. self.bound_vars.pop()
  411. def analyze_lvalue(self, lvalue: Lvalue) -> None:
  412. if isinstance(lvalue, NameExpr):
  413. name = lvalue.name
  414. if name in self.bound_vars:
  415. # Name bound in a surrounding with statement, so it can be renamed
  416. self.visit_name_expr(lvalue)
  417. else:
  418. var_info = self.refs[-1]
  419. if name not in var_info:
  420. var_info[name] = []
  421. var_info[name].append([])
  422. self.bound_vars.append(name)
  423. elif isinstance(lvalue, (ListExpr, TupleExpr)):
  424. for item in lvalue.items:
  425. self.analyze_lvalue(item)
  426. elif isinstance(lvalue, MemberExpr):
  427. lvalue.expr.accept(self)
  428. elif isinstance(lvalue, IndexExpr):
  429. lvalue.base.accept(self)
  430. lvalue.index.accept(self)
  431. elif isinstance(lvalue, StarExpr):
  432. self.analyze_lvalue(lvalue.expr)
  433. def visit_import(self, imp: Import) -> None:
  434. # We don't support renaming imports
  435. for id, as_id in imp.ids:
  436. self.record_skipped(as_id or id)
  437. def visit_import_from(self, imp: ImportFrom) -> None:
  438. # We don't support renaming imports
  439. for id, as_id in imp.names:
  440. self.record_skipped(as_id or id)
  441. def visit_import_all(self, imp: ImportAll) -> None:
  442. # Give up, since we don't know all imported names yet
  443. self.reject_redefinition_of_vars_in_scope()
  444. def visit_name_expr(self, expr: NameExpr) -> None:
  445. name = expr.name
  446. if name in self.bound_vars:
  447. # Record reference so that it can be renamed later
  448. for scope in reversed(self.refs):
  449. if name in scope:
  450. scope[name][-1].append(expr)
  451. else:
  452. self.record_skipped(name)
  453. @contextmanager
  454. def enter_scope(self) -> Iterator[None]:
  455. self.skipped.append(set())
  456. self.refs.append({})
  457. yield None
  458. self.flush_refs()
  459. def reject_redefinition_of_vars_in_scope(self) -> None:
  460. self.record_skipped("*")
  461. def record_skipped(self, name: str) -> None:
  462. self.skipped[-1].add(name)
  463. def flush_refs(self) -> None:
  464. ref_dict = self.refs.pop()
  465. skipped = self.skipped.pop()
  466. if "*" not in skipped:
  467. for name, refs in ref_dict.items():
  468. if len(refs) <= 1 or name in skipped:
  469. continue
  470. # At module top level we must not rename the final definition,
  471. # as it may be publicly visible
  472. to_rename = refs[:-1]
  473. for i, item in enumerate(to_rename):
  474. rename_refs(item, i)
  475. def rename_refs(names: list[NameExpr], index: int) -> None:
  476. name = names[0].name
  477. new_name = name + "'" * (index + 1)
  478. for expr in names:
  479. expr.name = new_name