Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Backport maintenance/3.3.x] fix issue when inferring single-node or non-const JoinedStr #2579

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
34 changes: 16 additions & 18 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
31 changes: 31 additions & 0 deletions tests/test_inference.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import pytest

from astroid import (
Assign,
Const,
Slice,
Uninferable,
Expand Down Expand Up @@ -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)
Loading