Skip to content

Commit e41a896

Browse files
authored
Move _filter_stmts into its own file (#1303)
We need access to all types of nodes in _filter_stmts. Not only those in node_classes (the file in which it was defined). This change shows that by moving _filter_stmts into its own file we can import the nodes module and access all of them. This will allow to fix some issue with it. I have tried to keep the change as minimal as possible and only changed how we refer to nodes.
1 parent df854be commit e41a896

File tree

3 files changed

+240
-225
lines changed

3 files changed

+240
-225
lines changed

astroid/filter_statements.py

+238
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
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+
"""_filter_stmts and helper functions. This method gets used in LocalsDictnodes.NodeNG._scope_lookup.
5+
It is not considered public.
6+
"""
7+
8+
from typing import List, Optional, Tuple
9+
10+
from astroid import nodes
11+
12+
13+
def _get_filtered_node_statements(
14+
base_node: nodes.NodeNG, stmt_nodes: List[nodes.NodeNG]
15+
) -> List[Tuple[nodes.NodeNG, nodes.Statement]]:
16+
statements = [(node, node.statement(future=True)) for node in stmt_nodes]
17+
# Next we check if we have ExceptHandlers that are parent
18+
# of the underlying variable, in which case the last one survives
19+
if len(statements) > 1 and all(
20+
isinstance(stmt, nodes.ExceptHandler) for _, stmt in statements
21+
):
22+
statements = [
23+
(node, stmt) for node, stmt in statements if stmt.parent_of(base_node)
24+
]
25+
return statements
26+
27+
28+
def _is_from_decorator(node):
29+
"""Return True if the given node is the child of a decorator"""
30+
return any(isinstance(parent, nodes.Decorators) for parent in node.node_ancestors())
31+
32+
33+
def _get_if_statement_ancestor(node: nodes.NodeNG) -> Optional[nodes.If]:
34+
"""Return the first parent node that is an If node (or None)"""
35+
for parent in node.node_ancestors():
36+
if isinstance(parent, nodes.If):
37+
return parent
38+
return None
39+
40+
41+
def _filter_stmts(base_node: nodes.NodeNG, stmts, frame, offset):
42+
"""Filter the given list of statements to remove ignorable statements.
43+
44+
If base_node is not a frame itself and the name is found in the inner
45+
frame locals, statements will be filtered to remove ignorable
46+
statements according to base_node's location.
47+
48+
:param stmts: The statements to filter.
49+
:type stmts: list(nodes.NodeNG)
50+
51+
:param frame: The frame that all of the given statements belong to.
52+
:type frame: nodes.NodeNG
53+
54+
:param offset: The line offset to filter statements up to.
55+
:type offset: int
56+
57+
:returns: The filtered statements.
58+
:rtype: list(nodes.NodeNG)
59+
"""
60+
# if offset == -1, my actual frame is not the inner frame but its parent
61+
#
62+
# class A(B): pass
63+
#
64+
# we need this to resolve B correctly
65+
if offset == -1:
66+
myframe = base_node.frame().parent.frame()
67+
else:
68+
myframe = base_node.frame()
69+
# If the frame of this node is the same as the statement
70+
# of this node, then the node is part of a class or
71+
# a function definition and the frame of this node should be the
72+
# the upper frame, not the frame of the definition.
73+
# For more information why this is important,
74+
# see Pylint issue #295.
75+
# For example, for 'b', the statement is the same
76+
# as the frame / scope:
77+
#
78+
# def test(b=1):
79+
# ...
80+
if (
81+
base_node.parent
82+
and base_node.statement(future=True) is myframe
83+
and myframe.parent
84+
):
85+
myframe = myframe.parent.frame()
86+
87+
mystmt: Optional[nodes.Statement] = None
88+
if base_node.parent:
89+
mystmt = base_node.statement(future=True)
90+
91+
# line filtering if we are in the same frame
92+
#
93+
# take care node may be missing lineno information (this is the case for
94+
# nodes inserted for living objects)
95+
if myframe is frame and mystmt and mystmt.fromlineno is not None:
96+
assert mystmt.fromlineno is not None, mystmt
97+
mylineno = mystmt.fromlineno + offset
98+
else:
99+
# disabling lineno filtering
100+
mylineno = 0
101+
102+
_stmts = []
103+
_stmt_parents = []
104+
statements = _get_filtered_node_statements(base_node, stmts)
105+
for node, stmt in statements:
106+
# line filtering is on and we have reached our location, break
107+
if stmt.fromlineno and stmt.fromlineno > mylineno > 0:
108+
break
109+
# Ignore decorators with the same name as the
110+
# decorated function
111+
# Fixes issue #375
112+
if mystmt is stmt and _is_from_decorator(base_node):
113+
continue
114+
assert hasattr(node, "assign_type"), (
115+
node,
116+
node.scope(),
117+
node.scope().locals,
118+
)
119+
assign_type = node.assign_type()
120+
if node.has_base(base_node):
121+
break
122+
123+
_stmts, done = assign_type._get_filtered_stmts(base_node, node, _stmts, mystmt)
124+
if done:
125+
break
126+
127+
optional_assign = assign_type.optional_assign
128+
if optional_assign and assign_type.parent_of(base_node):
129+
# we are inside a loop, loop var assignment is hiding previous
130+
# assignment
131+
_stmts = [node]
132+
_stmt_parents = [stmt.parent]
133+
continue
134+
135+
if isinstance(assign_type, nodes.NamedExpr):
136+
# If the NamedExpr is in an if statement we do some basic control flow inference
137+
if_parent = _get_if_statement_ancestor(assign_type)
138+
if if_parent:
139+
# If the if statement is within another if statement we append the node
140+
# to possible statements
141+
if _get_if_statement_ancestor(if_parent):
142+
optional_assign = False
143+
_stmts.append(node)
144+
_stmt_parents.append(stmt.parent)
145+
# If the if statement is first-level and not within an orelse block
146+
# we know that it will be evaluated
147+
elif not if_parent.is_orelse:
148+
_stmts = [node]
149+
_stmt_parents = [stmt.parent]
150+
# Else we do not known enough about the control flow to be 100% certain
151+
# and we append to possible statements
152+
else:
153+
_stmts.append(node)
154+
_stmt_parents.append(stmt.parent)
155+
else:
156+
_stmts = [node]
157+
_stmt_parents = [stmt.parent]
158+
159+
# XXX comment various branches below!!!
160+
try:
161+
pindex = _stmt_parents.index(stmt.parent)
162+
except ValueError:
163+
pass
164+
else:
165+
# we got a parent index, this means the currently visited node
166+
# is at the same block level as a previously visited node
167+
if _stmts[pindex].assign_type().parent_of(assign_type):
168+
# both statements are not at the same block level
169+
continue
170+
# if currently visited node is following previously considered
171+
# assignment and both are not exclusive, we can drop the
172+
# previous one. For instance in the following code ::
173+
#
174+
# if a:
175+
# x = 1
176+
# else:
177+
# x = 2
178+
# print x
179+
#
180+
# we can't remove neither x = 1 nor x = 2 when looking for 'x'
181+
# of 'print x'; while in the following ::
182+
#
183+
# x = 1
184+
# x = 2
185+
# print x
186+
#
187+
# we can remove x = 1 when we see x = 2
188+
#
189+
# moreover, on loop assignment types, assignment won't
190+
# necessarily be done if the loop has no iteration, so we don't
191+
# want to clear previous assignments if any (hence the test on
192+
# optional_assign)
193+
if not (optional_assign or nodes.are_exclusive(_stmts[pindex], node)):
194+
del _stmt_parents[pindex]
195+
del _stmts[pindex]
196+
197+
# If base_node and node are exclusive, then we can ignore node
198+
if nodes.are_exclusive(base_node, node):
199+
continue
200+
201+
# An AssignName node overrides previous assignments if:
202+
# 1. node's statement always assigns
203+
# 2. node and base_node are in the same block (i.e., has the same parent as base_node)
204+
if isinstance(node, (nodes.NamedExpr, nodes.AssignName)):
205+
if isinstance(stmt, nodes.ExceptHandler):
206+
# If node's statement is an ExceptHandler, then it is the variable
207+
# bound to the caught exception. If base_node is not contained within
208+
# the exception handler block, node should override previous assignments;
209+
# otherwise, node should be ignored, as an exception variable
210+
# is local to the handler block.
211+
if stmt.parent_of(base_node):
212+
_stmts = []
213+
_stmt_parents = []
214+
else:
215+
continue
216+
elif not optional_assign and mystmt and stmt.parent is mystmt.parent:
217+
_stmts = []
218+
_stmt_parents = []
219+
elif isinstance(node, nodes.DelName):
220+
# Remove all previously stored assignments
221+
_stmts = []
222+
_stmt_parents = []
223+
continue
224+
# Add the new assignment
225+
_stmts.append(node)
226+
if isinstance(node, nodes.Arguments) or isinstance(
227+
node.parent, nodes.Arguments
228+
):
229+
# Special case for _stmt_parents when node is a function parameter;
230+
# in this case, stmt is the enclosing FunctionDef, which is what we
231+
# want to add to _stmt_parents, not stmt.parent. This case occurs when
232+
# node is an Arguments node (representing varargs or kwargs parameter),
233+
# and when node.parent is an Arguments node (other parameters).
234+
# See issue #180.
235+
_stmt_parents.append(stmt)
236+
else:
237+
_stmt_parents.append(stmt.parent)
238+
return _stmts

0 commit comments

Comments
 (0)