refinfo.py 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. """Find line-level reference information from a mypy AST (undocumented feature)"""
  2. from __future__ import annotations
  3. from mypy.nodes import (
  4. LDEF,
  5. Expression,
  6. FuncDef,
  7. MemberExpr,
  8. MypyFile,
  9. NameExpr,
  10. RefExpr,
  11. SymbolNode,
  12. TypeInfo,
  13. )
  14. from mypy.traverser import TraverserVisitor
  15. from mypy.typeops import tuple_fallback
  16. from mypy.types import (
  17. FunctionLike,
  18. Instance,
  19. TupleType,
  20. Type,
  21. TypeType,
  22. TypeVarLikeType,
  23. get_proper_type,
  24. )
  25. class RefInfoVisitor(TraverserVisitor):
  26. def __init__(self, type_map: dict[Expression, Type]) -> None:
  27. super().__init__()
  28. self.type_map = type_map
  29. self.data: list[dict[str, object]] = []
  30. def visit_name_expr(self, expr: NameExpr) -> None:
  31. super().visit_name_expr(expr)
  32. self.record_ref_expr(expr)
  33. def visit_member_expr(self, expr: MemberExpr) -> None:
  34. super().visit_member_expr(expr)
  35. self.record_ref_expr(expr)
  36. def visit_func_def(self, func: FuncDef) -> None:
  37. if func.expanded:
  38. for item in func.expanded:
  39. if isinstance(item, FuncDef):
  40. super().visit_func_def(item)
  41. else:
  42. super().visit_func_def(func)
  43. def record_ref_expr(self, expr: RefExpr) -> None:
  44. fullname = None
  45. if expr.kind != LDEF and "." in expr.fullname:
  46. fullname = expr.fullname
  47. elif isinstance(expr, MemberExpr):
  48. typ = self.type_map.get(expr.expr)
  49. sym = None
  50. if isinstance(expr.expr, RefExpr):
  51. sym = expr.expr.node
  52. if typ:
  53. tfn = type_fullname(typ, sym)
  54. if tfn:
  55. fullname = f"{tfn}.{expr.name}"
  56. if not fullname:
  57. fullname = f"*.{expr.name}"
  58. if fullname is not None:
  59. self.data.append({"line": expr.line, "column": expr.column, "target": fullname})
  60. def type_fullname(typ: Type, node: SymbolNode | None = None) -> str | None:
  61. typ = get_proper_type(typ)
  62. if isinstance(typ, Instance):
  63. return typ.type.fullname
  64. elif isinstance(typ, TypeType):
  65. return type_fullname(typ.item)
  66. elif isinstance(typ, FunctionLike) and typ.is_type_obj():
  67. if isinstance(node, TypeInfo):
  68. return node.fullname
  69. return type_fullname(typ.fallback)
  70. elif isinstance(typ, TupleType):
  71. return type_fullname(tuple_fallback(typ))
  72. elif isinstance(typ, TypeVarLikeType):
  73. return type_fullname(typ.upper_bound)
  74. return None
  75. def get_undocumented_ref_info_json(
  76. tree: MypyFile, type_map: dict[Expression, Type]
  77. ) -> list[dict[str, object]]:
  78. visitor = RefInfoVisitor(type_map)
  79. tree.accept(visitor)
  80. return visitor.data