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

Implement inference for JoinedStr and FormattedValue #2459

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
3 changes: 3 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ Release date: TBA

Closes pylint-dev/pylint#7126

* Implement inference for JoinedStr and FormattedValue



What's New in astroid 3.2.5?
============================
Expand Down
59 changes: 59 additions & 0 deletions astroid/nodes/node_classes.py
Original file line number Diff line number Diff line change
Expand Up @@ -4673,6 +4673,37 @@ def get_children(self):
if self.format_spec is not None:
yield self.format_spec

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
uninferable_already_generated = False
for format_spec in self.format_spec.infer(context, **kwargs):
if not isinstance(format_spec, Const):
if not uninferable_already_generated:
yield util.Uninferable
uninferable_already_generated = True
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved
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,
)


MISSING_VALUE = "{MISSING_VALUE}"


class JoinedStr(NodeNG):
"""Represents a list of string expressions to be joined.
Expand Down Expand Up @@ -4734,6 +4765,34 @@ def postinit(self, values: list[NodeNG] | None = None) -> None:
def get_children(self):
yield from self.values

def _infer(
self, context: InferenceContext | None = None, **kwargs: Any
) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
yield from self._infer_from_values(self.values, context)

@classmethod
def _infer_from_values(
cls, nodes: list[NodeNG], context: InferenceContext | None = None, **kwargs: Any
jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved
) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
if len(nodes) == 1:
yield from nodes[0]._infer(context, **kwargs)
return
uninferable_already_generated = False
for prefix in nodes[0]._infer(context, **kwargs):
for suffix in cls._infer_from_values(nodes[1:], context, **kwargs):
result = ""
for node in (prefix, suffix):
if isinstance(node, Const):
result += str(node.value)
continue
result += MISSING_VALUE
if MISSING_VALUE in result:
if not uninferable_already_generated:
uninferable_already_generated = True
yield util.Uninferable
else:
yield Const(result)


class NamedExpr(_base_nodes.AssignTypeNode):
"""Represents the assignment from the assignment expression
Expand Down
29 changes: 29 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 (
Const,
Slice,
Uninferable,
arguments,
Expand Down Expand Up @@ -652,6 +653,34 @@ def process_line(word_pos):
)
)

def test_fstring_inference(self) -> None:
code = """
name = "John"
result = f"Hello {name}!"
"""
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, "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!")

jacobtylerwalls marked this conversation as resolved.
Show resolved Hide resolved
def test_float_complex_ambiguity(self) -> None:
code = '''
def no_conjugate_member(magic_flag): #@
Expand Down
Loading