Skip to content

Commit f8155a0

Browse files
committed
The last except handler wins when inferring variables bound in an except handler.
Close pylint-dev/pylint#2777
1 parent 7a88072 commit f8155a0

File tree

3 files changed

+77
-2
lines changed

3 files changed

+77
-2
lines changed

ChangeLog

+4
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ Release Date: 2019-03-02
4242
Close PyCQA/pylint#2776
4343

4444

45+
* The last except handler wins when inferring variables bound in an except handler.
46+
47+
Close PyCQA/pylint#2777
48+
4549
What's New in astroid 2.2.0?
4650
============================
4751
Release Date: 2019-02-27

astroid/node_classes.py

+14-2
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,16 @@ def ilookup(self, name):
10961096
context = contextmod.InferenceContext()
10971097
return bases._infer_stmts(stmts, context, frame)
10981098

1099+
def _get_filtered_node_statements(self, nodes):
1100+
statements = {node: node.statement() for node in nodes}
1101+
# Next we check if we have ExceptHandlers that are parent
1102+
# of the underlying variable, in which case the last one survives
1103+
if all(isinstance(stmt, ExceptHandler) for stmt in statements.values()):
1104+
statements = {
1105+
node: stmt for node, stmt in statements.items() if stmt.parent_of(self)
1106+
}
1107+
return statements
1108+
10991109
def _filter_stmts(self, stmts, frame, offset):
11001110
"""Filter the given list of statements to remove ignorable statements.
11011111
@@ -1149,10 +1159,12 @@ def _filter_stmts(self, stmts, frame, offset):
11491159
else:
11501160
# disabling lineno filtering
11511161
mylineno = 0
1162+
11521163
_stmts = []
11531164
_stmt_parents = []
1154-
for node in stmts:
1155-
stmt = node.statement()
1165+
statements = self._get_filtered_node_statements(stmts)
1166+
1167+
for node, stmt in statements.items():
11561168
# line filtering is on and we have reached our location, break
11571169
if stmt.fromlineno > mylineno > 0:
11581170
break

astroid/tests/unittest_inference.py

+59
Original file line numberDiff line numberDiff line change
@@ -5090,5 +5090,64 @@ def test(self):
50905090
assert isinstance(inferred, Slice)
50915091

50925092

5093+
def test_exception_lookup_last_except_handler_wins():
5094+
node = extract_node(
5095+
"""
5096+
try:
5097+
1/0
5098+
except ValueError as exc:
5099+
pass
5100+
try:
5101+
1/0
5102+
except OSError as exc:
5103+
exc #@
5104+
"""
5105+
)
5106+
inferred = node.inferred()
5107+
assert len(inferred) == 1
5108+
inferred_exc = inferred[0]
5109+
assert isinstance(inferred_exc, Instance)
5110+
assert inferred_exc.name == "OSError"
5111+
5112+
# Check that two except handlers on the same TryExcept works the same as separate
5113+
# TryExcepts
5114+
node = extract_node(
5115+
"""
5116+
try:
5117+
1/0
5118+
except ZeroDivisionError as exc:
5119+
pass
5120+
except ValueError as exc:
5121+
exc #@
5122+
"""
5123+
)
5124+
inferred = node.inferred()
5125+
assert len(inferred) == 1
5126+
inferred_exc = inferred[0]
5127+
assert isinstance(inferred_exc, Instance)
5128+
assert inferred_exc.name == "ValueError"
5129+
5130+
5131+
def test_exception_lookup_name_bound_in_except_handler():
5132+
node = extract_node(
5133+
"""
5134+
try:
5135+
1/0
5136+
except ValueError:
5137+
name = 1
5138+
try:
5139+
1/0
5140+
except OSError:
5141+
name = 2
5142+
name #@
5143+
"""
5144+
)
5145+
inferred = node.inferred()
5146+
assert len(inferred) == 1
5147+
inferred_exc = inferred[0]
5148+
assert isinstance(inferred_exc, nodes.Const)
5149+
assert inferred_exc.value == 2
5150+
5151+
50935152
if __name__ == "__main__":
50945153
unittest.main()

0 commit comments

Comments
 (0)