From 287c45a8631d09b1808e8a107f09c801b083b9bf Mon Sep 17 00:00:00 2001 From: Jukka Lehtosalo Date: Thu, 9 Mar 2023 15:03:51 +0000 Subject: [PATCH] Generate better reference information for member expressions (#14864) Previously most member expressions only produced the wildcard reference such as `*.attr` when using the (undocumented) `--export-ref-info` flag. Use the type of the object to generate better fullnames, such as `module.Cls.attr`. --- mypy/build.py | 10 +++++++--- mypy/refinfo.py | 46 ++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 47 insertions(+), 9 deletions(-) diff --git a/mypy/build.py b/mypy/build.py index 6b0e4a9faa86..e36535a1aa80 100644 --- a/mypy/build.py +++ b/mypy/build.py @@ -2413,7 +2413,9 @@ def finish_passes(self) -> None: self.update_fine_grained_deps(self.manager.fg_deps) if manager.options.export_ref_info: - write_undocumented_ref_info(self, manager.metastore, manager.options) + write_undocumented_ref_info( + self, manager.metastore, manager.options, self.type_map() + ) self.free_state() if not manager.options.fine_grained_incremental and not manager.options.preserve_asts: @@ -3624,7 +3626,9 @@ def is_silent_import_module(manager: BuildManager, path: str) -> bool: ) -def write_undocumented_ref_info(state: State, metastore: MetadataStore, options: Options) -> None: +def write_undocumented_ref_info( + state: State, metastore: MetadataStore, options: Options, type_map: dict[Expression, Type] +) -> None: # This exports some dependency information in a rather ad-hoc fashion, which # can be helpful for some tools. This is all highly experimental and could be # removed at any time. @@ -3639,5 +3643,5 @@ def write_undocumented_ref_info(state: State, metastore: MetadataStore, options: ref_info_file = ".".join(data_file.split(".")[:-2]) + ".refs.json" assert not ref_info_file.startswith(".") - deps_json = get_undocumented_ref_info_json(state.tree) + deps_json = get_undocumented_ref_info_json(state.tree, type_map) metastore.write(ref_info_file, json.dumps(deps_json, separators=(",", ":"))) diff --git a/mypy/refinfo.py b/mypy/refinfo.py index 4262824f8f97..3df1e575a35c 100644 --- a/mypy/refinfo.py +++ b/mypy/refinfo.py @@ -2,13 +2,24 @@ from __future__ import annotations -from mypy.nodes import LDEF, MemberExpr, MypyFile, NameExpr, RefExpr +from mypy.nodes import LDEF, Expression, MemberExpr, MypyFile, NameExpr, RefExpr from mypy.traverser import TraverserVisitor +from mypy.typeops import tuple_fallback +from mypy.types import ( + FunctionLike, + Instance, + TupleType, + Type, + TypeType, + TypeVarLikeType, + get_proper_type, +) class RefInfoVisitor(TraverserVisitor): - def __init__(self) -> None: + def __init__(self, type_map: dict[Expression, Type]) -> None: super().__init__() + self.type_map = type_map self.data: list[dict[str, object]] = [] def visit_name_expr(self, expr: NameExpr) -> None: @@ -23,13 +34,36 @@ def record_ref_expr(self, expr: RefExpr) -> None: fullname = None if expr.kind != LDEF and "." in expr.fullname: fullname = expr.fullname - elif isinstance(expr, MemberExpr) and not expr.fullname: - fullname = f"*.{expr.name}" + elif isinstance(expr, MemberExpr): + typ = self.type_map.get(expr.expr) + if typ: + tfn = type_fullname(typ) + if tfn: + fullname = f"{tfn}.{expr.name}" + if not fullname: + fullname = f"*.{expr.name}" if fullname is not None: self.data.append({"line": expr.line, "column": expr.column, "target": fullname}) -def get_undocumented_ref_info_json(tree: MypyFile) -> list[dict[str, object]]: - visitor = RefInfoVisitor() +def type_fullname(typ: Type) -> str | None: + typ = get_proper_type(typ) + if isinstance(typ, Instance): + return typ.type.fullname + elif isinstance(typ, TypeType): + return type_fullname(typ.item) + elif isinstance(typ, FunctionLike) and typ.is_type_obj(): + return type_fullname(typ.fallback) + elif isinstance(typ, TupleType): + return type_fullname(tuple_fallback(typ)) + elif isinstance(typ, TypeVarLikeType): + return type_fullname(typ.upper_bound) + return None + + +def get_undocumented_ref_info_json( + tree: MypyFile, type_map: dict[Expression, Type] +) -> list[dict[str, object]]: + visitor = RefInfoVisitor(type_map) tree.accept(visitor) return visitor.data