Skip to content

Support **expr in dict (PEP 448) #1960

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

Merged
merged 1 commit into from
Jul 29, 2016
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
67 changes: 48 additions & 19 deletions mypy/checkexpr.py
Original file line number Diff line number Diff line change
Expand Up @@ -1360,29 +1360,58 @@ def visit_tuple_expr(self, e: TupleExpr) -> Type:
return TupleType(items, self.chk.named_generic_type('builtins.tuple', [fallback_item]))

def visit_dict_expr(self, e: DictExpr) -> Type:
# Translate into type checking a generic function call.
"""Type check a dict expression.

Translate it into a call to dict(), with provisions for **expr.
"""
# Collect function arguments, watching out for **expr.
args = [] # type: List[Node] # Regular "key: value"
stargs = [] # type: List[Node] # For "**expr"
for key, value in e.items:
if key is None:
stargs.append(value)
else:
args.append(TupleExpr([key, value]))
# Define type variables (used in constructors below).
ktdef = TypeVarDef('KT', -1, [], self.chk.object_type())
vtdef = TypeVarDef('VT', -2, [], self.chk.object_type())
kt = TypeVarType(ktdef)
vt = TypeVarType(vtdef)
# The callable type represents a function like this:
#
# def <unnamed>(*v: Tuple[kt, vt]) -> Dict[kt, vt]: ...
constructor = CallableType(
[TupleType([kt, vt], self.named_type('builtins.tuple'))],
[nodes.ARG_STAR],
[None],
self.chk.named_generic_type('builtins.dict', [kt, vt]),
self.named_type('builtins.function'),
name='<list>',
variables=[ktdef, vtdef])
# Synthesize function arguments.
args = [] # type: List[Node]
for key, value in e.items:
args.append(TupleExpr([key, value]))
return self.check_call(constructor,
args,
[nodes.ARG_POS] * len(args), e)[0]
# Call dict(*args), unless it's empty and stargs is not.
if args or not stargs:
# The callable type represents a function like this:
#
# def <unnamed>(*v: Tuple[kt, vt]) -> Dict[kt, vt]: ...
constructor = CallableType(
[TupleType([kt, vt], self.named_type('builtins.tuple'))],
[nodes.ARG_STAR],
[None],
self.chk.named_generic_type('builtins.dict', [kt, vt]),
self.named_type('builtins.function'),
name='<list>',
variables=[ktdef, vtdef])
rv = self.check_call(constructor, args, [nodes.ARG_POS] * len(args), e)[0]
else:
# dict(...) will be called below.
rv = None
# Call rv.update(arg) for each arg in **stargs,
# except if rv isn't set yet, then set rv = dict(arg).
if stargs:
for arg in stargs:
if rv is None:
constructor = CallableType(
[self.chk.named_generic_type('typing.Mapping', [kt, vt])],
[nodes.ARG_POS],
[None],
self.chk.named_generic_type('builtins.dict', [kt, vt]),
self.named_type('builtins.function'),
name='<list>',
variables=[ktdef, vtdef])
rv = self.check_call(constructor, [arg], [nodes.ARG_POS], arg)[0]
else:
method = self.analyze_external_member_access('update', rv, arg)
self.check_call(method, [arg], [nodes.ARG_POS], arg)
return rv

def visit_func_expr(self, e: FuncExpr) -> Type:
"""Type check lambda expression."""
Expand Down
4 changes: 3 additions & 1 deletion mypy/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1457,7 +1457,9 @@ class DictExpr(Expression):

def __init__(self, items: List[Tuple[Expression, Expression]]) -> None:
self.items = items
if all(x[0].literal == LITERAL_YES and x[1].literal == LITERAL_YES
# key is None for **item, e.g. {'a': 1, **x} has
# keys ['a', None] and values [1, x].
if all(x[0] and x[0].literal == LITERAL_YES and x[1].literal == LITERAL_YES
for x in items):
self.literal = LITERAL_YES
self.literal_hash = ('Dict',) + tuple(
Expand Down
3 changes: 2 additions & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -1937,7 +1937,8 @@ def visit_set_expr(self, expr: SetExpr) -> None:

def visit_dict_expr(self, expr: DictExpr) -> None:
for key, value in expr.items:
key.accept(self)
if key is not None:
key.accept(self)
value.accept(self)

def visit_star_expr(self, expr: StarExpr) -> None:
Expand Down
3 changes: 2 additions & 1 deletion mypy/traverser.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ def visit_tuple_expr(self, o: TupleExpr) -> None:

def visit_dict_expr(self, o: DictExpr) -> None:
for k, v in o.items:
k.accept(self)
if k is not None:
k.accept(self)
v.accept(self)

def visit_set_expr(self, o: SetExpr) -> None:
Expand Down
16 changes: 16 additions & 0 deletions test-data/unit/check-expressions.test
Original file line number Diff line number Diff line change
Expand Up @@ -1503,3 +1503,19 @@ None == None
[case testLtNone]
None < None # E: Unsupported left operand type for < (None)
[builtins fixtures/ops.py]

[case testDictWithStarExpr]
# options: fast_parser
b = {'z': 26, *a} # E: invalid syntax
[builtins fixtures/dict.py]

[case testDictWithStarStarExpr]
# options: fast_parser
from typing import Dict
a = {'a': 1}
b = {'z': 26, **a}
c = {**b}
d = {**a, **b, 'c': 3}
e = {1: 'a', **a} # E: Argument 1 to "update" of "dict" has incompatible type Dict[str, int]; expected Mapping[int, str]
f = {**b} # type: Dict[int, int] # E: List item 0 has incompatible type Dict[str, int]
[builtins fixtures/dict.py]
6 changes: 3 additions & 3 deletions test-data/unit/fixtures/dict.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Builtins stub used in dictionary-related test cases.

from typing import TypeVar, Generic, Iterable, Iterator, Tuple, overload
from typing import TypeVar, Generic, Iterable, Iterator, Mapping, Tuple, overload

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

class type: pass

class dict(Iterable[KT], Generic[KT, VT]):
class dict(Iterable[KT], Mapping[KT, VT], Generic[KT, VT]):
@overload
def __init__(self, **kwargs: VT) -> None: pass
@overload
def __init__(self, arg: Iterable[Tuple[KT, VT]], **kwargs: VT) -> None: pass
def __setitem__(self, k: KT, v: VT) -> None: pass
def __iter__(self) -> Iterator[KT]: pass
def update(self, a: 'dict[KT, VT]') -> None: pass
def update(self, a: Mapping[KT, VT]) -> None: pass

class int: pass # for convenience

Expand Down
2 changes: 2 additions & 0 deletions test-data/unit/lib-stub/typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class Sequence(Iterable[T], Generic[T]):
@abstractmethod
def __getitem__(self, n: Any) -> T: pass

class Mapping(Generic[T, U]): pass

def NewType(name: str, tp: Type[T]) -> Callable[[T], T]:
def new_type(x):
return x
Expand Down