From eb207cd01e43be06edb1bcae2c9f9faccfb340b0 Mon Sep 17 00:00:00 2001 From: Josh Hong Date: Tue, 31 Mar 2026 23:10:58 -0400 Subject: [PATCH 1/2] Improve error message for incompatible **kwargs argument --- mypy/messages.py | 5 ++++ test-data/unit/check-columns.test | 3 +- test-data/unit/check-expressions.test | 3 +- test-data/unit/check-functions.test | 3 +- test-data/unit/check-functools.test | 8 +++-- test-data/unit/check-kwargs.test | 27 +++++++++++++---- .../unit/check-parameter-specification.test | 17 +++++++++-- test-data/unit/check-tuples.test | 29 +++++++++++++------ test-data/unit/check-varargs.test | 3 +- 9 files changed, 74 insertions(+), 24 deletions(-) diff --git a/mypy/messages.py b/mypy/messages.py index 51bb0b7ee9be6..7869f863dc51a 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -803,6 +803,11 @@ def incompatible_argument( if isinstance(arg_type, Instance) and isinstance(type, Instance): notes = append_invariance_notes(notes, arg_type, type) notes = append_numbers_notes(notes, arg_type, type) + if arg_kind == ARG_STAR2 and isinstance(arg_type, Instance): + if arg_type.type.fullname == "builtins.dict" and len(arg_type.args) >= 2: + notes.append( + 'Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict' + ) object_type = get_proper_type(object_type) if isinstance(object_type, TypedDictType): code = codes.TYPEDDICT_ITEM diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index dd8389c195583..c3b6782f73ab2 100644 --- a/test-data/unit/check-columns.test +++ b/test-data/unit/check-columns.test @@ -71,7 +71,8 @@ def g(**x: int) -> None: pass a = [''] f(*a) # E:4: Argument 1 to "f" has incompatible type "*list[str]"; expected "int" b = {'x': 'y'} -g(**b) # E:5: Argument 1 to "g" has incompatible type "**dict[str, str]"; expected "int" +g(**b) # E:5: Argument 1 to "g" has incompatible type "**dict[str, str]"; expected "int" \ + # N:5: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict [builtins fixtures/dict.pyi] [case testColumnsMultipleStatementsPerLine] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 17ce4cd596590..5ce37b7edb681 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -1770,7 +1770,8 @@ kw2 = {'x': ''} d2 = dict(it, **kw2) d2() # E: "dict[str, object]" not callable -d3 = dict(it, **kw2) # type: Dict[str, int] # E: Argument 2 to "dict" has incompatible type "**dict[str, str]"; expected "int" +d3 = dict(it, **kw2) # type: Dict[str, int] # E: Argument 2 to "dict" has incompatible type "**dict[str, str]"; expected "int" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict [builtins fixtures/dict.pyi] [case testDictFromIterableAndStarStarArgs2] diff --git a/test-data/unit/check-functions.test b/test-data/unit/check-functions.test index 80a40d3ef1795..fdd8aead57c83 100644 --- a/test-data/unit/check-functions.test +++ b/test-data/unit/check-functions.test @@ -3749,7 +3749,8 @@ def foo(x: P, y: P) -> None: ... args: list[object] foo(*args) # E: Argument 1 to "foo" has incompatible type "*list[object]"; expected "P" kwargs: dict[str, object] -foo(**kwargs) # E: Argument 1 to "foo" has incompatible type "**dict[str, object]"; expected "P" +foo(**kwargs) # E: Argument 1 to "foo" has incompatible type "**dict[str, object]"; expected "P" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict [builtins fixtures/dict.pyi] [case testNoImplicitReturnErrorOnDeferral_no_empty] diff --git a/test-data/unit/check-functools.test b/test-data/unit/check-functools.test index 77070d61a013c..14a2b0f4c1a19 100644 --- a/test-data/unit/check-functools.test +++ b/test-data/unit/check-functools.test @@ -213,8 +213,10 @@ functools.partial(foo, 1, "a", "b", "c", d="a") # E: Argument 3 to "foo" has in def bar(*a: bytes, **k: int): p1("a", 2, 3, 4, d="a", **k) p1("a", d="a", **k) - p1("a", **k) # E: Argument 2 to "foo" has incompatible type "**dict[str, int]"; expected "str" - p1(**k) # E: Argument 1 to "foo" has incompatible type "**dict[str, int]"; expected "str" + p1("a", **k) # E: Argument 2 to "foo" has incompatible type "**dict[str, int]"; expected "str" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict + p1(**k) # E: Argument 1 to "foo" has incompatible type "**dict[str, int]"; expected "str" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict p1(*a) # E: Expected iterable as variadic argument @@ -225,6 +227,8 @@ def test_baz(xs: List[int]): p3(1) # E: Too many arguments for "baz" + + [builtins fixtures/dict.pyi] [case testFunctoolsPartialGeneric] diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index c8653143bd9dd..6fd485c3e6c75 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -301,9 +301,20 @@ d: Dict[str, A] f(**d) f(x=A(), **d) d2: Dict[str, B] -f(**d2) # E: Argument 1 to "f" has incompatible type "**dict[str, B]"; expected "A" -f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**dict[str, B]"; expected "A" -f(**{'x': B()}) # E: Argument 1 to "f" has incompatible type "**dict[str, B]"; expected "A" +f(**d2) # E: Argument 1 to "f" has incompatible type "**dict[str, B]"; expected "A" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict +f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**dict[str, B]"; expected "A" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict +f(**{'x': B()}) # E: Argument 1 to "f" has incompatible type "**dict[str, B]"; expected "A" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict +[builtins fixtures/dict.pyi] + +[case testIncompatibleKwargsNote] +from typing import Any +def f(x: int, y: str) -> None: pass +d: dict[str, int] = {} +f(**d) # E: Argument 1 to "f" has incompatible type "**dict[str, int]"; expected "str" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict [builtins fixtures/dict.pyi] [case testKwargsAllowedInDunderCall] @@ -372,7 +383,8 @@ def f(a: 'A', b: 'B') -> None: pass d: Dict[str, Any] f(**d) d2: Dict[str, A] -f(**d2) # E: Argument 1 to "f" has incompatible type "**dict[str, A]"; expected "B" +f(**d2) # E: Argument 1 to "f" has incompatible type "**dict[str, A]"; expected "B" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict class A: pass class B: pass [builtins fixtures/dict.pyi] @@ -449,7 +461,8 @@ f(**a) # okay b = {'': ''} f(b) # E: Argument 1 to "f" has incompatible type "dict[str, str]"; expected "int" -f(**b) # E: Argument 1 to "f" has incompatible type "**dict[str, str]"; expected "int" +f(**b) # E: Argument 1 to "f" has incompatible type "**dict[str, str]"; expected "int" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict c = {0: 0} f(**c) # E: Argument after ** must have string keys @@ -509,7 +522,8 @@ def g(arg: int = 0, **kwargs: object) -> None: d = {} # type: Dict[str, object] f(**d) -g(**d) # E: Argument 1 to "g" has incompatible type "**dict[str, object]"; expected "int" +g(**d) # E: Argument 1 to "g" has incompatible type "**dict[str, object]"; expected "int" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict m = {} # type: Mapping[str, object] f(**m) @@ -584,6 +598,7 @@ main:37: error: Argument 1 to "foo" has incompatible type "**B[str, str]"; expec main:38: error: Argument after ** must be a mapping, not "C[str, float]" main:39: error: Argument after ** must be a mapping, not "D" main:41: error: Argument 1 to "foo" has incompatible type "**dict[str, str]"; expected "float" +main:41: note: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict [builtins fixtures/dict.pyi] [case testLiteralKwargs] diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index b0808105a3858..5b7c5040b1bf7 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1276,7 +1276,8 @@ def c3(f: Callable[P, int], *args, **kwargs) -> int: ... def c4(f: Callable[P, int], *args: int, **kwargs: str) -> int: # but not ok to call: f(*args, **kwargs) # E: Argument 1 has incompatible type "*tuple[int, ...]"; expected "P.args" \ - # E: Argument 2 has incompatible type "**dict[str, str]"; expected "P.kwargs" + # E: Argument 2 has incompatible type "**dict[str, str]"; expected "P.kwargs" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict return 1 def f1(f: Callable[P, int], *args, **kwargs: P.kwargs) -> int: ... # E: ParamSpec must have "*args" typed as "P.args" and "**kwargs" typed as "P.kwargs" @@ -1289,6 +1290,7 @@ def f5(f: Callable[P, int], *args: P.args, extra_keyword_arg: int, **kwargs: P.k P1 = ParamSpec('P1') def m1(f: Callable[P1, int], *a, **k: P1.kwargs) -> int: ... # E: ParamSpec must have "*args" typed as "P1.args" and "**kwargs" typed as "P1.kwargs" + [builtins fixtures/paramspec.pyi] @@ -1306,7 +1308,8 @@ def c3(f: Callable[Concatenate[int, P], int], *args, **kwargs) -> int: ... def c4(f: Callable[Concatenate[int, P], int], *args: int, **kwargs: str) -> int: # but not ok to call: f(1, *args, **kwargs) # E: Argument 2 has incompatible type "*tuple[int, ...]"; expected "P.args" \ - # E: Argument 3 has incompatible type "**dict[str, str]"; expected "P.kwargs" + # E: Argument 3 has incompatible type "**dict[str, str]"; expected "P.kwargs" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict return 1 def f1(f: Callable[Concatenate[int, P], int], *args, **kwargs: P.kwargs) -> int: ... # E: ParamSpec must have "*args" typed as "P.args" and "**kwargs" typed as "P.kwargs" @@ -1314,6 +1317,7 @@ def f2(f: Callable[Concatenate[int, P], int], *args: P.args, **kwargs) -> int: . def f3(f: Callable[Concatenate[int, P], int], *args: P.args) -> int: ... # E: ParamSpec must have "*args" typed as "P.args" and "**kwargs" typed as "P.kwargs" def f4(f: Callable[Concatenate[int, P], int], **kwargs: P.kwargs) -> int: ... # E: ParamSpec must have "*args" typed as "P.args" and "**kwargs" typed as "P.kwargs" def f5(f: Callable[Concatenate[int, P], int], *args: P.args, extra_keyword_arg: int, **kwargs: P.kwargs) -> int: ... # E: Arguments not allowed after ParamSpec.args + [builtins fixtures/paramspec.pyi] @@ -2417,7 +2421,8 @@ def run3(func: Callable[Concatenate[int, P], T], *args: P.args, **kwargs: P.kwar func2 = partial(func, 1, *args) d = {"":""} func2(**d) # E: Too few arguments \ - # E: Argument 1 has incompatible type "**dict[str, str]"; expected "P.kwargs" + # E: Argument 1 has incompatible type "**dict[str, str]"; expected "P.kwargs" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict return func2(**kwargs) def run4(func: Callable[Concatenate[int, P], T], *args: P.args, **kwargs: P.kwargs) -> T: @@ -2454,6 +2459,12 @@ def run_bad4(func: Callable[Concatenate[int, P], T], *args: P.args, **kwargs: P. # E: Argument 1 has incompatible type "int"; expected "P.args" return func2(**kwargs) # E: Too few arguments + + + + + + [builtins fixtures/paramspec.pyi] [case testOtherVarArgs] diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 9653d9d037ce6..53d814ad3f7ba 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1593,13 +1593,13 @@ from typing import Tuple t: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", 11) # E: Incompatible types in assignment (3 tuple items are incompatible) \ # N: Expression tuple item 8 has type "str"; "int" expected; \ # N: Expression tuple item 9 has type "str"; "int" expected; \ - # N: Expression tuple item 10 has type "str"; "int" expected; + # N: Expression tuple item 10 has type "str"; "int" expected; # long initializer assignment with more mismatches t1: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \ # N: Expression tuple item 8 has type "str"; "int" expected; \ # N: Expression tuple item 9 has type "str"; "int" expected; \ - # N: Expression tuple item 10 has type "str"; "int" expected; + # N: Expression tuple item 10 has type "str"; "int" expected; # short tuple initializer assignment t2: Tuple[int, ...] = (1, 2, "s", 4) # E: Incompatible types in assignment (expression has type "tuple[int, int, str, int]", variable has type "tuple[int, ...]") @@ -1607,13 +1607,13 @@ t2: Tuple[int, ...] = (1, 2, "s", 4) # E: Incompatible types in assignment (expr # long initializer assignment with few mismatches, no ellipsis t3: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "str", "str") # E: Incompatible types in assignment (2 tuple items are incompatible) \ # N: Expression tuple item 10 has type "str"; "int" expected; \ - # N: Expression tuple item 11 has type "str"; "int" expected; + # N: Expression tuple item 11 has type "str"; "int" expected; # long initializer assignment with more mismatches, no ellipsis t4: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \ # N: Expression tuple item 8 has type "str"; "int" expected; \ # N: Expression tuple item 9 has type "str"; "int" expected; \ - # N: Expression tuple item 10 has type "str"; "int" expected; + # N: Expression tuple item 10 has type "str"; "int" expected; # short tuple initializer assignment, no ellipsis t5: Tuple[int, int] = (1, 2, "s", 4) # E: Incompatible types in assignment (expression has type "tuple[int, int, str, int]", variable has type "tuple[int, int]") @@ -1621,6 +1621,17 @@ t5: Tuple[int, int] = (1, 2, "s", 4) # E: Incompatible types in assignment (exp # long initializer assignment with mismatched pairs t6: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str", 1, 1, 1, 1, 1) # E: Incompatible types in assignment (expression has type "tuple[int, int, ... <15 more items>]", variable has type "tuple[int, int, ... <10 more items>]") + + + + + + + + + + + [builtins fixtures/tuple.pyi] [case testPropertyLongTupleReturnTypeMismatchUnion] @@ -1657,9 +1668,9 @@ class A: ) [out] main:18: error: Incompatible return value type (6 tuple items are incompatible; 3 items are omitted) -main:18: note: Expression tuple item 6 has type "str | int"; "str" expected; -main:18: note: Expression tuple item 7 has type "str | float"; "str" expected; -main:18: note: Expression tuple item 8 has type "str | None"; "str" expected; +main:18: note: Expression tuple item 6 has type "str | int"; "str" expected; +main:18: note: Expression tuple item 7 has type "str | float"; "str" expected; +main:18: note: Expression tuple item 8 has type "str | None"; "str" expected; [builtins fixtures/property.pyi] [case testPropertyLongTupleReturnTypeMismatchUnionWiderExpected] @@ -1696,8 +1707,8 @@ class A: ) [out] main:18: error: Incompatible return value type (2 tuple items are incompatible) -main:18: note: Expression tuple item 2 has type "str"; "int" expected; -main:18: note: Expression tuple item 11 has type "float | int"; "str" expected; +main:18: note: Expression tuple item 2 has type "str"; "int" expected; +main:18: note: Expression tuple item 11 has type "float | int"; "str" expected; [builtins fixtures/property.pyi] [case testTupleWithStarExpr] diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 172e57cf1a4b3..236a350735484 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -830,7 +830,8 @@ Weird = TypedDict("Weird", {"@": int}) def foo(**kwargs: Unpack[Weird]) -> None: reveal_type(kwargs["@"]) # N: Revealed type is "builtins.int" foo(**{"@": 42}) -foo(**{"no": "way"}) # E: Argument 1 to "foo" has incompatible type "**dict[str, str]"; expected "int" +foo(**{"no": "way"}) # E: Argument 1 to "foo" has incompatible type "**dict[str, str]"; expected "int" \ + # N: Consider annotating the ** argument as "**kwargs: Any" or using a TypedDict [builtins fixtures/dict.pyi] [typing fixtures/typing-typeddict.pyi] From 62fc87b976f3a53168c89574fabb188aced563b1 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 1 Apr 2026 03:19:11 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- test-data/unit/check-tuples.test | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test index 53d814ad3f7ba..955661832b276 100644 --- a/test-data/unit/check-tuples.test +++ b/test-data/unit/check-tuples.test @@ -1593,13 +1593,13 @@ from typing import Tuple t: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", 11) # E: Incompatible types in assignment (3 tuple items are incompatible) \ # N: Expression tuple item 8 has type "str"; "int" expected; \ # N: Expression tuple item 9 has type "str"; "int" expected; \ - # N: Expression tuple item 10 has type "str"; "int" expected; + # N: Expression tuple item 10 has type "str"; "int" expected; # long initializer assignment with more mismatches t1: Tuple[int, ...] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \ # N: Expression tuple item 8 has type "str"; "int" expected; \ # N: Expression tuple item 9 has type "str"; "int" expected; \ - # N: Expression tuple item 10 has type "str"; "int" expected; + # N: Expression tuple item 10 has type "str"; "int" expected; # short tuple initializer assignment t2: Tuple[int, ...] = (1, 2, "s", 4) # E: Incompatible types in assignment (expression has type "tuple[int, int, str, int]", variable has type "tuple[int, ...]") @@ -1607,13 +1607,13 @@ t2: Tuple[int, ...] = (1, 2, "s", 4) # E: Incompatible types in assignment (expr # long initializer assignment with few mismatches, no ellipsis t3: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, 9, 10, "str", "str") # E: Incompatible types in assignment (2 tuple items are incompatible) \ # N: Expression tuple item 10 has type "str"; "int" expected; \ - # N: Expression tuple item 11 has type "str"; "int" expected; + # N: Expression tuple item 11 has type "str"; "int" expected; # long initializer assignment with more mismatches, no ellipsis t4: Tuple[int, int, int, int, int, int, int, int, int, int, int, int] = (1, 2, 3, 4, 5, 6, 7, 8, "str", "str", "str", "str") # E: Incompatible types in assignment (4 tuple items are incompatible; 1 items are omitted) \ # N: Expression tuple item 8 has type "str"; "int" expected; \ # N: Expression tuple item 9 has type "str"; "int" expected; \ - # N: Expression tuple item 10 has type "str"; "int" expected; + # N: Expression tuple item 10 has type "str"; "int" expected; # short tuple initializer assignment, no ellipsis t5: Tuple[int, int] = (1, 2, "s", 4) # E: Incompatible types in assignment (expression has type "tuple[int, int, str, int]", variable has type "tuple[int, int]") @@ -1668,9 +1668,9 @@ class A: ) [out] main:18: error: Incompatible return value type (6 tuple items are incompatible; 3 items are omitted) -main:18: note: Expression tuple item 6 has type "str | int"; "str" expected; -main:18: note: Expression tuple item 7 has type "str | float"; "str" expected; -main:18: note: Expression tuple item 8 has type "str | None"; "str" expected; +main:18: note: Expression tuple item 6 has type "str | int"; "str" expected; +main:18: note: Expression tuple item 7 has type "str | float"; "str" expected; +main:18: note: Expression tuple item 8 has type "str | None"; "str" expected; [builtins fixtures/property.pyi] [case testPropertyLongTupleReturnTypeMismatchUnionWiderExpected] @@ -1707,8 +1707,8 @@ class A: ) [out] main:18: error: Incompatible return value type (2 tuple items are incompatible) -main:18: note: Expression tuple item 2 has type "str"; "int" expected; -main:18: note: Expression tuple item 11 has type "float | int"; "str" expected; +main:18: note: Expression tuple item 2 has type "str"; "int" expected; +main:18: note: Expression tuple item 11 has type "float | int"; "str" expected; [builtins fixtures/property.pyi] [case testTupleWithStarExpr]