Skip to content

Commit

Permalink
Generate better reference information for member expressions (#14864)
Browse files Browse the repository at this point in the history
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`.
  • Loading branch information
JukkaL authored Mar 9, 2023
1 parent 2e8dcbf commit 287c45a
Show file tree
Hide file tree
Showing 2 changed files with 47 additions and 9 deletions.
10 changes: 7 additions & 3 deletions mypy/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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.
Expand All @@ -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=(",", ":")))
46 changes: 40 additions & 6 deletions mypy/refinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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

0 comments on commit 287c45a

Please sign in to comment.