diff --git a/mypy/messages.py b/mypy/messages.py index 199b7c42b11ba..3291d7292e9b4 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -817,6 +817,8 @@ def incompatible_argument( quote_type_string(arg_type_str), quote_type_string(expected_type_str), ) + if isinstance(arg_type, Instance): + notes = append_kwargs_notes(notes, arg_type, arg_kind) expected_type = get_proper_type(expected_type) if isinstance(expected_type, UnionType): expected_types = list(expected_type.items) @@ -3099,6 +3101,13 @@ def pretty_seq(args: Sequence[str], conjunction: str) -> str: return ", ".join(quoted[:-1]) + last_sep + quoted[-1] +def append_kwargs_notes(notes: list[str], arg_type: Instance, arg_kind: ArgKind) -> list[str]: + """Explain that annotating keyword arguments may resolve incompatible type issues.""" + if arg_kind == ARG_STAR2 and arg_type.type.fullname == "builtins.dict": + notes.append('Consider using a TypedDict type or "Dict[str, any]" for the ** argument') + return notes + + def append_invariance_notes( notes: list[str], arg_type: Instance, expected_type: Instance ) -> list[str]: diff --git a/test-data/unit/check-columns.test b/test-data/unit/check-columns.test index 44524b9df9435..1a386205154a1 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 using a TypedDict type or "Dict[str, any]" for the ** argument [builtins fixtures/dict.pyi] [case testColumnsMultipleStatementsPerLine] diff --git a/test-data/unit/check-expressions.test b/test-data/unit/check-expressions.test index 04b3f7a131cc9..8dda33da3efa4 100644 --- a/test-data/unit/check-expressions.test +++ b/test-data/unit/check-expressions.test @@ -1712,7 +1712,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 using a TypedDict type or "Dict[str, any]" for the ** argument [builtins fixtures/dict.pyi] [case testDictFromIterableAndStarStarArgs2] diff --git a/test-data/unit/check-kwargs.test b/test-data/unit/check-kwargs.test index 4beac047e278b..f4154df2c42bb 100644 --- a/test-data/unit/check-kwargs.test +++ b/test-data/unit/check-kwargs.test @@ -75,6 +75,32 @@ def f(a: int): pass def g(): f(0, a=1) [out] +[case testIncompatibleKeywordArgumentsClass] +from typing import Dict + +class A: + def __init__(self, a: str, b: str, c: str, d: int = 2) -> None: + self.a: str = a + self.b: str = b + self.c: str = c + self.d: int = d + +argument_a: str = "argument_a" +arguments_b_c: Dict[str, str] = {"b": "argument_b", "c": "argument_c"} +object_a: A = A(a=argument_a, **arguments_b_c) # E: Argument 2 to "A" has incompatible type "**Dict[str, str]"; expected "int" \ + # N: Consider using a TypedDict type or "Dict[str, any]" for the ** argument +[builtins fixtures/dict.pyi] + +[case testIncompatibleKeywordArgumentsJSON] +import json +json_kwargs = dict(indent=2) +json.dumps({}, **json_kwargs) # E: Argument 2 to "dumps" has incompatible type "**Dict[str, int]"; expected "bool" \ + # N: Consider using a TypedDict type or "Dict[str, any]" for the ** argument \ + # E: Argument 2 to "dumps" has incompatible type "**Dict[str, int]"; expected "Optional[Type[JSONEncoder]]" \ + # E: Argument 2 to "dumps" has incompatible type "**Dict[str, int]"; expected "Optional[Tuple[str, str]]" \ + # E: Argument 2 to "dumps" has incompatible type "**Dict[str, int]"; expected "Optional[Callable[[Any], Any]]" +[builtins fixtures/dict.pyi] + [case testInvalidKeywordArgument] import typing def f(a: 'A') -> None: pass # N: "f" defined here @@ -301,9 +327,12 @@ 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 using a TypedDict type or "Dict[str, any]" for the ** argument +f(x=A(), **d2) # E: Argument 2 to "f" has incompatible type "**Dict[str, B]"; expected "A" \ + # N: Consider using a TypedDict type or "Dict[str, any]" for the ** argument +f(**{'x': B()}) # E: Argument 1 to "f" has incompatible type "**Dict[str, B]"; expected "A" \ + # N: Consider using a TypedDict type or "Dict[str, any]" for the ** argument [builtins fixtures/dict.pyi] [case testKwargsAllowedInDunderCall] @@ -366,7 +395,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 using a TypedDict type or "Dict[str, any]" for the ** argument class A: pass class B: pass [builtins fixtures/dict.pyi] @@ -443,7 +473,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 using a TypedDict type or "Dict[str, any]" for the ** argument c = {0: 0} f(**c) # E: Keywords must be strings @@ -488,7 +519,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 using a TypedDict type or "Dict[str, any]" for the ** argument m = {} # type: Mapping[str, object] f(**m) @@ -563,4 +595,5 @@ 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 using a TypedDict type or "Dict[str, any]" for the ** argument [builtins fixtures/dict.pyi] diff --git a/test-data/unit/check-parameter-specification.test b/test-data/unit/check-parameter-specification.test index 8fd9abcb97526..168130a05e67a 100644 --- a/test-data/unit/check-parameter-specification.test +++ b/test-data/unit/check-parameter-specification.test @@ -1257,7 +1257,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 using a TypedDict type or "Dict[str, any]" for the ** argument 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" @@ -1287,7 +1288,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 using a TypedDict type or "Dict[str, any]" for the ** argument 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" diff --git a/test-data/unit/check-varargs.test b/test-data/unit/check-varargs.test index 2495a883aa715..434fbf450dfde 100644 --- a/test-data/unit/check-varargs.test +++ b/test-data/unit/check-varargs.test @@ -804,7 +804,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 using a TypedDict type or "Dict[str, any]" for the ** argument [builtins fixtures/dict.pyi] [case testUnpackKwargsEmpty]