Skip to content

Commit b34a63a

Browse files
DanielNoordcdce8p
andauthored
Move Mixin classes out of main scoped_nodes file (#1463)
Co-authored-by: Marc Mueller <[email protected]>
1 parent b5fc7b5 commit b34a63a

File tree

3 files changed

+174
-160
lines changed

3 files changed

+174
-160
lines changed

astroid/nodes/scoped_nodes/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@
66
A scope node is a node that opens a new local scope in the language definition:
77
Module, ClassDef, FunctionDef (and Lambda, GeneratorExp, DictComp and SetComp to some extent).
88
"""
9+
10+
from astroid.nodes.scoped_nodes.mixin import ComprehensionScope, LocalsDictNodeNG
911
from astroid.nodes.scoped_nodes.scoped_nodes import (
1012
AsyncFunctionDef,
1113
ClassDef,
12-
ComprehensionScope,
1314
DictComp,
1415
FunctionDef,
1516
GeneratorExp,
1617
Lambda,
1718
ListComp,
18-
LocalsDictNodeNG,
1919
Module,
2020
SetComp,
2121
_is_metaclass,

astroid/nodes/scoped_nodes/mixin.py

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
# Licensed under the LGPL: https://www.gnu.org/licenses/old-licenses/lgpl-2.1.en.html
2+
# For details: https://github.com/PyCQA/astroid/blob/main/LICENSE
3+
4+
"""This module contains mixin classes for scoped nodes."""
5+
6+
from typing import TYPE_CHECKING, Dict, List, TypeVar
7+
8+
from astroid.filter_statements import _filter_stmts
9+
from astroid.nodes import node_classes, scoped_nodes
10+
from astroid.nodes.scoped_nodes.utils import builtin_lookup
11+
12+
if TYPE_CHECKING:
13+
from astroid import nodes
14+
15+
_T = TypeVar("_T")
16+
17+
18+
class LocalsDictNodeNG(node_classes.LookupMixIn, node_classes.NodeNG):
19+
"""this class provides locals handling common to Module, FunctionDef
20+
and ClassDef nodes, including a dict like interface for direct access
21+
to locals information
22+
"""
23+
24+
# attributes below are set by the builder module or by raw factories
25+
26+
locals: Dict[str, List["nodes.NodeNG"]] = {}
27+
"""A map of the name of a local variable to the node defining the local."""
28+
29+
def qname(self):
30+
"""Get the 'qualified' name of the node.
31+
32+
For example: module.name, module.class.name ...
33+
34+
:returns: The qualified name.
35+
:rtype: str
36+
"""
37+
# pylint: disable=no-member; github.com/pycqa/astroid/issues/278
38+
if self.parent is None:
39+
return self.name
40+
return f"{self.parent.frame(future=True).qname()}.{self.name}"
41+
42+
def scope(self: _T) -> _T:
43+
"""The first parent node defining a new scope.
44+
45+
:returns: The first parent scope node.
46+
:rtype: Module or FunctionDef or ClassDef or Lambda or GenExpr
47+
"""
48+
return self
49+
50+
def _scope_lookup(self, node, name, offset=0):
51+
"""XXX method for interfacing the scope lookup"""
52+
try:
53+
stmts = _filter_stmts(node, self.locals[name], self, offset)
54+
except KeyError:
55+
stmts = ()
56+
if stmts:
57+
return self, stmts
58+
59+
# Handle nested scopes: since class names do not extend to nested
60+
# scopes (e.g., methods), we find the next enclosing non-class scope
61+
pscope = self.parent and self.parent.scope()
62+
while pscope is not None:
63+
if not isinstance(pscope, scoped_nodes.ClassDef):
64+
return pscope.scope_lookup(node, name)
65+
pscope = pscope.parent and pscope.parent.scope()
66+
67+
# self is at the top level of a module, or is enclosed only by ClassDefs
68+
return builtin_lookup(name)
69+
70+
def set_local(self, name, stmt):
71+
"""Define that the given name is declared in the given statement node.
72+
73+
.. seealso:: :meth:`scope`
74+
75+
:param name: The name that is being defined.
76+
:type name: str
77+
78+
:param stmt: The statement that defines the given name.
79+
:type stmt: NodeNG
80+
"""
81+
# assert not stmt in self.locals.get(name, ()), (self, stmt)
82+
self.locals.setdefault(name, []).append(stmt)
83+
84+
__setitem__ = set_local
85+
86+
def _append_node(self, child):
87+
"""append a child, linking it in the tree"""
88+
# pylint: disable=no-member; depending by the class
89+
# which uses the current class as a mixin or base class.
90+
# It's rewritten in 2.0, so it makes no sense for now
91+
# to spend development time on it.
92+
self.body.append(child)
93+
child.parent = self
94+
95+
def add_local_node(self, child_node, name=None):
96+
"""Append a child that should alter the locals of this scope node.
97+
98+
:param child_node: The child node that will alter locals.
99+
:type child_node: NodeNG
100+
101+
:param name: The name of the local that will be altered by
102+
the given child node.
103+
:type name: str or None
104+
"""
105+
if name != "__class__":
106+
# add __class__ node as a child will cause infinite recursion later!
107+
self._append_node(child_node)
108+
self.set_local(name or child_node.name, child_node)
109+
110+
def __getitem__(self, item):
111+
"""The first node the defines the given local.
112+
113+
:param item: The name of the locally defined object.
114+
:type item: str
115+
116+
:raises KeyError: If the name is not defined.
117+
"""
118+
return self.locals[item][0]
119+
120+
def __iter__(self):
121+
"""Iterate over the names of locals defined in this scoped node.
122+
123+
:returns: The names of the defined locals.
124+
:rtype: iterable(str)
125+
"""
126+
return iter(self.keys())
127+
128+
def keys(self):
129+
"""The names of locals defined in this scoped node.
130+
131+
:returns: The names of the defined locals.
132+
:rtype: list(str)
133+
"""
134+
return list(self.locals.keys())
135+
136+
def values(self):
137+
"""The nodes that define the locals in this scoped node.
138+
139+
:returns: The nodes that define locals.
140+
:rtype: list(NodeNG)
141+
"""
142+
# pylint: disable=consider-using-dict-items
143+
# It look like this class override items/keys/values,
144+
# probably not worth the headache
145+
return [self[key] for key in self.keys()]
146+
147+
def items(self):
148+
"""Get the names of the locals and the node that defines the local.
149+
150+
:returns: The names of locals and their associated node.
151+
:rtype: list(tuple(str, NodeNG))
152+
"""
153+
return list(zip(self.keys(), self.values()))
154+
155+
def __contains__(self, name):
156+
"""Check if a local is defined in this scope.
157+
158+
:param name: The name of the local to check for.
159+
:type name: str
160+
161+
:returns: True if this node has a local of the given name,
162+
False otherwise.
163+
:rtype: bool
164+
"""
165+
return name in self.locals
166+
167+
168+
class ComprehensionScope(LocalsDictNodeNG):
169+
"""Scoping for different types of comprehensions."""
170+
171+
scope_lookup = LocalsDictNodeNG._scope_lookup

astroid/nodes/scoped_nodes/scoped_nodes.py

Lines changed: 1 addition & 158 deletions
Original file line numberDiff line numberDiff line change
@@ -74,11 +74,11 @@
7474
StatementMissing,
7575
TooManyLevelsError,
7676
)
77-
from astroid.filter_statements import _filter_stmts
7877
from astroid.interpreter.dunder_lookup import lookup
7978
from astroid.interpreter.objectmodel import ClassModel, FunctionModel, ModuleModel
8079
from astroid.manager import AstroidManager
8180
from astroid.nodes import Arguments, Const, node_classes
81+
from astroid.nodes.scoped_nodes.mixin import ComprehensionScope, LocalsDictNodeNG
8282
from astroid.nodes.scoped_nodes.utils import builtin_lookup
8383
from astroid.nodes.utils import Position
8484

@@ -215,157 +215,6 @@ def function_to_method(n, klass):
215215
return n
216216

217217

218-
# TODO move this Mixin to mixins.py; problem: 'FunctionDef' in _scope_lookup
219-
class LocalsDictNodeNG(node_classes.LookupMixIn, node_classes.NodeNG):
220-
"""this class provides locals handling common to Module, FunctionDef
221-
and ClassDef nodes, including a dict like interface for direct access
222-
to locals information
223-
"""
224-
225-
# attributes below are set by the builder module or by raw factories
226-
227-
locals: Dict[str, List[node_classes.NodeNG]] = {}
228-
"""A map of the name of a local variable to the node defining the local."""
229-
230-
def qname(self):
231-
"""Get the 'qualified' name of the node.
232-
233-
For example: module.name, module.class.name ...
234-
235-
:returns: The qualified name.
236-
:rtype: str
237-
"""
238-
# pylint: disable=no-member; github.com/pycqa/astroid/issues/278
239-
if self.parent is None:
240-
return self.name
241-
return f"{self.parent.frame(future=True).qname()}.{self.name}"
242-
243-
def scope(self: T) -> T:
244-
"""The first parent node defining a new scope.
245-
246-
:returns: The first parent scope node.
247-
:rtype: Module or FunctionDef or ClassDef or Lambda or GenExpr
248-
"""
249-
return self
250-
251-
def _scope_lookup(self, node, name, offset=0):
252-
"""XXX method for interfacing the scope lookup"""
253-
try:
254-
stmts = _filter_stmts(node, self.locals[name], self, offset)
255-
except KeyError:
256-
stmts = ()
257-
if stmts:
258-
return self, stmts
259-
260-
# Handle nested scopes: since class names do not extend to nested
261-
# scopes (e.g., methods), we find the next enclosing non-class scope
262-
pscope = self.parent and self.parent.scope()
263-
while pscope is not None:
264-
if not isinstance(pscope, ClassDef):
265-
return pscope.scope_lookup(node, name)
266-
pscope = pscope.parent and pscope.parent.scope()
267-
268-
# self is at the top level of a module, or is enclosed only by ClassDefs
269-
return builtin_lookup(name)
270-
271-
def set_local(self, name, stmt):
272-
"""Define that the given name is declared in the given statement node.
273-
274-
.. seealso:: :meth:`scope`
275-
276-
:param name: The name that is being defined.
277-
:type name: str
278-
279-
:param stmt: The statement that defines the given name.
280-
:type stmt: NodeNG
281-
"""
282-
# assert not stmt in self.locals.get(name, ()), (self, stmt)
283-
self.locals.setdefault(name, []).append(stmt)
284-
285-
__setitem__ = set_local
286-
287-
def _append_node(self, child):
288-
"""append a child, linking it in the tree"""
289-
# pylint: disable=no-member; depending by the class
290-
# which uses the current class as a mixin or base class.
291-
# It's rewritten in 2.0, so it makes no sense for now
292-
# to spend development time on it.
293-
self.body.append(child)
294-
child.parent = self
295-
296-
def add_local_node(self, child_node, name=None):
297-
"""Append a child that should alter the locals of this scope node.
298-
299-
:param child_node: The child node that will alter locals.
300-
:type child_node: NodeNG
301-
302-
:param name: The name of the local that will be altered by
303-
the given child node.
304-
:type name: str or None
305-
"""
306-
if name != "__class__":
307-
# add __class__ node as a child will cause infinite recursion later!
308-
self._append_node(child_node)
309-
self.set_local(name or child_node.name, child_node)
310-
311-
def __getitem__(self, item):
312-
"""The first node the defines the given local.
313-
314-
:param item: The name of the locally defined object.
315-
:type item: str
316-
317-
:raises KeyError: If the name is not defined.
318-
"""
319-
return self.locals[item][0]
320-
321-
def __iter__(self):
322-
"""Iterate over the names of locals defined in this scoped node.
323-
324-
:returns: The names of the defined locals.
325-
:rtype: iterable(str)
326-
"""
327-
return iter(self.keys())
328-
329-
def keys(self):
330-
"""The names of locals defined in this scoped node.
331-
332-
:returns: The names of the defined locals.
333-
:rtype: list(str)
334-
"""
335-
return list(self.locals.keys())
336-
337-
def values(self):
338-
"""The nodes that define the locals in this scoped node.
339-
340-
:returns: The nodes that define locals.
341-
:rtype: list(NodeNG)
342-
"""
343-
# pylint: disable=consider-using-dict-items
344-
# It look like this class override items/keys/values,
345-
# probably not worth the headache
346-
return [self[key] for key in self.keys()]
347-
348-
def items(self):
349-
"""Get the names of the locals and the node that defines the local.
350-
351-
:returns: The names of locals and their associated node.
352-
:rtype: list(tuple(str, NodeNG))
353-
"""
354-
return list(zip(self.keys(), self.values()))
355-
356-
def __contains__(self, name):
357-
"""Check if a local is defined in this scope.
358-
359-
:param name: The name of the local to check for.
360-
:type name: str
361-
362-
:returns: True if this node has a local of the given name,
363-
False otherwise.
364-
:rtype: bool
365-
"""
366-
return name in self.locals
367-
368-
369218
class Module(LocalsDictNodeNG):
370219
"""Class representing an :class:`ast.Module` node.
371220
@@ -849,12 +698,6 @@ def frame(self: T, *, future: Literal[None, True] = None) -> T:
849698
return self
850699

851700

852-
class ComprehensionScope(LocalsDictNodeNG):
853-
"""Scoping for different types of comprehensions."""
854-
855-
scope_lookup = LocalsDictNodeNG._scope_lookup
856-
857-
858701
class GeneratorExp(ComprehensionScope):
859702
"""Class representing an :class:`ast.GeneratorExp` node.
860703

0 commit comments

Comments
 (0)