diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 877b53769e7d5..55d2cfd7db6e6 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -1694,6 +1694,18 @@ def visit_dict_expr(self, e: DictExpr) -> Type: Translate it into a call to dict(), with provisions for **expr. """ + # if the dict literal doesn't match TypedDict, check_typeddict_call_with_dict reports + # an error, but returns the TypedDict type that matches the literal it found + # that would cause a second error when that TypedDict type is returned upstream + # to avoid the second error, we always return TypedDict type that was requested + if isinstance(self.type_context[-1], TypedDictType): + self.check_typeddict_call_with_dict( + callee=self.type_context[-1], + kwargs=e, + context=e + ) + return self.type_context[-1].copy_modified() + # Collect function arguments, watching out for **expr. args = [] # type: List[Expression] # Regular "key: value" stargs = [] # type: List[Expression] # For "**expr" diff --git a/test-data/unit/README.md b/test-data/unit/README.md index 61ccd16cb0f0f..9dd87a91f5976 100644 --- a/test-data/unit/README.md +++ b/test-data/unit/README.md @@ -29,7 +29,10 @@ Add the test in this format anywhere in the file: - `# E: abc...` indicates that this line should result in type check error with text "abc..." - note a space after `E:` and `flags:` -- lines without `# E: ` should cause no type check errors +- `# E:12` adds column number to the expected error +- repeating `# E: ` several times in one line indicates multiple expected errors in one line +- `W: ...` and `N: ...` works exactly like `E:`, but report a warning and a note respectively +- lines that don't contain the above should cause no type check errors - optional `[builtins fixtures/...]` tells the type checker to use stubs from the indicated file (see Fixtures section below) - optional `[out]` is an alternative to the "# E:" notation: it indicates that diff --git a/test-data/unit/check-typeddict.test b/test-data/unit/check-typeddict.test index 78fade8c8258a..269ef4de85933 100644 --- a/test-data/unit/check-typeddict.test +++ b/test-data/unit/check-typeddict.test @@ -610,3 +610,42 @@ def f() -> None: A = TypedDict('A', {'x': int}) A # E: Name 'A' is not defined [builtins fixtures/dict.pyi] + + +-- Use dict literals + +[case testTypedDictDictLiterals] +from mypy_extensions import TypedDict + +Point = TypedDict('Point', {'x': int, 'y': int}) + +def f(p: Point) -> None: + p = {'x': 2, 'y': 3} + p = {'x': 2} # E: Expected items ['x', 'y'] but found ['x']. + p = dict(x=2, y=3) + +f({'x': 1, 'y': 3}) +f({'x': 1, 'y': 'z'}) # E: Incompatible types (expression has type "str", TypedDict item "y" has type "int") + +f(dict(x=1, y=3)) +f(dict(x=1, y=3, z=4)) # E: Expected items ['x', 'y'] but found ['x', 'y', 'z']. + +[builtins fixtures/dict.pyi] + + +[case testTypedDictExplicitTypes] +from mypy_extensions import TypedDict + +Point = TypedDict('Point', {'x': int, 'y': int}) + +p1: Point = {'x': 'hi'} # E: Expected items ['x', 'y'] but found ['x']. + +p2: Point +p2 = dict(x='bye') # E: Expected items ['x', 'y'] but found ['x']. + +p3 = Point(x=1, y=2) +p3 = {'x': 'hi'} # E: Expected items ['x', 'y'] but found ['x']. + +p4: Point = {'x': 1, 'y': 2} + +[builtins fixtures/dict.pyi]