mergecheck.py 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283
  1. """Check for duplicate AST nodes after merge."""
  2. from __future__ import annotations
  3. from typing import Final
  4. from mypy.nodes import Decorator, FakeInfo, FuncDef, SymbolNode, Var
  5. from mypy.server.objgraph import get_path, get_reachable_graph
  6. # If True, print more verbose output on failure.
  7. DUMP_MISMATCH_NODES: Final = False
  8. def check_consistency(o: object) -> None:
  9. """Fail if there are two AST nodes with the same fullname reachable from 'o'.
  10. Raise AssertionError on failure and print some debugging output.
  11. """
  12. seen, parents = get_reachable_graph(o)
  13. reachable = list(seen.values())
  14. syms = [x for x in reachable if isinstance(x, SymbolNode)]
  15. m: dict[str, SymbolNode] = {}
  16. for sym in syms:
  17. if isinstance(sym, FakeInfo):
  18. continue
  19. fn = sym.fullname
  20. # Skip None names, since they are ambiguous.
  21. # TODO: Everything should have a proper full name?
  22. if fn is None:
  23. continue
  24. # Skip stuff that should be expected to have duplicate names
  25. if isinstance(sym, (Var, Decorator)):
  26. continue
  27. if isinstance(sym, FuncDef) and sym.is_overload:
  28. continue
  29. if fn not in m:
  30. m[sym.fullname] = sym
  31. continue
  32. # We have trouble and need to decide what to do about it.
  33. sym1, sym2 = sym, m[fn]
  34. # If the type changed, then it shouldn't have been merged.
  35. if type(sym1) is not type(sym2):
  36. continue
  37. path1 = get_path(sym1, seen, parents)
  38. path2 = get_path(sym2, seen, parents)
  39. if fn in m:
  40. print(f"\nDuplicate {type(sym).__name__!r} nodes with fullname {fn!r} found:")
  41. print("[1] %d: %s" % (id(sym1), path_to_str(path1)))
  42. print("[2] %d: %s" % (id(sym2), path_to_str(path2)))
  43. if DUMP_MISMATCH_NODES and fn in m:
  44. # Add verbose output with full AST node contents.
  45. print("---")
  46. print(id(sym1), sym1)
  47. print("---")
  48. print(id(sym2), sym2)
  49. assert sym.fullname not in m
  50. def path_to_str(path: list[tuple[object, object]]) -> str:
  51. result = "<root>"
  52. for attr, obj in path:
  53. t = type(obj).__name__
  54. if t in ("dict", "tuple", "SymbolTable", "list"):
  55. result += f"[{repr(attr)}]"
  56. else:
  57. if isinstance(obj, Var):
  58. result += f".{attr}({t}:{obj.name})"
  59. elif t in ("BuildManager", "FineGrainedBuildManager"):
  60. # Omit class name for some classes that aren't part of a class
  61. # hierarchy since there isn't much ambiguity.
  62. result += f".{attr}"
  63. else:
  64. result += f".{attr}({t})"
  65. return result