Skip to content

Commit c213532

Browse files
isidenticalcdce8p
andauthored
Infer the actual unpacked value on Dict's getitem() (#1196)
Co-authored-by: Marc Mueller <[email protected]>
1 parent 39c2a98 commit c213532

File tree

3 files changed

+46
-4
lines changed

3 files changed

+46
-4
lines changed

ChangeLog

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ Release date: TBA
5555
* Fix test for Python ``3.11``. In some instances ``err.__traceback__`` will
5656
be uninferable now.
5757

58+
* Infer the ``DictUnpack`` value for ``Dict.getitem`` calls.
59+
60+
Closes #1195
61+
5862
What's New in astroid 2.11.6?
5963
=============================
6064
Release date: TBA

astroid/nodes/node_classes.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2346,24 +2346,33 @@ def itered(self):
23462346
"""
23472347
return [key for (key, _) in self.items]
23482348

2349-
def getitem(self, index, context=None):
2349+
def getitem(
2350+
self, index: Const | Slice, context: InferenceContext | None = None
2351+
) -> NodeNG:
23502352
"""Get an item from this node.
23512353
23522354
:param index: The node to use as a subscript index.
2353-
:type index: Const or Slice
23542355
23552356
:raises AstroidTypeError: When the given index cannot be used as a
23562357
subscript index, or if this node is not subscriptable.
23572358
:raises AstroidIndexError: If the given index does not exist in the
23582359
dictionary.
23592360
"""
2361+
# pylint: disable-next=import-outside-toplevel; circular import
2362+
from astroid.helpers import safe_infer
2363+
23602364
for key, value in self.items:
23612365
# TODO(cpopa): no support for overriding yet, {1:2, **{1: 3}}.
23622366
if isinstance(key, DictUnpack):
2367+
inferred_value = safe_infer(value, context)
2368+
if not isinstance(inferred_value, Dict):
2369+
continue
2370+
23632371
try:
2364-
return value.getitem(index, context)
2372+
return inferred_value.getitem(index, context)
23652373
except (AstroidTypeError, AstroidIndexError):
23662374
continue
2375+
23672376
for inferredkey in key.infer(context):
23682377
if inferredkey is util.Uninferable:
23692378
continue

tests/unittest_python3.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import unittest
66
from textwrap import dedent
77

8-
from astroid import nodes
8+
import pytest
9+
10+
from astroid import exceptions, nodes
911
from astroid.builder import AstroidBuilder, extract_node
1012
from astroid.test_utils import require_version
1113

@@ -285,6 +287,33 @@ def test_unpacking_in_dict_getitem(self) -> None:
285287
self.assertIsInstance(value, nodes.Const)
286288
self.assertEqual(value.value, expected)
287289

290+
@staticmethod
291+
def test_unpacking_in_dict_getitem_with_ref() -> None:
292+
node = extract_node(
293+
"""
294+
a = {1: 2}
295+
{**a, 2: 3} #@
296+
"""
297+
)
298+
assert isinstance(node, nodes.Dict)
299+
300+
for key, expected in ((1, 2), (2, 3)):
301+
value = node.getitem(nodes.Const(key))
302+
assert isinstance(value, nodes.Const)
303+
assert value.value == expected
304+
305+
@staticmethod
306+
def test_unpacking_in_dict_getitem_uninferable() -> None:
307+
node = extract_node("{**a, 2: 3}")
308+
assert isinstance(node, nodes.Dict)
309+
310+
with pytest.raises(exceptions.AstroidIndexError):
311+
node.getitem(nodes.Const(1))
312+
313+
value = node.getitem(nodes.Const(2))
314+
assert isinstance(value, nodes.Const)
315+
assert value.value == 3
316+
288317
def test_format_string(self) -> None:
289318
code = "f'{greetings} {person}'"
290319
node = extract_node(code)

0 commit comments

Comments
 (0)