1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156
|
# Copyright (c) Meta Platforms, Inc. and affiliates.
# SPDX-License-Identifier: LGPL-2.1-or-later
import itertools
from typing import Generic, Iterator, List, Mapping, Sequence, TypeVar, Union
from drgndoc.parse import (
Class,
DocumentedNode,
Function,
Import,
ImportFrom,
Module,
Node,
Variable,
)
NodeT_co = TypeVar("NodeT_co", bound=Node, covariant=True)
class BoundNode(Generic[NodeT_co]):
def __init__(self, name: str, node: NodeT_co) -> None:
self.name = name
self.node = node
class ResolvedNode(Generic[NodeT_co]):
def __init__(
self,
modules: Sequence[BoundNode[Module]],
classes: Sequence[BoundNode[Class]],
name: str,
node: NodeT_co,
) -> None:
self.modules = modules
self.classes = classes
self.name = name
self.node = node
def qualified_name(self) -> str:
return ".".join(
itertools.chain(
(module.name for module in self.modules),
(class_.name for class_ in self.classes),
(self.name,),
)
)
def attrs(self) -> Iterator["ResolvedNode[Node]"]:
if isinstance(self.node, Module):
modules = list(self.modules)
modules.append(BoundNode(self.name, self.node))
for attr, node in self.node.attrs.items():
yield ResolvedNode(modules, self.classes, attr, node)
elif isinstance(self.node, Class):
classes = list(self.classes)
classes.append(BoundNode(self.name, self.node))
for attr, node in self.node.attrs.items():
yield ResolvedNode(self.modules, classes, attr, node)
def attr(self, attr: str) -> "ResolvedNode[Node]":
if isinstance(self.node, Module):
modules = list(self.modules)
modules.append(BoundNode(self.name, self.node))
return ResolvedNode(modules, self.classes, attr, self.node.attrs[attr])
elif isinstance(self.node, Class):
classes = list(self.classes)
classes.append(BoundNode(self.name, self.node))
return ResolvedNode(self.modules, classes, attr, self.node.attrs[attr])
else:
raise KeyError(attr)
UnresolvedName = str
class Namespace:
def __init__(self, modules: Mapping[str, Module]) -> None:
self.modules = modules
# NB: this modifies the passed lists.
def _resolve_name(
self,
modules: List[BoundNode[Module]],
classes: List[BoundNode[Class]],
name_components: List[str],
) -> Union[ResolvedNode[DocumentedNode], UnresolvedName]:
name_components.reverse()
while name_components:
attrs: Mapping[str, Node]
if classes:
attrs = classes[-1].node.attrs
elif modules:
attrs = modules[-1].node.attrs
else:
attrs = self.modules
name = name_components.pop()
try:
node = attrs[name]
except KeyError:
break
if isinstance(node, (Import, ImportFrom)):
classes.clear()
if isinstance(node, Import):
modules.clear()
elif isinstance(node, ImportFrom):
if node.level >= len(modules):
# Relative import beyond top-level package. Bail.
break
# Absolute import is level 0, which clears the whole list.
del modules[-node.level :]
name_components.append(node.name)
if node.module is not None:
name_components.extend(reversed(node.module.split(".")))
elif name_components:
if isinstance(node, Module):
assert not classes
modules.append(BoundNode(name, node))
elif isinstance(node, Class):
classes.append(BoundNode(name, node))
else:
break
else:
assert isinstance(node, (Module, Class, Function, Variable))
return ResolvedNode(modules, classes, name, node)
return ".".join(
itertools.chain(
(module.name for module in modules),
(class_.name for class_ in classes),
(name,),
reversed(name_components),
)
)
def resolve_global_name(
self, name: str
) -> Union[ResolvedNode[DocumentedNode], UnresolvedName]:
return self._resolve_name([], [], name.split("."))
def resolve_name_in_scope(
self,
modules: Sequence[BoundNode[Module]],
classes: Sequence[BoundNode[Class]],
name: str,
) -> Union[ResolvedNode[DocumentedNode], UnresolvedName]:
name_components = name.split(".")
attr = name_components[0]
if classes and attr in classes[-1].node.attrs:
classes = list(classes)
elif modules and attr in modules[-1].node.attrs:
classes = []
else:
return name
modules = list(modules)
return self._resolve_name(modules, classes, name_components)
|