134 lines
4.6 KiB
Python
134 lines
4.6 KiB
Python
from __future__ import annotations
|
|
|
|
from collections.abc import Iterable
|
|
|
|
import mypy.types as types
|
|
from mypy.types import TypeVisitor
|
|
from mypy.util import split_module_names
|
|
|
|
|
|
def extract_module_names(type_name: str | None) -> list[str]:
|
|
"""Returns the module names of a fully qualified type name."""
|
|
if type_name is not None:
|
|
# Discard the first one, which is just the qualified name of the type
|
|
possible_module_names = split_module_names(type_name)
|
|
return possible_module_names[1:]
|
|
else:
|
|
return []
|
|
|
|
|
|
class TypeIndirectionVisitor(TypeVisitor[None]):
|
|
"""Returns all module references within a particular type."""
|
|
|
|
def __init__(self) -> None:
|
|
# Module references are collected here
|
|
self.modules: set[str] = set()
|
|
# User to avoid infinite recursion with recursive type aliases
|
|
self.seen_aliases: set[types.TypeAliasType] = set()
|
|
# Used to avoid redundant work
|
|
self.seen_fullnames: set[str] = set()
|
|
|
|
def find_modules(self, typs: Iterable[types.Type]) -> set[str]:
|
|
self.modules = set()
|
|
self.seen_fullnames = set()
|
|
self.seen_aliases = set()
|
|
self._visit(typs)
|
|
return self.modules
|
|
|
|
def _visit(self, typ_or_typs: types.Type | Iterable[types.Type]) -> None:
|
|
typs = [typ_or_typs] if isinstance(typ_or_typs, types.Type) else typ_or_typs
|
|
for typ in typs:
|
|
if isinstance(typ, types.TypeAliasType):
|
|
# Avoid infinite recursion for recursive type aliases.
|
|
if typ in self.seen_aliases:
|
|
continue
|
|
self.seen_aliases.add(typ)
|
|
typ.accept(self)
|
|
|
|
def _visit_module_name(self, module_name: str) -> None:
|
|
if module_name not in self.modules:
|
|
self.modules.update(split_module_names(module_name))
|
|
|
|
def visit_unbound_type(self, t: types.UnboundType) -> None:
|
|
self._visit(t.args)
|
|
|
|
def visit_any(self, t: types.AnyType) -> None:
|
|
pass
|
|
|
|
def visit_none_type(self, t: types.NoneType) -> None:
|
|
pass
|
|
|
|
def visit_uninhabited_type(self, t: types.UninhabitedType) -> None:
|
|
pass
|
|
|
|
def visit_erased_type(self, t: types.ErasedType) -> None:
|
|
pass
|
|
|
|
def visit_deleted_type(self, t: types.DeletedType) -> None:
|
|
pass
|
|
|
|
def visit_type_var(self, t: types.TypeVarType) -> None:
|
|
self._visit(t.values)
|
|
self._visit(t.upper_bound)
|
|
self._visit(t.default)
|
|
|
|
def visit_param_spec(self, t: types.ParamSpecType) -> None:
|
|
self._visit(t.upper_bound)
|
|
self._visit(t.default)
|
|
|
|
def visit_type_var_tuple(self, t: types.TypeVarTupleType) -> None:
|
|
self._visit(t.upper_bound)
|
|
self._visit(t.default)
|
|
|
|
def visit_unpack_type(self, t: types.UnpackType) -> None:
|
|
t.type.accept(self)
|
|
|
|
def visit_parameters(self, t: types.Parameters) -> None:
|
|
self._visit(t.arg_types)
|
|
|
|
def visit_instance(self, t: types.Instance) -> None:
|
|
self._visit(t.args)
|
|
if t.type:
|
|
# Uses of a class depend on everything in the MRO,
|
|
# as changes to classes in the MRO can add types to methods,
|
|
# change property types, change the MRO itself, etc.
|
|
for s in t.type.mro:
|
|
self._visit_module_name(s.module_name)
|
|
if t.type.metaclass_type is not None:
|
|
self._visit_module_name(t.type.metaclass_type.type.module_name)
|
|
|
|
def visit_callable_type(self, t: types.CallableType) -> None:
|
|
self._visit(t.arg_types)
|
|
self._visit(t.ret_type)
|
|
if t.definition is not None:
|
|
fullname = t.definition.fullname
|
|
if fullname not in self.seen_fullnames:
|
|
self.modules.update(extract_module_names(t.definition.fullname))
|
|
self.seen_fullnames.add(fullname)
|
|
|
|
def visit_overloaded(self, t: types.Overloaded) -> None:
|
|
self._visit(t.items)
|
|
self._visit(t.fallback)
|
|
|
|
def visit_tuple_type(self, t: types.TupleType) -> None:
|
|
self._visit(t.items)
|
|
self._visit(t.partial_fallback)
|
|
|
|
def visit_typeddict_type(self, t: types.TypedDictType) -> None:
|
|
self._visit(t.items.values())
|
|
self._visit(t.fallback)
|
|
|
|
def visit_literal_type(self, t: types.LiteralType) -> None:
|
|
self._visit(t.fallback)
|
|
|
|
def visit_union_type(self, t: types.UnionType) -> None:
|
|
self._visit(t.items)
|
|
|
|
def visit_partial_type(self, t: types.PartialType) -> None:
|
|
pass
|
|
|
|
def visit_type_type(self, t: types.TypeType) -> None:
|
|
self._visit(t.item)
|
|
|
|
def visit_type_alias_type(self, t: types.TypeAliasType) -> None:
|
|
self._visit(types.get_proper_type(t))
|