from typing import Dict, NamedTuple, Optional
from mypy.nodes import FUNC_NO_INFO, SymbolNode, TypeInfo
from .types import RelevantSymbolNode
[docs]class LookupResult(NamedTuple):
symbol: Optional[SymbolNode]
"""Symbol that was found"""
containing_class: Optional[TypeInfo]
"""Class the symbol was found on"""
[docs]def get_symbol_class(symbol: SymbolNode) -> Optional[TypeInfo]:
"""
Get the class the given symbol is defined on.
:param symbol: Symbol to retrieve the class of
"""
cls = getattr(symbol, "info", None)
if cls and isinstance(cls, TypeInfo):
return cls
return None
[docs]def lookup_symbol(
symbol_map: Dict[str, RelevantSymbolNode], symbol_to_lookup: SymbolNode
) -> LookupResult:
"""
Find the given symbol in the symbol map.
Ideally the symbol is just found under the exact same ``fullname``.
If not and the symbol is a method, this will take the symbol's class and look it up
in the map and then try to resolve the symbol on that class using its method
resolution order.
If the result of the lookup has a different ``fullname`` than the original symbol
the given symbol is defined at a different point in the class hierarchy than
expected.
:param symbol_map: Dictionary for looking up symbols by their full name
:param symbol_to_lookup: Symbol to search for
:return: The found symbol (if any) and the class it was found on (if any)
"""
fail = LookupResult(None, None)
symbol = symbol_map.get(symbol_to_lookup.fullname)
if symbol:
return LookupResult(symbol, get_symbol_class(symbol))
# Check if we have a class on the symbol we're looking up
cls_to_lookup = getattr(symbol_to_lookup, "info", None)
if not cls_to_lookup or cls_to_lookup == FUNC_NO_INFO:
return fail
symbol_cls = symbol_map.get(cls_to_lookup.fullname)
if not symbol_cls or not isinstance(symbol_cls, TypeInfo):
return fail
found_symbol_table_node = symbol_cls.get(symbol_to_lookup.name)
if not found_symbol_table_node or not found_symbol_table_node.node:
return fail
return LookupResult(
found_symbol_table_node.node, get_symbol_class(found_symbol_table_node.node)
)