Skip to content

Commit 805a3f6

Browse files
authored
Improve consistency of JoinedStr inference (#2622)
Co-authored-by: Eric Vergnaud <eric.vergnaud@databricks.com>
1 parent b114f6b commit 805a3f6

File tree

3 files changed

+55
-16
lines changed

3 files changed

+55
-16
lines changed

ChangeLog

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ Release date: TBA
1717
Closes #2521
1818
Closes #2523
1919

20+
* Improve consistency of ``JoinedStr`` inference by not raising ``InferenceError`` and
21+
returning either ``Uninferable`` or a fully resolved ``Const``.
22+
23+
Closes #2621
24+
2025
* Fix crash when typing._alias() call is missing arguments.
2126

2227
Closes #2513

astroid/nodes/node_classes.py

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4715,7 +4715,7 @@ def _infer(
47154715
continue
47164716

47174717

4718-
MISSING_VALUE = "{MISSING_VALUE}"
4718+
UNINFERABLE_VALUE = "{Uninferable}"
47194719

47204720

47214721
class JoinedStr(NodeNG):
@@ -4781,33 +4781,59 @@ def get_children(self):
47814781
def _infer(
47824782
self, context: InferenceContext | None = None, **kwargs: Any
47834783
) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
4784-
yield from self._infer_from_values(self.values, context)
4784+
if self.values:
4785+
yield from self._infer_with_values(context)
4786+
else:
4787+
yield Const("")
4788+
4789+
def _infer_with_values(
4790+
self, context: InferenceContext | None = None, **kwargs: Any
4791+
) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
4792+
uninferable_already_generated = False
4793+
for inferred in self._infer_from_values(self.values, context):
4794+
failed = (
4795+
inferred is util.Uninferable
4796+
or isinstance(inferred, Const)
4797+
and UNINFERABLE_VALUE in inferred.value
4798+
)
4799+
if failed:
4800+
if not uninferable_already_generated:
4801+
uninferable_already_generated = True
4802+
yield util.Uninferable
4803+
continue
4804+
yield inferred
47854805

47864806
@classmethod
47874807
def _infer_from_values(
47884808
cls, nodes: list[NodeNG], context: InferenceContext | None = None, **kwargs: Any
47894809
) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
47904810
if not nodes:
4791-
yield
47924811
return
47934812
if len(nodes) == 1:
4794-
yield from nodes[0]._infer(context, **kwargs)
4813+
for node in cls._safe_infer_from_node(nodes[0], context, **kwargs):
4814+
if isinstance(node, Const):
4815+
yield node
4816+
continue
4817+
yield Const(UNINFERABLE_VALUE)
47954818
return
4796-
uninferable_already_generated = False
4797-
for prefix in nodes[0]._infer(context, **kwargs):
4819+
for prefix in cls._safe_infer_from_node(nodes[0], context, **kwargs):
47984820
for suffix in cls._infer_from_values(nodes[1:], context, **kwargs):
47994821
result = ""
48004822
for node in (prefix, suffix):
48014823
if isinstance(node, Const):
48024824
result += str(node.value)
48034825
continue
4804-
result += MISSING_VALUE
4805-
if MISSING_VALUE in result:
4806-
if not uninferable_already_generated:
4807-
uninferable_already_generated = True
4808-
yield util.Uninferable
4809-
else:
4810-
yield Const(result)
4826+
result += UNINFERABLE_VALUE
4827+
yield Const(result)
4828+
4829+
@classmethod
4830+
def _safe_infer_from_node(
4831+
cls, node: NodeNG, context: InferenceContext | None = None, **kwargs: Any
4832+
) -> Generator[InferenceResult, None, InferenceErrorInfo | None]:
4833+
try:
4834+
yield from node._infer(context, **kwargs)
4835+
except InferenceError:
4836+
yield util.Uninferable
48114837

48124838

48134839
class NamedExpr(_base_nodes.AssignTypeNode):

tests/test_inference.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7402,7 +7402,12 @@ class Cls:
74027402
""",
74037403
"<__main__.Cls",
74047404
),
7405-
("s1 = f'{5}' #@", "5"),
7405+
(
7406+
"s1 = f'{5}' #@",
7407+
"5",
7408+
),
7409+
("s1 = f'{missing}'", None),
7410+
("s1 = f'a/{missing}/b'", None),
74067411
],
74077412
)
74087413
def test_joined_str_returns_string(source, expected) -> None:
@@ -7413,5 +7418,8 @@ def test_joined_str_returns_string(source, expected) -> None:
74137418
assert target
74147419
inferred = list(target.inferred())
74157420
assert len(inferred) == 1
7416-
assert isinstance(inferred[0], Const)
7417-
inferred[0].value.startswith(expected)
7421+
if expected:
7422+
assert isinstance(inferred[0], Const)
7423+
inferred[0].value.startswith(expected)
7424+
else:
7425+
assert inferred[0] is Uninferable

0 commit comments

Comments
 (0)