Skip to content

Commit

Permalink
Fix pylint regression with invalid format strings (#2496) (#2497)
Browse files Browse the repository at this point in the history
Catch exceptions when calling string.format

(cherry picked from commit 04f4f3f)

Co-authored-by: Eric Vergnaud <eric.vergnaud@wanadoo.fr>
  • Loading branch information
github-actions[bot] and ericvergnaud authored Aug 6, 2024
1 parent 47030b1 commit 8357bd3
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 28 deletions.
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ What's New in astroid 3.3.1?
============================
Release date: TBA

* Fix a crash introduced in 3.3.0 involving invalid format strings.

Closes #2492


What's New in astroid 3.3.0?
Expand Down
31 changes: 18 additions & 13 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4687,19 +4687,24 @@ def _infer(
uninferable_already_generated = True
continue
for value in self.value.infer(context, **kwargs):
if not isinstance(value, Const):
if not uninferable_already_generated:
yield util.Uninferable
uninferable_already_generated = True
continue
formatted = format(value.value, format_spec.value)
yield Const(
formatted,
lineno=self.lineno,
col_offset=self.col_offset,
end_lineno=self.end_lineno,
end_col_offset=self.end_col_offset,
)
if isinstance(value, Const):
try:
formatted = format(value.value, format_spec.value)
yield Const(
formatted,
lineno=self.lineno,
col_offset=self.col_offset,
end_lineno=self.end_lineno,
end_col_offset=self.end_col_offset,
)
continue
except (ValueError, TypeError):
# happens when format_spec.value is invalid
pass # fall through
if not uninferable_already_generated:
yield util.Uninferable
uninferable_already_generated = True
continue


MISSING_VALUE = "{MISSING_VALUE}"
Expand Down
60 changes: 45 additions & 15 deletions tests/test_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -666,21 +666,6 @@ def test_fstring_inference(self) -> None:
self.assertIsInstance(value_node, Const)
self.assertEqual(value_node.value, "Hello John!")

def test_formatted_fstring_inference(self) -> None:
code = """
width = 10
precision = 4
value = 12.34567
result = f"result: {value:{width}.{precision}}!"
"""
ast = parse(code, __name__)
node = ast["result"]
inferred = node.inferred()
self.assertEqual(len(inferred), 1)
value_node = inferred[0]
self.assertIsInstance(value_node, Const)
self.assertEqual(value_node.value, "result: 12.35!")

def test_float_complex_ambiguity(self) -> None:
code = '''
def no_conjugate_member(magic_flag): #@
Expand Down Expand Up @@ -5517,6 +5502,51 @@ class instance(object):
self.assertIsInstance(inferred, Instance)


@pytest.mark.parametrize(
"code, result",
[
# regular f-string
(
"""width = 10
precision = 4
value = 12.34567
result = f"result: {value:{width}.{precision}}!"
""",
"result: 12.35!",
),
# unsupported format
(
"""width = None
precision = 4
value = 12.34567
result = f"result: {value:{width}.{precision}}!"
""",
None,
),
# unsupported value
(
"""width = 10
precision = 4
value = None
result = f"result: {value:{width}.{precision}}!"
""",
None,
),
],
)
def test_formatted_fstring_inference(code, result) -> None:
ast = parse(code, __name__)
node = ast["result"]
inferred = node.inferred()
assert len(inferred) == 1
value_node = inferred[0]
if result is None:
assert value_node is util.Uninferable
else:
assert isinstance(value_node, Const)
assert value_node.value == result


def test_augassign_recursion() -> None:
"""Make sure inference doesn't throw a RecursionError.
Expand Down

0 comments on commit 8357bd3

Please sign in to comment.