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

Allow any subtype of typing.Mapping[str, Any] to be passed in as kwargs. #3604

Merged
merged 4 commits into from Jun 27, 2017
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
11 changes: 6 additions & 5 deletions mypy/checkexpr.py
Expand Up @@ -957,7 +957,8 @@ def check_argument_types(self, arg_types: List[Type], arg_kinds: List[int],
messages.invalid_var_arg(arg_type, context)
if (arg_kinds[actual] == nodes.ARG_STAR2 and
not self.is_valid_keyword_var_arg(arg_type)):
messages.invalid_keyword_var_arg(arg_type, context)
is_mapping = is_subtype(arg_type, self.chk.named_type('typing.Mapping'))
messages.invalid_keyword_var_arg(arg_type, is_mapping, context)
# Get the type of an individual actual argument (for *args
# and **args this is the item type, not the collection type).
if (isinstance(arg_type, TupleType)
Expand Down Expand Up @@ -2260,17 +2261,17 @@ def is_valid_keyword_var_arg(self, typ: Type) -> bool:
"""Is a type valid as a **kwargs argument?"""
if self.chk.options.python_version[0] >= 3:
return is_subtype(typ, self.chk.named_generic_type(
'builtins.dict', [self.named_type('builtins.str'),
AnyType()]))
'typing.Mapping', [self.named_type('builtins.str'),
AnyType()]))
else:
return (
is_subtype(typ, self.chk.named_generic_type(
'builtins.dict',
'typing.Mapping',
[self.named_type('builtins.str'),
AnyType()]))
or
is_subtype(typ, self.chk.named_generic_type(
'builtins.dict',
'typing.Mapping',
[self.named_type('builtins.unicode'),
AnyType()])))

Expand Down
12 changes: 8 additions & 4 deletions mypy/messages.py
Expand Up @@ -740,12 +740,16 @@ def could_not_infer_type_arguments(self, callee_type: CallableType, n: int,
def invalid_var_arg(self, typ: Type, context: Context) -> None:
self.fail('List or tuple expected as variable arguments', context)

def invalid_keyword_var_arg(self, typ: Type, context: Context) -> None:
if isinstance(typ, Instance) and (typ.type.fullname() == 'builtins.dict'):
def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) -> None:
if isinstance(typ, Instance) and is_mapping:
self.fail('Keywords must be strings', context)
else:
self.fail('Argument after ** must be a dictionary',
context)
suffix = ''
if isinstance(typ, Instance):
suffix = ', not {}'.format(self.format(typ))
self.fail(
'Argument after ** must be a mapping{}'.format(suffix),
context)

def undefined_in_superclass(self, member: str, context: Context) -> None:
self.fail('"{}" undefined in superclass'.format(member), context)
Expand Down
25 changes: 23 additions & 2 deletions test-data/unit/check-kwargs.test
Expand Up @@ -280,12 +280,33 @@ reveal_type(formatter.__call__) # E: Revealed type is 'def (message: builtins.s
[builtins fixtures/bool.pyi]
[out]

[case testPassingMappingForKeywordVarArg]
from typing import Mapping
def f(**kwargs: 'A') -> None: pass
b = None # type: Mapping
d = None # type: Mapping[A, A]
m = None # type: Mapping[str, A]
f(**d) # E: Keywords must be strings
f(**m)
f(**b)
class A: pass
[builtins fixtures/dict.pyi]

[case testPassingMappingSubclassForKeywordVarArg]
from typing import Mapping
class MappingSubclass(Mapping[str, str]): pass
def f(**kwargs: 'A') -> None: pass
d = None # type: MappingSubclass
f(**d)
class A: pass
[builtins fixtures/dict.pyi]

[case testInvalidTypeForKeywordVarArg]
from typing import Dict
def f( **kwargs: 'A') -> None: pass
def f(**kwargs: 'A') -> None: pass
d = None # type: Dict[A, A]
f(**d) # E: Keywords must be strings
f(**A()) # E: Argument after ** must be a dictionary
f(**A()) # E: Argument after ** must be a mapping, not "A"
class A: pass
[builtins fixtures/dict.pyi]

Expand Down
5 changes: 3 additions & 2 deletions test-data/unit/fixtures/args.pyi
@@ -1,6 +1,6 @@
# Builtins stub used to support *args, **kwargs.

from typing import TypeVar, Generic, Iterable, Tuple, Dict, Any, overload
from typing import TypeVar, Generic, Iterable, Tuple, Dict, Any, overload, Mapping

Tco = TypeVar('Tco', covariant=True)
T = TypeVar('T')
Expand All @@ -19,7 +19,8 @@ class type:
def __call__(self, *args: Any, **kwargs: Any) -> Any: pass

class tuple(Iterable[Tco], Generic[Tco]): pass
class dict(Generic[T, S]): pass

class dict(Iterable[T], Mapping[T, S], Generic[T, S]): pass

class int:
def __eq__(self, o: object) -> bool: pass
Expand Down