From e6818188bdf1e5f98925e2a3e07b68548dd98711 Mon Sep 17 00:00:00 2001 From: Eric Vergnaud Date: Mon, 23 Sep 2024 19:53:55 +0200 Subject: [PATCH] Fix issue when inferring single-node or non-const JoinedStr (#2578) (cherry picked from commit 8585ce64d2e53aeb556e3c49b866571f1276f9cb) --- ChangeLog | 4 ++++ astroid/nodes/node_classes.py | 34 ++++++++++++++++------------------ tests/test_inference.py | 31 +++++++++++++++++++++++++++++++ 3 files changed, 51 insertions(+), 18 deletions(-) diff --git a/ChangeLog b/ChangeLog index 1f6ee6d560..d5f5207030 100644 --- a/ChangeLog +++ b/ChangeLog @@ -13,6 +13,10 @@ What's New in astroid 3.3.4? ============================ Release date: TBA +* Fix regression with f-string inference. + + Closes pylint-dev/pylint#9947 + * Fix bug with ``manager.clear_cache()`` not fully clearing cache Refs https://github.com/pylint-dev/pylint/pull/9932#issuecomment-2364985551 diff --git a/astroid/nodes/node_classes.py b/astroid/nodes/node_classes.py index 48326e2194..6845ca99c1 100644 --- a/astroid/nodes/node_classes.py +++ b/astroid/nodes/node_classes.py @@ -4676,32 +4676,30 @@ def get_children(self): def _infer( self, context: InferenceContext | None = None, **kwargs: Any ) -> Generator[InferenceResult, None, InferenceErrorInfo | None]: - if self.format_spec is None: - yield from self.value.infer(context, **kwargs) - return + format_specs = Const("") if self.format_spec is None else self.format_spec uninferable_already_generated = False - for format_spec in self.format_spec.infer(context, **kwargs): + for format_spec in format_specs.infer(context, **kwargs): if not isinstance(format_spec, Const): if not uninferable_already_generated: yield util.Uninferable uninferable_already_generated = True continue for value in self.value.infer(context, **kwargs): + value_to_format = value 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: + value_to_format = value.value + try: + formatted = format(value_to_format, 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 yield util.Uninferable uninferable_already_generated = True continue diff --git a/tests/test_inference.py b/tests/test_inference.py index daa5613565..acc8a2b1f8 100644 --- a/tests/test_inference.py +++ b/tests/test_inference.py @@ -19,6 +19,7 @@ import pytest from astroid import ( + Assign, Const, Slice, Uninferable, @@ -7388,3 +7389,33 @@ def test_empty_format_spec() -> None: assert isinstance(node, nodes.JoinedStr) assert list(node.infer()) == [util.Uninferable] + + +@pytest.mark.parametrize( + "source, expected", + [ + ( + """ +class Cls: + # pylint: disable=too-few-public-methods + pass + +c_obj = Cls() + +s1 = f'{c_obj!r}' #@ +""", + "<__main__.Cls", + ), + ("s1 = f'{5}' #@", "5"), + ], +) +def test_joined_str_returns_string(source, expected) -> None: + """Regression test for https://github.com/pylint-dev/pylint/issues/9947.""" + node = extract_node(source) + assert isinstance(node, Assign) + target = node.targets[0] + assert target + inferred = list(target.inferred()) + assert len(inferred) == 1 + assert isinstance(inferred[0], Const) + inferred[0].value.startswith(expected)