Skip to content

Commit 364b068

Browse files
reworked is_valid_keyword_var_arg
1 parent 8cc1c29 commit 364b068

4 files changed

Lines changed: 49 additions & 10 deletions

File tree

mypy/checkexpr.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6144,9 +6144,29 @@ def is_valid_var_arg(self, typ: Type) -> bool:
61446144
def is_valid_keyword_var_arg(self, typ: Type) -> bool:
61456145
"""Is a type valid as a **kwargs argument?"""
61466146
typ = get_proper_type(typ)
6147-
if isinstance(typ, ParamSpecType | AnyType):
6147+
6148+
# factorize over unions
6149+
if isinstance(typ, UnionType):
6150+
return all(self.is_valid_keyword_var_arg(item) for item in typ.items)
6151+
6152+
if isinstance(typ, AnyType):
61486153
return True
61496154

6155+
if isinstance(typ, ParamSpecType):
6156+
return typ.flavor == ParamSpecFlavor.KWARGS
6157+
6158+
# fast path for builtins.dict
6159+
if isinstance(typ, Instance) and typ.type.fullname == "builtins.dict":
6160+
return is_subtype(typ.args[0], self.named_type("builtins.str"))
6161+
6162+
# fast fail if not SupportsKeysAndGetItem[Any, Any]
6163+
any_type = AnyType(TypeOfAny.from_omitted_generics)
6164+
if not is_subtype(
6165+
typ,
6166+
self.chk.named_generic_type("_typeshed.SupportsKeysAndGetItem", [any_type, any_type]),
6167+
):
6168+
return False
6169+
61506170
# Check if 'typ' is a SupportsKeysAndGetItem[T, Any] for some T <: str
61516171
# Note: is_subtype(typ, SupportsKeysAndGetItem[str, Any])` is too harsh
61526172
# since SupportsKeysAndGetItem is invariant in the key type parameter.
@@ -6158,11 +6178,9 @@ def is_valid_keyword_var_arg(self, typ: Type) -> bool:
61586178
id=TypeVarId(-1, namespace="<kwargs>"),
61596179
values=[],
61606180
upper_bound=self.named_type("builtins.str"),
6161-
default=AnyType(TypeOfAny.from_omitted_generics),
6162-
)
6163-
template = self.chk.named_generic_type(
6164-
"_typeshed.SupportsKeysAndGetItem", [T, AnyType(TypeOfAny.special_form)]
6181+
default=any_type,
61656182
)
6183+
template = self.chk.named_generic_type("_typeshed.SupportsKeysAndGetItem", [T, any_type])
61666184

61676185
return solve_as_subtype(typ, template) is not None
61686186

mypy/messages.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1401,7 +1401,7 @@ def invalid_var_arg(self, typ: Type, context: Context) -> None:
14011401

14021402
def invalid_keyword_var_arg(self, typ: Type, is_mapping: bool, context: Context) -> None:
14031403
typ = get_proper_type(typ)
1404-
if isinstance(typ, Instance) and is_mapping:
1404+
if is_mapping:
14051405
self.fail("Argument after ** must have string keys", context, code=codes.ARG_TYPE)
14061406
else:
14071407
self.fail(

test-data/unit/check-kwargs.test

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -572,7 +572,7 @@ main:41: error: Argument 1 to "foo" has incompatible type "**dict[str, str]"; ex
572572
[builtins fixtures/dict.pyi]
573573

574574
[case testLiteralKwargs]
575-
from typing import Literal, Mapping, Iterable
575+
from typing import Literal, Mapping, Iterable, Union
576576
def func(a: int, b: int) -> None: ...
577577

578578
class GOOD_KW:
@@ -597,4 +597,19 @@ def test(
597597
func(**bad_dict) # E: Argument after ** must have string keys
598598
func(**good_mapping)
599599
func(**bad_mapping) # E: Argument after ** must have string keys
600+
601+
def test_union(
602+
good_kw: Union[GOOD_KW, dict[str, int]],
603+
bad_kw: Union[BAD_KW, dict[str, int]],
604+
good_dict: Union[dict[Literal["a", "b"], int], dict[str, int]],
605+
bad_dict: Union[dict[Literal["one", 1], int], dict[str, int]],
606+
good_mapping: Union[Mapping[Literal["a", "b"], int], dict[str, int]],
607+
bad_mapping: Union[Mapping[Literal["one", 1], int], dict[str, int]],
608+
) -> None:
609+
func(**good_kw)
610+
func(**bad_kw) # E: Argument after ** must have string keys
611+
func(**good_dict)
612+
func(**bad_dict) # E: Argument after ** must have string keys
613+
func(**good_mapping)
614+
func(**bad_mapping) # E: Argument after ** must have string keys
600615
[builtins fixtures/dict.pyi]

test-data/unit/check-parameter-specification.test

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -461,14 +461,16 @@ class C(Generic[P, P2]):
461461
self.m1(*args, **kwargs)
462462
self.m2(*args, **kwargs) # E: Argument 1 to "m2" of "C" has incompatible type "*P.args"; expected "P2.args" \
463463
# E: Argument 2 to "m2" of "C" has incompatible type "**P.kwargs"; expected "P2.kwargs"
464-
self.m1(*kwargs, **args) # E: Argument 1 to "m1" of "C" has incompatible type "*P.kwargs"; expected "P.args" \
465-
# E: Argument 2 to "m1" of "C" has incompatible type "**P.args"; expected "P.kwargs"
464+
self.m1(*kwargs, **args) # E: Argument after ** must be a mapping, not "P.args" \
465+
# E: Argument 1 to "m1" of "C" has incompatible type "*P.kwargs"; expected "P.args" \
466+
# E: Argument 2 to "m1" of "C" has incompatible type "**P.args"; expected "P.kwargs"
466467
self.m3(*args, **kwargs) # E: Argument 1 to "m3" of "C" has incompatible type "*P.args"; expected "int" \
467468
# E: Argument 2 to "m3" of "C" has incompatible type "**P.kwargs"; expected "int"
468469
self.m4(*args, **kwargs) # E: Argument 1 to "m4" of "C" has incompatible type "*P.args"; expected "int" \
469470
# E: Argument 2 to "m4" of "C" has incompatible type "**P.kwargs"; expected "int"
470471

471-
self.m1(*args, **args) # E: Argument 2 to "m1" of "C" has incompatible type "**P.args"; expected "P.kwargs"
472+
self.m1(*args, **args) # E: Argument after ** must be a mapping, not "P.args" \
473+
# E: Argument 2 to "m1" of "C" has incompatible type "**P.args"; expected "P.kwargs"
472474
self.m1(*kwargs, **kwargs) # E: Argument 1 to "m1" of "C" has incompatible type "*P.kwargs"; expected "P.args"
473475

474476
def m2(self, *args: P2.args, **kwargs: P2.kwargs) -> None:
@@ -479,6 +481,10 @@ class C(Generic[P, P2]):
479481

480482
def m4(self, x: int) -> None:
481483
pass
484+
485+
486+
487+
482488
[builtins fixtures/dict.pyi]
483489

484490
[case testParamSpecOverUnannotatedDecorator]

0 commit comments

Comments
 (0)