Skip to content

Commit 47d3858

Browse files
author
Guido van Rossum
committed
Support **expr in dict expressions.
1 parent da0576e commit 47d3858

File tree

7 files changed

+76
-25
lines changed

7 files changed

+76
-25
lines changed

mypy/checkexpr.py

+48-19
Original file line numberDiff line numberDiff line change
@@ -1360,29 +1360,58 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
13601360
return TupleType(items, self.chk.named_generic_type('builtins.tuple', [fallback_item]))
13611361

13621362
def visit_dict_expr(self, e: DictExpr) -> Type:
1363-
# Translate into type checking a generic function call.
1363+
"""Type check a dict expression.
1364+
1365+
Translate it into a call to dict(), with provisions for **expr.
1366+
"""
1367+
# Collect function arguments, watching out for **expr.
1368+
args = [] # type: List[Node] # Regular "key: value"
1369+
stargs = [] # type: List[Node] # For "**expr"
1370+
for key, value in e.items:
1371+
if key is None:
1372+
stargs.append(value)
1373+
else:
1374+
args.append(TupleExpr([key, value]))
1375+
# Define type variables (used in constructors below).
13641376
ktdef = TypeVarDef('KT', -1, [], self.chk.object_type())
13651377
vtdef = TypeVarDef('VT', -2, [], self.chk.object_type())
13661378
kt = TypeVarType(ktdef)
13671379
vt = TypeVarType(vtdef)
1368-
# The callable type represents a function like this:
1369-
#
1370-
# def <unnamed>(*v: Tuple[kt, vt]) -> Dict[kt, vt]: ...
1371-
constructor = CallableType(
1372-
[TupleType([kt, vt], self.named_type('builtins.tuple'))],
1373-
[nodes.ARG_STAR],
1374-
[None],
1375-
self.chk.named_generic_type('builtins.dict', [kt, vt]),
1376-
self.named_type('builtins.function'),
1377-
name='<list>',
1378-
variables=[ktdef, vtdef])
1379-
# Synthesize function arguments.
1380-
args = [] # type: List[Node]
1381-
for key, value in e.items:
1382-
args.append(TupleExpr([key, value]))
1383-
return self.check_call(constructor,
1384-
args,
1385-
[nodes.ARG_POS] * len(args), e)[0]
1380+
# Call dict(*args), unless it's empty and stargs is not.
1381+
if args or not stargs:
1382+
# The callable type represents a function like this:
1383+
#
1384+
# def <unnamed>(*v: Tuple[kt, vt]) -> Dict[kt, vt]: ...
1385+
constructor = CallableType(
1386+
[TupleType([kt, vt], self.named_type('builtins.tuple'))],
1387+
[nodes.ARG_STAR],
1388+
[None],
1389+
self.chk.named_generic_type('builtins.dict', [kt, vt]),
1390+
self.named_type('builtins.function'),
1391+
name='<list>',
1392+
variables=[ktdef, vtdef])
1393+
rv = self.check_call(constructor, args, [nodes.ARG_POS] * len(args), e)[0]
1394+
else:
1395+
# dict(...) will be called below.
1396+
rv = None
1397+
# Call rv.update(arg) for each arg in **stargs,
1398+
# except if rv isn't set yet, then set rv = dict(arg).
1399+
if stargs:
1400+
for arg in stargs:
1401+
if rv is None:
1402+
constructor = CallableType(
1403+
[self.chk.named_generic_type('typing.Mapping', [kt, vt])],
1404+
[nodes.ARG_POS],
1405+
[None],
1406+
self.chk.named_generic_type('builtins.dict', [kt, vt]),
1407+
self.named_type('builtins.function'),
1408+
name='<list>',
1409+
variables=[ktdef, vtdef])
1410+
rv = self.check_call(constructor, [arg], [nodes.ARG_POS], arg)[0]
1411+
else:
1412+
method = self.analyze_external_member_access('update', rv, arg)
1413+
self.check_call(method, [arg], [nodes.ARG_POS], arg)
1414+
return rv
13861415

13871416
def visit_func_expr(self, e: FuncExpr) -> Type:
13881417
"""Type check lambda expression."""

mypy/nodes.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -1457,7 +1457,9 @@ class DictExpr(Expression):
14571457

14581458
def __init__(self, items: List[Tuple[Expression, Expression]]) -> None:
14591459
self.items = items
1460-
if all(x[0].literal == LITERAL_YES and x[1].literal == LITERAL_YES
1460+
# key is None for **item, e.g. {'a': 1, **x} has
1461+
# keys ['a', None] and values [1, x].
1462+
if all(x[0] and x[0].literal == LITERAL_YES and x[1].literal == LITERAL_YES
14611463
for x in items):
14621464
self.literal = LITERAL_YES
14631465
self.literal_hash = ('Dict',) + tuple(

mypy/semanal.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -1937,7 +1937,8 @@ def visit_set_expr(self, expr: SetExpr) -> None:
19371937

19381938
def visit_dict_expr(self, expr: DictExpr) -> None:
19391939
for key, value in expr.items:
1940-
key.accept(self)
1940+
if key is not None:
1941+
key.accept(self)
19411942
value.accept(self)
19421943

19431944
def visit_star_expr(self, expr: StarExpr) -> None:

mypy/traverser.py

+2-1
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,8 @@ def visit_tuple_expr(self, o: TupleExpr) -> None:
180180

181181
def visit_dict_expr(self, o: DictExpr) -> None:
182182
for k, v in o.items:
183-
k.accept(self)
183+
if k is not None:
184+
k.accept(self)
184185
v.accept(self)
185186

186187
def visit_set_expr(self, o: SetExpr) -> None:

test-data/unit/check-expressions.test

+16
Original file line numberDiff line numberDiff line change
@@ -1503,3 +1503,19 @@ None == None
15031503
[case testLtNone]
15041504
None < None # E: Unsupported left operand type for < (None)
15051505
[builtins fixtures/ops.py]
1506+
1507+
[case testDictWithStarExpr]
1508+
# options: fast_parser
1509+
b = {'z': 26, *a} # E: invalid syntax
1510+
[builtins fixtures/dict.py]
1511+
1512+
[case testDictWithStarStarExpr]
1513+
# options: fast_parser
1514+
from typing import Dict
1515+
a = {'a': 1}
1516+
b = {'z': 26, **a}
1517+
c = {**b}
1518+
d = {**a, **b, 'c': 3}
1519+
e = {1: 'a', **a} # E: Argument 1 to "update" of "dict" has incompatible type Dict[str, int]; expected Mapping[int, str]
1520+
f = {**b} # type: Dict[int, int] # E: List item 0 has incompatible type Dict[str, int]
1521+
[builtins fixtures/dict.py]

test-data/unit/fixtures/dict.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Builtins stub used in dictionary-related test cases.
22

3-
from typing import TypeVar, Generic, Iterable, Iterator, Tuple, overload
3+
from typing import TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload
44

55
T = TypeVar('T')
66
KT = TypeVar('KT')
@@ -11,14 +11,14 @@ def __init__(self) -> None: pass
1111

1212
class type: pass
1313

14-
class dict(Iterable[KT], Generic[KT, VT]):
14+
class dict(Iterable[KT], Mapping[KT, VT], Generic[KT, VT]):
1515
@overload
1616
def __init__(self, **kwargs: VT) -> None: pass
1717
@overload
1818
def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: pass
1919
def __setitem__(self, k: KT, v: VT) -> None: pass
2020
def __iter__(self) -> Iterator[KT]: pass
21-
def update(self, a: 'dict[KT, VT]') -> None: pass
21+
def update(self, a: Mapping[KT, VT]) -> None: pass
2222

2323
class int: pass # for convenience
2424

test-data/unit/lib-stub/typing.py

+2
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,8 @@ class Sequence(Iterable[T], Generic[T]):
7474
@abstractmethod
7575
def __getitem__(self, n: Any) -> T: pass
7676

77+
class Mapping(Generic[T, U]): pass
78+
7779
def NewType(name: str, tp: Type[T]) -> Callable[[T], T]:
7880
def new_type(x):
7981
return x

0 commit comments

Comments
 (0)