From dc3c31e2c50f8dd2a9e98b5a4b6779a41637ef38 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Aug 2025 08:22:19 -0700 Subject: [PATCH 1/5] spec, conformance: add PEP 728 I mostly copied the spec from PEP 728, with light editing to remove references to "this PEP" and fix links. In a followup I plan to reorganize the TypedDict spec. The conformance tests primarily derive from the examples in the PEP. --- .../results/mypy/typeddicts_extra_items.toml | 143 +++++ .../results/pyre/typeddicts_extra_items.toml | 139 +++++ .../pyright/typeddicts_extra_items.toml | 65 ++ conformance/tests/typeddicts_extra_items.py | 347 +++++++++++ docs/spec/typeddict.rst | 554 ++++++++++++++++++ 5 files changed, 1248 insertions(+) create mode 100644 conformance/results/mypy/typeddicts_extra_items.toml create mode 100644 conformance/results/pyre/typeddicts_extra_items.toml create mode 100644 conformance/results/pyright/typeddicts_extra_items.toml create mode 100644 conformance/tests/typeddicts_extra_items.py diff --git a/conformance/results/mypy/typeddicts_extra_items.toml b/conformance/results/mypy/typeddicts_extra_items.toml new file mode 100644 index 00000000..ef43d71c --- /dev/null +++ b/conformance/results/mypy/typeddicts_extra_items.toml @@ -0,0 +1,143 @@ +conformant = "Fail" +notes = """ +Not supported. +""" +conformance_automated = "Fail" +errors_diff = """ +Line 181: Expected 1 errors +Line 184: Expected 1 errors +Line 206: Expected 1 errors +Line 213: Expected 1 errors +Line 233: Expected 1 errors +Line 247: Expected 1 errors +Line 248: Expected 1 errors +Line 259: Expected 1 errors +Lines 91, 92: Expected error (tag 'MovieC') +Lines 94, 95: Expected error (tag 'MovieD') +Line 11: Unexpected errors ['typeddicts_extra_items.py:11: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 14: Unexpected errors ['typeddicts_extra_items.py:14: error: Extra key "novel_adaptation" for TypedDict "Movie" [typeddict-unknown-key]'] +Line 19: Unexpected errors ['typeddicts_extra_items.py:19: error: Unexpected keyword argument "extra_items" for "TypedDict" [misc]'] +Line 21: Unexpected errors ['typeddicts_extra_items.py:21: error: Extra keys ("name", "novel_adaptation") for TypedDict "MovieFunctional" [typeddict-unknown-key]'] +Line 29: Unexpected errors ['typeddicts_extra_items.py:29: error: Expression is of type "Any", not "bool" [assert-type]', 'typeddicts_extra_items.py:29: error: TypedDict "Movie" has no key "novel_adaptation" [typeddict-item]'] +Line 33: Unexpected errors ['typeddicts_extra_items.py:33: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 40: Unexpected errors ['typeddicts_extra_items.py:40: error: Extra key "other_extra_key" for TypedDict "InheritedMovie" [typeddict-unknown-key]'] +Line 55: Unexpected errors ['typeddicts_extra_items.py:55: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 64: Unexpected errors ['typeddicts_extra_items.py:64: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 70: Unexpected errors ['typeddicts_extra_items.py:70: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 82: Unexpected errors ['typeddicts_extra_items.py:82: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 88: Unexpected errors ['typeddicts_extra_items.py:88: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 100: Unexpected errors ['typeddicts_extra_items.py:100: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 103: Unexpected errors ['typeddicts_extra_items.py:103: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 106: Unexpected errors ['typeddicts_extra_items.py:106: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 121: Unexpected errors ['typeddicts_extra_items.py:121: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 126: Unexpected errors ['typeddicts_extra_items.py:126: error: TypedDict "MovieEI" has no key "year" [typeddict-item]'] +Line 134: Unexpected errors ['typeddicts_extra_items.py:134: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 141: Unexpected errors ['typeddicts_extra_items.py:141: error: Unexpected keyword argument "year" for "unpack_extra" [call-arg]'] +Line 146: Unexpected errors ['typeddicts_extra_items.py:146: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 149: Unexpected errors ['typeddicts_extra_items.py:149: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 152: Unexpected errors ['typeddicts_extra_items.py:152: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 162: Unexpected errors ['typeddicts_extra_items.py:162: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 168: Unexpected errors ['typeddicts_extra_items.py:168: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 178: Unexpected errors ['typeddicts_extra_items.py:178: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 201: Unexpected errors ['typeddicts_extra_items.py:201: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 208: Unexpected errors ['typeddicts_extra_items.py:208: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 219: Unexpected errors ['typeddicts_extra_items.py:219: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 222: Unexpected errors ['typeddicts_extra_items.py:222: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 226: Unexpected errors ['typeddicts_extra_items.py:226: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 239: Unexpected errors ['typeddicts_extra_items.py:239: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 242: Unexpected errors ['typeddicts_extra_items.py:242: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 245: Unexpected errors ['typeddicts_extra_items.py:245: error: Extra key "year" for TypedDict "MovieExtraInt" [typeddict-unknown-key]'] +Line 246: Unexpected errors ['typeddicts_extra_items.py:246: error: Extra key "description" for TypedDict "MovieExtraStr" [typeddict-unknown-key]'] +Line 257: Unexpected errors ['typeddicts_extra_items.py:257: error: Extra key "year" for TypedDict "MovieExtraInt" [typeddict-unknown-key]'] +Line 271: Unexpected errors ['typeddicts_extra_items.py:271: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 275: Unexpected errors ['typeddicts_extra_items.py:275: error: Extra key "year" for TypedDict "ExtraMovie" [typeddict-unknown-key]'] +Line 280: Unexpected errors ['typeddicts_extra_items.py:280: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 290: Unexpected errors ['typeddicts_extra_items.py:290: error: Extra key "summary" for TypedDict "MovieExtraStr" [typeddict-unknown-key]'] +Line 291: Unexpected errors ['typeddicts_extra_items.py:291: error: Incompatible types in assignment (expression has type "MovieExtraStr", variable has type "Mapping[str, str]") [assignment]'] +Line 293: Unexpected errors ['typeddicts_extra_items.py:293: error: Extra key "year" for TypedDict "MovieExtraInt" [typeddict-unknown-key]'] +Line 295: Unexpected errors ['typeddicts_extra_items.py:295: error: Incompatible types in assignment (expression has type "MovieExtraInt", variable has type "Mapping[str, int | str]") [assignment]'] +Line 301: Unexpected errors ['typeddicts_extra_items.py:301: error: Expression is of type "list[tuple[str, object]]", not "list[tuple[str, int | str]]" [assert-type]'] +Line 302: Unexpected errors ['typeddicts_extra_items.py:302: error: Expression is of type "list[object]", not "list[int | str]" [assert-type]'] +Line 310: Unexpected errors ['typeddicts_extra_items.py:310: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg]'] +Line 317: Unexpected errors ['typeddicts_extra_items.py:317: error: Incompatible types in assignment (expression has type "IntDict", variable has type "dict[str, int]") [assignment]'] +Line 320: Unexpected errors ['typeddicts_extra_items.py:320: error: Extra key "bar" for TypedDict "IntDictWithNum" [typeddict-unknown-key]'] +Line 321: Unexpected errors ['typeddicts_extra_items.py:321: error: Incompatible types in assignment (expression has type "IntDictWithNum", variable has type "dict[str, int]") [assignment]'] +Line 328: Unexpected errors ['typeddicts_extra_items.py:328: error: "IntDictWithNum" has no attribute "clear" [attr-defined]'] +Line 330: Unexpected errors ['typeddicts_extra_items.py:330: error: Expression is of type "Any", not "tuple[str, int]" [assert-type]', 'typeddicts_extra_items.py:330: error: "IntDictWithNum" has no attribute "popitem" [attr-defined]'] +Line 333: Unexpected errors ['typeddicts_extra_items.py:333: error: TypedDict key must be a string literal; expected one of ("num") [literal-required]'] +Line 334: Unexpected errors ['typeddicts_extra_items.py:334: error: Expected TypedDict key to be string literal [misc]'] +""" +output = """ +typeddicts_extra_items.py:11: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:14: error: Extra key "novel_adaptation" for TypedDict "Movie" [typeddict-unknown-key] +typeddicts_extra_items.py:15: error: Extra key "year" for TypedDict "Movie" [typeddict-unknown-key] +typeddicts_extra_items.py:19: error: Unexpected keyword argument "extra_items" for "TypedDict" [misc] +typeddicts_extra_items.py:21: error: Extra keys ("name", "novel_adaptation") for TypedDict "MovieFunctional" [typeddict-unknown-key] +typeddicts_extra_items.py:22: error: Extra keys ("name", "year") for TypedDict "MovieFunctional" [typeddict-unknown-key] +typeddicts_extra_items.py:29: error: Expression is of type "Any", not "bool" [assert-type] +typeddicts_extra_items.py:29: error: TypedDict "Movie" has no key "novel_adaptation" [typeddict-item] +typeddicts_extra_items.py:33: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:39: error: Incompatible types (expression has type "None", TypedDict item "year" has type "int") [typeddict-item] +typeddicts_extra_items.py:40: error: Extra key "other_extra_key" for TypedDict "InheritedMovie" [typeddict-unknown-key] +typeddicts_extra_items.py:49: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:55: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:64: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:67: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:70: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:73: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:82: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:88: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:100: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:103: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:106: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:111: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:114: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:121: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:125: error: Key "name" of TypedDict "MovieEI" cannot be deleted [misc] +typeddicts_extra_items.py:126: error: TypedDict "MovieEI" has no key "year" [typeddict-item] +typeddicts_extra_items.py:134: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:137: note: "unpack_no_extra" defined here +typeddicts_extra_items.py:138: note: "unpack_extra" defined here +typeddicts_extra_items.py:140: error: Unexpected keyword argument "year" for "unpack_no_extra" [call-arg] +typeddicts_extra_items.py:141: error: Unexpected keyword argument "year" for "unpack_extra" [call-arg] +typeddicts_extra_items.py:146: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:149: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:152: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:162: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:168: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:171: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:178: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:201: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:208: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:219: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:222: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:226: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:239: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:242: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:245: error: Extra key "year" for TypedDict "MovieExtraInt" [typeddict-unknown-key] +typeddicts_extra_items.py:246: error: Extra key "description" for TypedDict "MovieExtraStr" [typeddict-unknown-key] +typeddicts_extra_items.py:257: error: Extra key "year" for TypedDict "MovieExtraInt" [typeddict-unknown-key] +typeddicts_extra_items.py:269: error: Extra key "year" for TypedDict "NonClosedMovie" [typeddict-unknown-key] +typeddicts_extra_items.py:271: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:275: error: Extra key "year" for TypedDict "ExtraMovie" [typeddict-unknown-key] +typeddicts_extra_items.py:276: error: Extra key "language" for TypedDict "ExtraMovie" [typeddict-unknown-key] +typeddicts_extra_items.py:280: error: Unexpected keyword argument "closed" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:284: error: Extra key "year" for TypedDict "ClosedMovie" [typeddict-unknown-key] +typeddicts_extra_items.py:290: error: Extra key "summary" for TypedDict "MovieExtraStr" [typeddict-unknown-key] +typeddicts_extra_items.py:291: error: Incompatible types in assignment (expression has type "MovieExtraStr", variable has type "Mapping[str, str]") [assignment] +typeddicts_extra_items.py:293: error: Extra key "year" for TypedDict "MovieExtraInt" [typeddict-unknown-key] +typeddicts_extra_items.py:294: error: Incompatible types in assignment (expression has type "MovieExtraInt", variable has type "Mapping[str, int]") [assignment] +typeddicts_extra_items.py:295: error: Incompatible types in assignment (expression has type "MovieExtraInt", variable has type "Mapping[str, int | str]") [assignment] +typeddicts_extra_items.py:301: error: Expression is of type "list[tuple[str, object]]", not "list[tuple[str, int | str]]" [assert-type] +typeddicts_extra_items.py:302: error: Expression is of type "list[object]", not "list[int | str]" [assert-type] +typeddicts_extra_items.py:310: error: Unexpected keyword argument "extra_items" for "__init_subclass__" of "TypedDict" [call-arg] +typeddicts_extra_items.py:317: error: Incompatible types in assignment (expression has type "IntDict", variable has type "dict[str, int]") [assignment] +typeddicts_extra_items.py:320: error: Extra key "bar" for TypedDict "IntDictWithNum" [typeddict-unknown-key] +typeddicts_extra_items.py:321: error: Incompatible types in assignment (expression has type "IntDictWithNum", variable has type "dict[str, int]") [assignment] +typeddicts_extra_items.py:328: error: "IntDictWithNum" has no attribute "clear" [attr-defined] +typeddicts_extra_items.py:330: error: Expression is of type "Any", not "tuple[str, int]" [assert-type] +typeddicts_extra_items.py:330: error: "IntDictWithNum" has no attribute "popitem" [attr-defined] +typeddicts_extra_items.py:333: error: TypedDict key must be a string literal; expected one of ("num") [literal-required] +typeddicts_extra_items.py:334: error: Expected TypedDict key to be string literal [misc] +typeddicts_extra_items.py:343: error: Incompatible types in assignment (expression has type "dict[str, int]", variable has type "IntDict") [assignment] +""" diff --git a/conformance/results/pyre/typeddicts_extra_items.toml b/conformance/results/pyre/typeddicts_extra_items.toml new file mode 100644 index 00000000..79a77d13 --- /dev/null +++ b/conformance/results/pyre/typeddicts_extra_items.toml @@ -0,0 +1,139 @@ +conformant = "Fail" +notes = """ +Not supported. +""" +conformance_automated = "Fail" +errors_diff = """ +Line 181: Expected 1 errors +Line 184: Expected 1 errors +Line 206: Expected 1 errors +Line 213: Expected 1 errors +Line 233: Expected 1 errors +Line 247: Expected 1 errors +Line 248: Expected 1 errors +Line 259: Expected 1 errors +Lines 91, 92: Expected error (tag 'MovieC') +Lines 94, 95: Expected error (tag 'MovieD') +Line 11: Unexpected errors ['typeddicts_extra_items.py:11:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 14: Unexpected errors ['typeddicts_extra_items.py:14:11 TypedDict initialization error [55]: TypedDict `Movie` has no field `novel_adaptation`.'] +Line 21: Unexpected errors ['typeddicts_extra_items.py:21:21 TypedDict initialization error [55]: TypedDict `MovieFunctional` has no field `novel_adaptation`.'] +Line 29: Unexpected errors ['typeddicts_extra_items.py:29:4 Assert type [70]: Expected `bool` but got `str`.', 'typeddicts_extra_items.py:29:22 TypedDict accessed with a missing key [27]: TypedDict `Movie` has no key `novel_adaptation`.'] +Line 33: Unexpected errors ['typeddicts_extra_items.py:33:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 40: Unexpected errors ['typeddicts_extra_items.py:40:20 TypedDict initialization error [55]: TypedDict `InheritedMovie` has no field `other_extra_key`.'] +Line 55: Unexpected errors ['typeddicts_extra_items.py:55:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`.'] +Line 64: Unexpected errors ['typeddicts_extra_items.py:64:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`.'] +Line 70: Unexpected errors ['typeddicts_extra_items.py:70:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 82: Unexpected errors ['typeddicts_extra_items.py:82:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`.'] +Line 88: Unexpected errors ['typeddicts_extra_items.py:88:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`.'] +Line 100: Unexpected errors ['typeddicts_extra_items.py:100:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 103: Unexpected errors ['typeddicts_extra_items.py:103:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`.'] +Line 106: Unexpected errors ['typeddicts_extra_items.py:106:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 121: Unexpected errors ['typeddicts_extra_items.py:121:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 126: Unexpected errors ['typeddicts_extra_items.py:126:14 TypedDict accessed with a missing key [27]: TypedDict `MovieEI` has no key `year`.'] +Line 134: Unexpected errors ['typeddicts_extra_items.py:134:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 141: Unexpected errors ['typeddicts_extra_items.py:141:0 Unexpected keyword [28]: Unexpected keyword argument `year` to call `unpack_extra`.'] +Line 146: Unexpected errors ['typeddicts_extra_items.py:146:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 149: Unexpected errors ['typeddicts_extra_items.py:149:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 152: Unexpected errors ['typeddicts_extra_items.py:152:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 162: Unexpected errors ['typeddicts_extra_items.py:162:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 168: Unexpected errors ['typeddicts_extra_items.py:168:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 178: Unexpected errors ['typeddicts_extra_items.py:178:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 201: Unexpected errors ['typeddicts_extra_items.py:201:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 208: Unexpected errors ['typeddicts_extra_items.py:208:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 219: Unexpected errors ['typeddicts_extra_items.py:219:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 222: Unexpected errors ['typeddicts_extra_items.py:222:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 226: Unexpected errors ['typeddicts_extra_items.py:226:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 239: Unexpected errors ['typeddicts_extra_items.py:239:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 242: Unexpected errors ['typeddicts_extra_items.py:242:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 245: Unexpected errors ['typeddicts_extra_items.py:245:27 TypedDict initialization error [55]: TypedDict `MovieExtraInt` has no field `year`.'] +Line 246: Unexpected errors ['typeddicts_extra_items.py:246:27 TypedDict initialization error [55]: TypedDict `MovieExtraStr` has no field `description`.'] +Line 257: Unexpected errors ['typeddicts_extra_items.py:257:28 TypedDict initialization error [55]: TypedDict `MovieExtraInt` has no field `year`.'] +Line 271: Unexpected errors ['typeddicts_extra_items.py:271:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 275: Unexpected errors ['typeddicts_extra_items.py:275:0 Unexpected keyword [28]: Unexpected keyword argument `year` to call `ExtraMovie.__init__`.'] +Line 280: Unexpected errors ['typeddicts_extra_items.py:280:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`.'] +Line 290: Unexpected errors ['typeddicts_extra_items.py:290:28 TypedDict initialization error [55]: TypedDict `MovieExtraStr` has no field `summary`.'] +Line 291: Unexpected errors ['typeddicts_extra_items.py:291:0 Incompatible variable type [9]: str_mapping is declared to have type `Mapping[str, str]` but is used as type `MovieExtraStr`.'] +Line 293: Unexpected errors ['typeddicts_extra_items.py:293:28 TypedDict initialization error [55]: TypedDict `MovieExtraInt` has no field `year`.'] +Line 295: Unexpected errors ['typeddicts_extra_items.py:295:0 Incompatible variable type [9]: int_str_mapping is declared to have type `Mapping[str, Union[int, str]]` but is used as type `MovieExtraInt`.'] +Line 301: Unexpected errors ['typeddicts_extra_items.py:301:4 Assert type [70]: Expected `List[Tuple[str, Union[int, str]]]` but got `List[Tuple[str, object]]`.'] +Line 302: Unexpected errors ['typeddicts_extra_items.py:302:4 Assert type [70]: Expected `List[Union[int, str]]` but got `List[object]`.'] +Line 310: Unexpected errors ['typeddicts_extra_items.py:310:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`.'] +Line 317: Unexpected errors ['typeddicts_extra_items.py:317:4 Incompatible variable type [9]: v is declared to have type `Dict[str, int]` but is used as type `IntDict`.'] +Line 320: Unexpected errors ['typeddicts_extra_items.py:320:40 TypedDict initialization error [55]: TypedDict `IntDictWithNum` has no field `bar`.'] +Line 321: Unexpected errors ['typeddicts_extra_items.py:321:0 Incompatible variable type [9]: regular_dict is declared to have type `Dict[str, int]` but is used as type `IntDictWithNum`.'] +Line 328: Unexpected errors ['typeddicts_extra_items.py:328:0 Undefined attribute [16]: `IntDictWithNum` has no attribute `clear`.'] +Line 330: Unexpected errors ['typeddicts_extra_items.py:330:0 Assert type [70]: Expected `Tuple[str, int]` but got `typing.Any`.', 'typeddicts_extra_items.py:330:12 Undefined attribute [16]: `IntDictWithNum` has no attribute `popitem`.'] +Line 333: Unexpected errors ["typeddicts_extra_items.py:333:26 TypedDict accessed with a non-literal [26]: TypedDict key must be a string literal. Expected one of ('num')."] +Line 334: Unexpected errors ["typeddicts_extra_items.py:334:30 TypedDict accessed with a non-literal [26]: TypedDict key must be a string literal. Expected one of ('num')."] +""" +output = """ +typeddicts_extra_items.py:11:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:14:11 TypedDict initialization error [55]: TypedDict `Movie` has no field `novel_adaptation`. +typeddicts_extra_items.py:15:11 TypedDict initialization error [55]: TypedDict `Movie` has no field `year`. +typeddicts_extra_items.py:21:21 TypedDict initialization error [55]: TypedDict `MovieFunctional` has no field `novel_adaptation`. +typeddicts_extra_items.py:22:21 TypedDict initialization error [55]: TypedDict `MovieFunctional` has no field `year`. +typeddicts_extra_items.py:29:4 Assert type [70]: Expected `bool` but got `str`. +typeddicts_extra_items.py:29:22 TypedDict accessed with a missing key [27]: TypedDict `Movie` has no key `novel_adaptation`. +typeddicts_extra_items.py:33:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:39:20 TypedDict initialization error [55]: Expected type `int` for `InheritedMovie` field `year` but got `None`. +typeddicts_extra_items.py:40:20 TypedDict initialization error [55]: TypedDict `InheritedMovie` has no field `other_extra_key`. +typeddicts_extra_items.py:49:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`. +typeddicts_extra_items.py:55:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`. +typeddicts_extra_items.py:64:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`. +typeddicts_extra_items.py:67:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`. +typeddicts_extra_items.py:70:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:73:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`. +typeddicts_extra_items.py:82:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`. +typeddicts_extra_items.py:88:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`. +typeddicts_extra_items.py:100:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:103:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`. +typeddicts_extra_items.py:106:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:111:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:114:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:121:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:125:4 Invalid TypedDict operation [54]: Cannot delete required field `name` from TypedDict `MovieEI`. +typeddicts_extra_items.py:126:14 TypedDict accessed with a missing key [27]: TypedDict `MovieEI` has no key `year`. +typeddicts_extra_items.py:134:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:140:0 Unexpected keyword [28]: Unexpected keyword argument `year` to call `unpack_no_extra`. +typeddicts_extra_items.py:141:0 Unexpected keyword [28]: Unexpected keyword argument `year` to call `unpack_extra`. +typeddicts_extra_items.py:146:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:149:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:152:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:162:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:168:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:171:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:178:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:201:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:208:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:219:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:222:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:226:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:239:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:242:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:245:27 TypedDict initialization error [55]: TypedDict `MovieExtraInt` has no field `year`. +typeddicts_extra_items.py:246:27 TypedDict initialization error [55]: TypedDict `MovieExtraStr` has no field `description`. +typeddicts_extra_items.py:257:28 TypedDict initialization error [55]: TypedDict `MovieExtraInt` has no field `year`. +typeddicts_extra_items.py:269:0 Unexpected keyword [28]: Unexpected keyword argument `year` to call `NonClosedMovie.__init__`. +typeddicts_extra_items.py:271:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:275:0 Unexpected keyword [28]: Unexpected keyword argument `year` to call `ExtraMovie.__init__`. +typeddicts_extra_items.py:276:0 Unexpected keyword [28]: Unexpected keyword argument `language` to call `ExtraMovie.__init__`. +typeddicts_extra_items.py:280:0 Unexpected keyword [28]: Unexpected keyword argument `closed` to call `object.__init_subclass__`. +typeddicts_extra_items.py:284:0 Unexpected keyword [28]: Unexpected keyword argument `year` to call `ClosedMovie.__init__`. +typeddicts_extra_items.py:290:28 TypedDict initialization error [55]: TypedDict `MovieExtraStr` has no field `summary`. +typeddicts_extra_items.py:291:0 Incompatible variable type [9]: str_mapping is declared to have type `Mapping[str, str]` but is used as type `MovieExtraStr`. +typeddicts_extra_items.py:293:28 TypedDict initialization error [55]: TypedDict `MovieExtraInt` has no field `year`. +typeddicts_extra_items.py:294:0 Incompatible variable type [9]: int_mapping is declared to have type `Mapping[str, int]` but is used as type `MovieExtraInt`. +typeddicts_extra_items.py:295:0 Incompatible variable type [9]: int_str_mapping is declared to have type `Mapping[str, Union[int, str]]` but is used as type `MovieExtraInt`. +typeddicts_extra_items.py:301:4 Assert type [70]: Expected `List[Tuple[str, Union[int, str]]]` but got `List[Tuple[str, object]]`. +typeddicts_extra_items.py:302:4 Assert type [70]: Expected `List[Union[int, str]]` but got `List[object]`. +typeddicts_extra_items.py:310:0 Unexpected keyword [28]: Unexpected keyword argument `extra_items` to call `object.__init_subclass__`. +typeddicts_extra_items.py:317:4 Incompatible variable type [9]: v is declared to have type `Dict[str, int]` but is used as type `IntDict`. +typeddicts_extra_items.py:320:40 TypedDict initialization error [55]: TypedDict `IntDictWithNum` has no field `bar`. +typeddicts_extra_items.py:321:0 Incompatible variable type [9]: regular_dict is declared to have type `Dict[str, int]` but is used as type `IntDictWithNum`. +typeddicts_extra_items.py:328:0 Undefined attribute [16]: `IntDictWithNum` has no attribute `clear`. +typeddicts_extra_items.py:330:0 Assert type [70]: Expected `Tuple[str, int]` but got `typing.Any`. +typeddicts_extra_items.py:330:12 Undefined attribute [16]: `IntDictWithNum` has no attribute `popitem`. +typeddicts_extra_items.py:333:26 TypedDict accessed with a non-literal [26]: TypedDict key must be a string literal. Expected one of ('num'). +typeddicts_extra_items.py:334:30 TypedDict accessed with a non-literal [26]: TypedDict key must be a string literal. Expected one of ('num'). +typeddicts_extra_items.py:343:4 Incompatible variable type [9]: int_dict is declared to have type `IntDict` but is used as type `Dict[str, int]`. +""" diff --git a/conformance/results/pyright/typeddicts_extra_items.toml b/conformance/results/pyright/typeddicts_extra_items.toml new file mode 100644 index 00000000..8bdb0fab --- /dev/null +++ b/conformance/results/pyright/typeddicts_extra_items.toml @@ -0,0 +1,65 @@ +conformant = "Pass" +conformance_automated = "Pass" +errors_diff = """ +""" +output = """ +typeddicts_extra_items.py:15:45 - error: Type "dict[str, str | int]" is not assignable to declared type "Movie" +  "Literal[1982]" is not assignable to "bool" (reportAssignmentType) +typeddicts_extra_items.py:22:55 - error: Type "dict[str, str | int]" is not assignable to declared type "MovieFunctional" +  "Literal[1982]" is not assignable to "bool" (reportAssignmentType) +typeddicts_extra_items.py:39:54 - error: Type "dict[str, str | None]" is not assignable to declared type "InheritedMovie" +  "None" is not assignable to "int" (reportAssignmentType) +typeddicts_extra_items.py:49:35 - error: Expected "closed" parameter to have a value of True or False (reportGeneralTypeIssues) +typeddicts_extra_items.py:67:7 - error: Base class "ClosedBase" is a TypedDict that limits the type of extra items to type "Never" +  Cannot add item "age" (reportIncompatibleVariableOverride) +typeddicts_extra_items.py:73:7 - error: Base class "ExtraItemsBase" is a TypedDict that limits the type of extra items to type "int" +  Cannot add item "age" because it must be NotRequired (reportIncompatibleVariableOverride) +typeddicts_extra_items.py:91:7 - error: Base class "MovieA" is a TypedDict that limits the type of extra items to type "Never" +  Cannot add item "age" (reportIncompatibleVariableOverride) +typeddicts_extra_items.py:94:7 - error: Base class "MovieB" is a TypedDict that limits the type of extra items to type "Never" +  Cannot add item "age" (reportIncompatibleVariableOverride) +typeddicts_extra_items.py:111:50 - error: "Required" is not allowed in this context (reportInvalidTypeForm) +typeddicts_extra_items.py:114:57 - error: "NotRequired" is not allowed in this context (reportInvalidTypeForm) +typeddicts_extra_items.py:125:9 - error: Could not delete item in TypedDict +  "name" is a required key and cannot be deleted (reportGeneralTypeIssues) +typeddicts_extra_items.py:140:48 - error: No parameter named "year" (reportCallIssue) +typeddicts_extra_items.py:171:7 - error: Base class "Parent" is a TypedDict that limits the type of extra items to type "int | None" +  Cannot add item "extra_items" with type "int" (reportIncompatibleVariableOverride) +typeddicts_extra_items.py:181:7 - error: Base class "MovieBase2" is a TypedDict that limits the type of extra items to type "int | None" +  Cannot add item "year" because it must be NotRequired (reportIncompatibleVariableOverride) +typeddicts_extra_items.py:184:7 - error: Base class "MovieBase2" is a TypedDict that limits the type of extra items to type "int | None" +  Cannot add item "year" with type "int" (reportIncompatibleVariableOverride) +typeddicts_extra_items.py:206:22 - error: Type "MovieDetails" is not assignable to declared type "MovieBase2" +  Type of "year" is incompatible with type of "extra_items" in "MovieBase2" +    Type "int" is not assignable to type "int | None" +      "int" is not assignable to "None" (reportAssignmentType) +typeddicts_extra_items.py:213:22 - error: Type "MovieWithYear2" is not assignable to declared type "MovieBase2" +  "year" is not required in "MovieBase2" (reportAssignmentType) +typeddicts_extra_items.py:233:19 - error: Type "MovieDetails5" is not assignable to declared type "MovieSI" +  Type of "actors" is incompatible with type of "extra_items" in "MovieSI" +    Type "list[str]" is not assignable to type "str | int" +      "list[str]" is not assignable to "str" +      "list[str]" is not assignable to "int" (reportAssignmentType) +typeddicts_extra_items.py:247:13 - error: Type "MovieExtraStr" is not assignable to declared type "MovieExtraInt" +  Type of "extra_items" is incompatible with type of "extra_items" in "MovieExtraStr" +    "str" is not assignable to "int" (reportAssignmentType) +typeddicts_extra_items.py:248:13 - error: Type "MovieExtraInt" is not assignable to declared type "MovieExtraStr" +  Type of "extra_items" is incompatible with type of "extra_items" in "MovieExtraInt" +    "int" is not assignable to "str" (reportAssignmentType) +typeddicts_extra_items.py:259:14 - error: Type "MovieNotClosed" is not assignable to declared type "MovieExtraInt" +  Type of "extra_items" is incompatible with type of "extra_items" in "MovieNotClosed" +    "object" is not assignable to "int" (reportAssignmentType) +typeddicts_extra_items.py:269:1 - error: No overloads for "__init__" match the provided arguments +  Argument types: (Literal['No Country for Old Men'], Literal[2007]) (reportCallIssue) +typeddicts_extra_items.py:276:52 - error: Argument of type "Literal['English']" cannot be assigned to parameter "language" of type "int" in function "__init__" +  "Literal['English']" is not assignable to "int" (reportArgumentType) +typeddicts_extra_items.py:284:1 - error: No overloads for "__init__" match the provided arguments +  Argument types: (Literal['No Country for Old Men'], Literal[2007]) (reportCallIssue) +typeddicts_extra_items.py:294:34 - error: Type "MovieExtraInt" is not assignable to declared type "Mapping[str, int]" +  "Mapping[str, str | int]" is not assignable to "Mapping[str, int]" +    Type parameter "_VT_co@Mapping" is covariant, but "str | int" is not a subtype of "int" +      Type "str | int" is not assignable to type "int" +        "str" is not assignable to "int" (reportAssignmentType) +typeddicts_extra_items.py:343:25 - error: Type "dict[str, int]" is not assignable to declared type "IntDict" +  "dict[str, int]" is not assignable to "IntDict" (reportAssignmentType) +""" diff --git a/conformance/tests/typeddicts_extra_items.py b/conformance/tests/typeddicts_extra_items.py new file mode 100644 index 00000000..040d543c --- /dev/null +++ b/conformance/tests/typeddicts_extra_items.py @@ -0,0 +1,347 @@ +# pyright: enableExperimentalFeatures=true +from collections.abc import Mapping +from typing_extensions import ReadOnly, TypedDict, Unpack +from typing import assert_type, Required, NotRequired, Never + + +# > For a TypedDict type that specifies +# > ``extra_items``, during construction, the value type of each unknown item +# is expected to be non-required and assignable to the ``extra_items`` argument. + +class Movie(TypedDict, extra_items=bool): + name: str + +a: Movie = {"name": "Blade Runner", "novel_adaptation": True} # OK +b: Movie = {"name": "Blade Runner", "year": 1982} # E: 'int' is not assignable to 'bool' + +# > The alternative inline syntax is also supported:: + +MovieFunctional = TypedDict("MovieFunctional", {"name": str}, extra_items=bool) + +c: MovieFunctional = {"name": "Blade Runner", "novel_adaptation": True} # OK +d: MovieFunctional = {"name": "Blade Runner", "year": 1982} # E: 'int' is not assignable to 'bool' + +# > Accessing extra items is allowed. Type checkers must infer their value type from +# > the ``extra_items`` argument + +def movie_keys(movie: Movie) -> None: + assert_type(movie["name"], str) + assert_type(movie["novel_adaptation"], bool) + +# > ``extra_items`` is inherited through subclassing + +class MovieBase(TypedDict, extra_items=ReadOnly[int | None]): + name: str + +class InheritedMovie(MovieBase): + year: int + +e: InheritedMovie = {"name": "Blade Runner", "year": None} # E: 'None' is incompatible with 'int' +f: InheritedMovie = { + "name": "Blade Runner", + "year": 1982, + "other_extra_key": None, +} # OK + +# > Similar to ``total``, only a literal ``True`` or ``False`` is supported as the +# > value of the ``closed`` argument. Type checkers should reject any non-literal value. + +class IllegalTD(TypedDict, closed=42 == 42): # E: Argument to "closed" must be a literal True or False + name: str + +# > Passing ``closed=False`` explicitly requests the default TypedDict behavior, +# > where arbitrary other keys may be present and subclasses may add arbitrary items. + +class BaseTD(TypedDict, closed=False): + name: str + +class ChildTD(BaseTD): # OK + age: int + +# > It is a type checker error to pass ``closed=False`` if a superclass has +# > ``closed=True`` or sets ``extra_items``. + +class ClosedBase(TypedDict, closed=True): + name: str + +class IllegalChild1(ClosedBase, closed=False): # E: Cannot set 'closed=False' when superclass is 'closed=True' + age: int + +class ExtraItemsBase(TypedDict, extra_items=int): + name: str + +class IllegalChild2(ExtraItemsBase, closed=False): # E: Cannot set 'closed=False' when superclass has 'extra_items' + age: int + +# > If ``closed`` is not provided, the behavior is inherited from the superclass. +# > If the superclass is TypedDict itself or the superclass does not have ``closed=True`` +# > or the ``extra_items`` parameter, the previous TypedDict behavior is preserved: +# > arbitrary extra items are allowed. If the superclass has ``closed=True``, the +# > child class is also closed. + +class BaseMovie(TypedDict, closed=True): + name: str + +class MovieA(BaseMovie): # OK, still closed + pass + +class MovieB(BaseMovie, closed=True): # OK, but redundant + pass + +class MovieC(MovieA): # E[MovieC] + age: int # E[MovieC]: "MovieC" is a closed TypedDict; extra key "age" not allowed + +class MovieD(MovieB): # E[MovieD] + age: int # E[MovieD]: "MovieD" is a closed TypedDict; extra key "age" not allowed + +# > It is possible to use ``closed=True`` when subclassing if the ``extra_items`` +# > argument is a read-only type. + +class MovieES(TypedDict, extra_items=ReadOnly[str]): + pass + +class MovieClosed(MovieES, closed=True): # OK + pass + +class MovieNever(MovieES, extra_items=Never): # OK, but 'closed=True' is preferred + pass + +# > It is an error to use ``Required[]`` or ``NotRequired[]`` with ``extra_items``. + +class IllegalExtraItemsTD(TypedDict, extra_items=Required[int]): # E: 'extra_items' value cannot be 'Required[...]' + name: str + +class AnotherIllegalExtraItemsTD(TypedDict, extra_items=NotRequired[int]): # E: 'extra_items' value cannot be 'NotRequired[...]' + name: str + +# > The extra items are non-required, regardless of the totality of the +# > TypedDict. Operations that are available to ``NotRequired`` items should also be available to the +# > extra items. + +class MovieEI(TypedDict, extra_items=int): + name: str + +def del_items(movie: MovieEI) -> None: + del movie["name"] # E: The value type of 'name' is 'Required[int]' + del movie["year"] # OK: The value type of 'year' is 'NotRequired[int]' + +# > For type checking purposes, ``Unpack[SomeTypedDict]`` with extra items should be +# > treated as its equivalent in regular parameters. + +class MovieNoExtra(TypedDict): + name: str + +class MovieExtra(TypedDict, extra_items=int): + name: str + +def unpack_no_extra(**kwargs: Unpack[MovieNoExtra]) -> None: ... +def unpack_extra(**kwargs: Unpack[MovieExtra]) -> None: ... + +unpack_no_extra(name="No Country for Old Men", year=2007) # E: Unrecognized item +unpack_extra(name="No Country for Old Men", year=2007) # OK + +# > Notably, if the TypedDict type specifies ``extra_items`` to be read-only, +# > subclasses of the TypedDict type may redeclare ``extra_items``. + +class ReadOnlyBase(TypedDict, extra_items=ReadOnly[int]): + pass + +class ReadOnlyChild(ReadOnlyBase, extra_items=ReadOnly[bool]): # OK + pass + +class MutableChild(ReadOnlyBase, extra_items=int): # OK + pass + +# > Because a non-closed TypedDict type implicitly allows non-required extra items +# > of value type ``ReadOnly[object]``, its subclass can override the +# > ``extra_items`` argument with more specific types. + +class NonClosedBase(TypedDict): + name: str + +class SpecificExtraItems(NonClosedBase, extra_items=bytes): # OK + year: int + +# > First, it is not allowed to change the value of ``extra_items`` in a subclass +# > unless it is declared to be ``ReadOnly`` in the superclass. + +class Parent(TypedDict, extra_items=int | None): + pass + +class Child(Parent, extra_items=int): # E: Cannot change 'extra_items' type unless it is 'ReadOnly' in the superclass + pass + +# > Second, ``extra_items=T`` effectively defines the value type of any unnamed +# > items accepted to the TypedDict and marks them as non-required. Thus, the above +# > restriction applies to any additional items defined in a subclass. + +class MovieBase2(TypedDict, extra_items=int | None): + name: str + +class MovieRequiredYear(MovieBase2): # E: Required key 'year' is not known to 'MovieBase' + year: int | None + +class MovieNotRequiredYear(MovieBase2): # E: 'int | None' is not consistent with 'int' + year: NotRequired[int] + +class MovieWithYear(MovieBase): # OK + year: NotRequired[int | None] + +# > Let ``S`` be the set of keys of the explicitly defined items on a TypedDict +# > type. If it specifies ``extra_items=T``, the TypedDict type is considered to +# > have an infinite set of items that all satisfy the following conditions. +# > - If ``extra_items`` is read-only: +# > - The key's value type is :term:`assignable` to ``T``. +# > - The key is not in ``S``. +# > - If ``extra_items`` is not read-only: +# > - The key is non-required. +# > - The key's value type is :term:`consistent` with ``T``. +# > - The key is not in ``S``. + +class MovieDetails(TypedDict, extra_items=int | None): + name: str + year: NotRequired[int] + +details2: MovieDetails = {"name": "Kill Bill Vol. 1", "year": 2003} +movie2: MovieBase2 = details2 # E: While 'int' is not consistent with 'int | None' + +class MovieWithYear2(TypedDict, extra_items=int | None): + name: str + year: int | None + +details3: MovieWithYear2 = {"name": "Kill Bill Vol. 1", "year": 2003} +movie3: MovieBase2 = details3 # E:'year' is not required in 'Movie', but it is required in 'MovieWithYear2' + +# > When ``extra_items`` is specified to be read-only on a TypedDict type, it is +# > possible for an item to have a :term:`narrower ` type than the +# > ``extra_items`` argument. + +class MovieSI(TypedDict, extra_items=ReadOnly[str | int]): + name: str + +class MovieDetails4(TypedDict, extra_items=int): + name: str + year: NotRequired[int] + +class MovieDetails5(TypedDict, extra_items=int): + name: str + actors: list[str] + +details4: MovieDetails4 = {"name": "Kill Bill Vol. 2", "year": 2004} +details5: MovieDetails5 = {"name": "Kill Bill Vol. 2", "actors": ["Uma Thurman"]} +movie4: MovieSI = details4 # OK. 'int' is assignable to 'str | int'. +movie5: MovieSI = details5 # E: 'list[str]' is not assignable to 'str | int'. + +# > ``extra_items`` as a pseudo-item follows the same rules that other items have, +# > so when both TypedDicts types specify ``extra_items``, this check is naturally +# > enforced. + +class MovieExtraInt(TypedDict, extra_items=int): + name: str + +class MovieExtraStr(TypedDict, extra_items=str): + name: str + +extra_int: MovieExtraInt = {"name": "No Country for Old Men", "year": 2007} +extra_str: MovieExtraStr = {"name": "No Country for Old Men", "description": ""} +extra_int = extra_str # E: 'str' is not assignable to extra items type 'int' +extra_str = extra_int # E: 'int' is not assignable to extra items type 'str' + +# > A non-closed TypedDict type implicitly allows non-required extra keys of value +# > type ``ReadOnly[object]``. Applying the assignability rules between this type +# > and a closed TypedDict type is allowed. + +class MovieNotClosed(TypedDict): + name: str + +extra_int2: MovieExtraInt = {"name": "No Country for Old Men", "year": 2007} +not_closed: MovieNotClosed = {"name": "No Country for Old Men"} +extra_int2 = not_closed # E: 'extra_items=ReadOnly[object]' implicitly on 'MovieNotClosed' is not assignable to with 'extra_items=int' +not_closed = extra_int2 # OK + +# > TypedDicts that allow extra items of type ``T`` also allow arbitrary keyword +# > arguments of this type when constructed by calling the class object. + +class NonClosedMovie(TypedDict): + name: str + +NonClosedMovie(name="No Country for Old Men") # OK +NonClosedMovie(name="No Country for Old Men", year=2007) # E: Unrecognized item + +class ExtraMovie(TypedDict, extra_items=int): + name: str + +ExtraMovie(name="No Country for Old Men") # OK +ExtraMovie(name="No Country for Old Men", year=2007) # OK +ExtraMovie(name="No Country for Old Men", language="English") # E: Wrong type for extra item 'language' + +# This implies 'extra_items=Never', +# so extra keyword arguments would produce an error +class ClosedMovie(TypedDict, closed=True): + name: str + +ClosedMovie(name="No Country for Old Men") # OK +ClosedMovie(name="No Country for Old Men", year=2007) # E: Extra items not allowed + +# > A TypedDict type is :term:`assignable` to a type of the form ``Mapping[str, VT]`` +# > when all value types of the items in the TypedDict +# > are assignable to ``VT``. + +extra_str3: MovieExtraStr = {"name": "Blade Runner", "summary": ""} +str_mapping: Mapping[str, str] = extra_str3 # OK + +extra_int3: MovieExtraInt = {"name": "Blade Runner", "year": 1982} +int_mapping: Mapping[str, int] = extra_int3 # E: 'int | str' is not assignable with 'int' +int_str_mapping: Mapping[str, int | str] = extra_int3 # OK + +# > Type checkers should infer the precise signatures of ``values()`` and ``items()`` +# > on such TypedDict types. + +def foo(movie: MovieExtraInt) -> None: + assert_type(list(movie.items()), list[tuple[str, int | str]]) + assert_type(list(movie.values()), list[int | str]) + +# > The TypedDict type is :term:`assignable` to ``dict[str, VT]`` if all +# > items on the TypedDict type satisfy the following conditions: +# > - The value type of the item is :term:`consistent` with ``VT``. +# > - The item is not read-only. +# > - The item is not required. + +class IntDict(TypedDict, extra_items=int): + pass + +class IntDictWithNum(IntDict): + num: NotRequired[int] + +def clear_intdict(x: IntDict) -> None: + v: dict[str, int] = x # OK + v.clear() # OK + +not_required_num_dict: IntDictWithNum = {"num": 1, "bar": 2} +regular_dict: dict[str, int] = not_required_num_dict # OK +clear_intdict(not_required_num_dict) # OK + +# > In this case, methods that are previously unavailable on a TypedDict are allowed, +# > with signatures matching ``dict[str, VT]`` +# > (e.g.: ``__setitem__(self, key: str, value: VT) -> None``). + +not_required_num_dict.clear() # OK + +assert_type(not_required_num_dict.popitem(), tuple[str, int]) + +def nrnd(not_required_num_dict: IntDictWithNum, key: str): + not_required_num_dict[key] = 42 # OK + del not_required_num_dict[key] # OK + +# > ``dict[str, VT]`` is not assignable to a TypedDict type, +# > because such dict can be a subtype of dict. + +class CustomDict(dict[str, int]): + pass + +def might_not_be(might_not_be_a_builtin_dict: dict[str, int]): + int_dict: IntDict = might_not_be_a_builtin_dict # E + print(int_dict) + +not_a_builtin_dict = CustomDict({"num": 1}) +might_not_be(not_a_builtin_dict) diff --git a/docs/spec/typeddict.rst b/docs/spec/typeddict.rst index 4cbeedba..153fd0b7 100644 --- a/docs/spec/typeddict.rst +++ b/docs/spec/typeddict.rst @@ -285,6 +285,8 @@ refers to a dictionary object does not need to be supported, to simplify implementation. +.. _typeddict-assignability: + Assignability ^^^^^^^^^^^^^ @@ -410,6 +412,8 @@ Discussion: f(b) +.. _typeddict-operations: + Supported and Unsupported Operations ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -841,3 +845,553 @@ As discussed in the section :ref:`unpack-kwargs`, an unpacked ``TypedDict`` can kwargs["key1"] = 3 # Type check error: key1 is readonly fn: Function = impl # Accepted by type checker: function signatures are identical + +Extra Items and Closed TypedDicts +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +(Originally specified in :pep:`728`.) + +This section discusses the ``extra_items`` and ``closed`` class parameters. + +If ``extra_items`` is specified, extra items are treated as :ref:`non-required +` +items matching the ``extra_items`` argument, whose keys are allowed when +determining :ref:`supported and unsupported operations +`. + +The ``extra_items`` Class Parameter +----------------------------------- + +By default ``extra_items`` is unset. For a TypedDict type that specifies +``extra_items``, during construction, the value type of each unknown item +is expected to be non-required and assignable to the ``extra_items`` argument. +For example:: + + class Movie(TypedDict, extra_items=bool): + name: str + + a: Movie = {"name": "Blade Runner", "novel_adaptation": True} # OK + b: Movie = { + "name": "Blade Runner", + "year": 1982, # Not OK. 'int' is not assignable to 'bool' + } + +Here, ``extra_items=bool`` specifies that items other than ``'name'`` +have a value type of ``bool`` and are non-required. + +The alternative inline syntax is also supported:: + + Movie = TypedDict("Movie", {"name": str}, extra_items=bool) + +Accessing extra items is allowed. Type checkers must infer their value type from +the ``extra_items`` argument:: + + def f(movie: Movie) -> None: + reveal_type(movie["name"]) # Revealed type is 'str' + reveal_type(movie["novel_adaptation"]) # Revealed type is 'bool' + +``extra_items`` is inherited through subclassing:: + + class MovieBase(TypedDict, extra_items=ReadOnly[int | None]): + name: str + + class Movie(MovieBase): + year: int + + a: Movie = {"name": "Blade Runner", "year": None} # Not OK. 'None' is incompatible with 'int' + b: Movie = { + "name": "Blade Runner", + "year": 1982, + "other_extra_key": None, + } # OK + +Here, ``'year'`` in ``a`` is an extra key defined on ``Movie`` whose value type +is ``int``. ``'other_extra_key'`` in ``b`` is another extra key whose value type +must be assignable to the value of ``extra_items`` defined on ``MovieBase``. + +.. _typed-dict-closed: + +The ``closed`` Class Parameter +------------------------------ + +When neither ``extra_items`` nor ``closed=True`` is specified, ``closed=False`` +is assumed. The TypedDict should allow non-required extra items of value type +``ReadOnly[object]`` during inheritance or assignability checks, to +preserve the default TypedDict behavior. Extra keys included in TypedDict +object construction should still be caught, as mentioned :ref:`above `. + +When ``closed=True`` is set, no extra items are allowed. This is equivalent to +``extra_items=Never``, because there can't be a value type that is assignable to +:class:`~typing.Never`. It is a runtime error to use the ``closed`` and +``extra_items`` parameters in the same TypedDict definition. + +Similar to ``total``, only a literal ``True`` or ``False`` is supported as the +value of the ``closed`` argument. Type checkers should reject any non-literal value. + +Passing ``closed=False`` explicitly requests the default TypedDict behavior, +where arbitrary other keys may be present and subclasses may add arbitrary items. +It is a type checker error to pass ``closed=False`` if a superclass has +``closed=True`` or sets ``extra_items``. + +If ``closed`` is not provided, the behavior is inherited from the superclass. +If the superclass is TypedDict itself or the superclass does not have ``closed=True`` +or the ``extra_items`` parameter, the previous TypedDict behavior is preserved: +arbitrary extra items are allowed. If the superclass has ``closed=True``, the +child class is also closed:: + + class BaseMovie(TypedDict, closed=True): + name: str + + class MovieA(BaseMovie): # OK, still closed + pass + + class MovieB(BaseMovie, closed=True): # OK, but redundant + pass + + class MovieC(BaseMovie, closed=False): # Type checker error + pass + +As a consequence of ``closed=True`` being equivalent to ``extra_items=Never``, +the same rules that apply to ``extra_items=Never`` also apply to +``closed=True``. While they both have the same effect, ``closed=True`` is +preferred over ``extra_items=Never``. + +It is possible to use ``closed=True`` when subclassing if the ``extra_items`` +argument is a read-only type:: + + class Movie(TypedDict, extra_items=ReadOnly[str]): + pass + + class MovieClosed(Movie, closed=True): # OK + pass + + class MovieNever(Movie, extra_items=Never): # OK, but 'closed=True' is preferred + pass + +This will be further discussed in +:ref:`a later section `. + +``closed`` is also supported with the functional syntax:: + + Movie = TypedDict("Movie", {"name": str}, closed=True) + +Interaction with Totality +------------------------- + +It is an error to use ``Required[]`` or ``NotRequired[]`` with ``extra_items``. +``total=False`` and ``total=True`` have no effect on ``extra_items`` itself. + +The extra items are non-required, regardless of the `totality +`__ of the +TypedDict. :ref:`Operations ` +that are available to ``NotRequired`` items should also be available to the +extra items:: + + class Movie(TypedDict, extra_items=int): + name: str + + def f(movie: Movie) -> None: + del movie["name"] # Not OK. The value type of 'name' is 'Required[int]' + del movie["year"] # OK. The value type of 'year' is 'NotRequired[int]' + +Interaction with ``Unpack`` +--------------------------- + +For type checking purposes, ``Unpack[SomeTypedDict]`` with extra items should be +treated as its equivalent in regular parameters, and the existing rules for +function parameters still apply:: + + class MovieNoExtra(TypedDict): + name: str + + class MovieExtra(TypedDict, extra_items=int): + name: str + + def f(**kwargs: Unpack[MovieNoExtra]) -> None: ... + def g(**kwargs: Unpack[MovieExtra]) -> None: ... + + # Should be equivalent to: + def f(*, name: str) -> None: ... + def g(*, name: str, **kwargs: int) -> None: ... + + f(name="No Country for Old Men", year=2007) # Not OK. Unrecognized item + g(name="No Country for Old Men", year=2007) # OK + +Interaction with Read-only Items +-------------------------------- + +When the ``extra_items`` argument is annotated with the ``ReadOnly[]`` +:term:`type qualifier`, the extra items on the TypedDict have the +properties of read-only items. This interacts with inheritance rules specified +in :ref:`Read-only Items `. + +Notably, if the TypedDict type specifies ``extra_items`` to be read-only, +subclasses of the TypedDict type may redeclare ``extra_items``. + +Because a non-closed TypedDict type implicitly allows non-required extra items +of value type ``ReadOnly[object]``, its subclass can override the +``extra_items`` argument with more specific types. + +More details are discussed in the later sections. + +Inheritance +----------- + +``extra_items`` is inherited in a similar way as a regular ``key: value_type`` +item. As with the other keys, the `inheritance rules +`__ +and :ref:`Read-only Items ` inheritance rules apply. + +We need to reinterpret these rules to define how ``extra_items`` interacts with +them. + + * Changing a field type of a parent TypedDict class in a subclass is not allowed. + +First, it is not allowed to change the value of ``extra_items`` in a subclass +unless it is declared to be ``ReadOnly`` in the superclass:: + + class Parent(TypedDict, extra_items=int | None): + pass + + class Child(Parent, extra_items=int): # Not OK. Like any other TypedDict item, extra_items's type cannot be changed + pass + +Second, ``extra_items=T`` effectively defines the value type of any unnamed +items accepted to the TypedDict and marks them as non-required. Thus, the above +restriction applies to any additional items defined in a subclass. For each item +added in a subclass, all of the following conditions should apply: + +.. _pep728-inheritance-read-only: + +- If ``extra_items`` is read-only + + - The item can be either required or non-required + + - The item's value type is :term:`assignable` to ``T`` + +- If ``extra_items`` is not read-only + + - The item is non-required + + - The item's value type is :term:`consistent` with ``T`` + +- If ``extra_items`` is not overridden, the subclass inherits it as-is. + +For example:: + + class MovieBase(TypedDict, extra_items=int | None): + name: str + + class MovieRequiredYear(MovieBase): # Not OK. Required key 'year' is not known to 'MovieBase' + year: int | None + + class MovieNotRequiredYear(MovieBase): # Not OK. 'int | None' is not consistent with 'int' + year: NotRequired[int] + + class MovieWithYear(MovieBase): # OK + year: NotRequired[int | None] + + class BookBase(TypedDict, extra_items=ReadOnly[int | str]): + title: str + + class Book(BookBase, extra_items=str): # OK + year: int # OK + +An important side effect of the inheritance rules is that we can define a +TypedDict type that disallows additional items:: + + class MovieClosed(TypedDict, extra_items=Never): + name: str + +Here, passing the value :class:`~typing.Never` to ``extra_items`` specifies that +there can be no other keys in ``MovieFinal`` other than the known ones. +Because of its potential common use, there is a preferred alternative:: + + class MovieClosed(TypedDict, closed=True): + name: str + +where we implicitly assume that ``extra_items=Never``. + +Assignability +------------- + +Let ``S`` be the set of keys of the explicitly defined items on a TypedDict +type. If it specifies ``extra_items=T``, the TypedDict type is considered to +have an infinite set of items that all satisfy the following conditions. + +- If ``extra_items`` is read-only: + + - The key's value type is :term:`assignable` to ``T``. + + - The key is not in ``S``. + +- If ``extra_items`` is not read-only: + + - The key is non-required. + + - The key's value type is :term:`consistent` with ``T``. + + - The key is not in ``S``. + +For type checking purposes, let ``extra_items`` be a non-required pseudo-item +when checking for assignability according to rules defined in the +:ref:`Read-only Items ` section, with a new rule added in bold +text as follows: + + A TypedDict type ``B`` is :term:`assignable` to a TypedDict type + ``A`` if ``B`` is :term:`structurally ` assignable to + ``A``. This is true if and only if all of the following are satisfied: + + * **[If no key with the same name can be found in ``B``, the 'extra_items' + argument is considered the value type of the corresponding key.]** + + * For each item in ``A``, ``B`` has the corresponding key, unless the item in + ``A`` is read-only, not required, and of top value type + (``ReadOnly[NotRequired[object]]``). + + * For each item in ``A``, if ``B`` has the corresponding key, the corresponding + value type in ``B`` is assignable to the value type in ``A``. + + * For each non-read-only item in ``A``, its value type is assignable to the + corresponding value type in ``B``, and the corresponding key is not read-only + in ``B``. + + * For each required key in ``A``, the corresponding key is required in ``B``. + + * For each non-required key in ``A``, if the item is not read-only in ``A``, + the corresponding key is not required in ``B``. + +The following examples illustrate these checks in action. + +``extra_items`` puts various restrictions on additional items for assignability +checks:: + + class Movie(TypedDict, extra_items=int | None): + name: str + + class MovieDetails(TypedDict, extra_items=int | None): + name: str + year: NotRequired[int] + + details: MovieDetails = {"name": "Kill Bill Vol. 1", "year": 2003} + movie: Movie = details # Not OK. While 'int' is assignable to 'int | None', + # 'int | None' is not assignable to 'int' + + class MovieWithYear(TypedDict, extra_items=int | None): + name: str + year: int | None + + details: MovieWithYear = {"name": "Kill Bill Vol. 1", "year": 2003} + movie: Movie = details # Not OK. 'year' is not required in 'Movie', + # but it is required in 'MovieWithYear' + +where ``MovieWithYear`` (B) is not assignable to ``Movie`` (A) +according to this rule: + + * For each non-required key in ``A``, if the item is not read-only in ``A``, + the corresponding key is not required in ``B``. + +When ``extra_items`` is specified to be read-only on a TypedDict type, it is +possible for an item to have a :term:`narrower ` type than the +``extra_items`` argument:: + + class Movie(TypedDict, extra_items=ReadOnly[str | int]): + name: str + + class MovieDetails(TypedDict, extra_items=int): + name: str + year: NotRequired[int] + + details: MovieDetails = {"name": "Kill Bill Vol. 2", "year": 2004} + movie: Movie = details # OK. 'int' is assignable to 'str | int'. + +This behaves the same way as if ``year: ReadOnly[str | int]`` is an item +explicitly defined in ``Movie``. + +``extra_items`` as a pseudo-item follows the same rules that other items have, +so when both TypedDicts types specify ``extra_items``, this check is naturally +enforced:: + + class MovieExtraInt(TypedDict, extra_items=int): + name: str + + class MovieExtraStr(TypedDict, extra_items=str): + name: str + + extra_int: MovieExtraInt = {"name": "No Country for Old Men", "year": 2007} + extra_str: MovieExtraStr = {"name": "No Country for Old Men", "description": ""} + extra_int = extra_str # Not OK. 'str' is not assignable to extra items type 'int' + extra_str = extra_int # Not OK. 'int' is not assignable to extra items type 'str' + +A non-closed TypedDict type implicitly allows non-required extra keys of value +type ``ReadOnly[object]``. Applying the assignability rules between this type +and a closed TypedDict type is allowed:: + + class MovieNotClosed(TypedDict): + name: str + + extra_int: MovieExtraInt = {"name": "No Country for Old Men", "year": 2007} + not_closed: MovieNotClosed = {"name": "No Country for Old Men"} + extra_int = not_closed # Not OK. + # 'extra_items=ReadOnly[object]' implicitly on 'MovieNotClosed' + # is not assignable to with 'extra_items=int' + not_closed = extra_int # OK + +Interaction with Constructors +----------------------------- + +TypedDicts that allow extra items of type ``T`` also allow arbitrary keyword +arguments of this type when constructed by calling the class object:: + + class NonClosedMovie(TypedDict): + name: str + + NonClosedMovie(name="No Country for Old Men") # OK + NonClosedMovie(name="No Country for Old Men", year=2007) # Not OK. Unrecognized item + + class ExtraMovie(TypedDict, extra_items=int): + name: str + + ExtraMovie(name="No Country for Old Men") # OK + ExtraMovie(name="No Country for Old Men", year=2007) # OK + ExtraMovie( + name="No Country for Old Men", + language="English", + ) # Not OK. Wrong type for extra item 'language' + + # This implies 'extra_items=Never', + # so extra keyword arguments would produce an error + class ClosedMovie(TypedDict, closed=True): + name: str + + ClosedMovie(name="No Country for Old Men") # OK + ClosedMovie( + name="No Country for Old Men", + year=2007, + ) # Not OK. Extra items not allowed + +Supported and Unsupported Operations +------------------------------------ + +This statement from :ref:`above ` still holds true. + + Operations with arbitrary str keys (instead of string literals or other + expressions with known string values) should generally be rejected. + +Operations that already apply to ``NotRequired`` items should generally also +apply to extra items, following the same rationale from :ref:`above `: + + The exact type checking rules are up to each type checker to decide. In some + cases potentially unsafe operations may be accepted if the alternative is to + generate false positive errors for idiomatic code. + +Some operations, including indexed accesses and assignments with arbitrary str keys, +may be allowed due to the TypedDict being :term:`assignable` to +``Mapping[str, VT]`` or ``dict[str, VT]``. The two following sections will expand +on that. + +Interaction with Mapping[str, VT] +--------------------------------- + +A TypedDict type is :term:`assignable` to a type of the form ``Mapping[str, VT]`` +when all value types of the items in the TypedDict +are assignable to ``VT``. For the purpose of this rule, a +TypedDict that does not have ``extra_items=`` or ``closed=`` set is considered +to have an item with a value of type ``ReadOnly[object]``. This extends the +general rule for :ref:`TypedDict assignability `. + +For example:: + + class MovieExtraStr(TypedDict, extra_items=str): + name: str + + extra_str: MovieExtraStr = {"name": "Blade Runner", "summary": ""} + str_mapping: Mapping[str, str] = extra_str # OK + + class MovieExtraInt(TypedDict, extra_items=int): + name: str + + extra_int: MovieExtraInt = {"name": "Blade Runner", "year": 1982} + int_mapping: Mapping[str, int] = extra_int # Not OK. 'int | str' is not assignable with 'int' + int_str_mapping: Mapping[str, int | str] = extra_int # OK + +Type checkers should infer the precise signatures of ``values()`` and ``items()`` +on such TypedDict types:: + + def foo(movie: MovieExtraInt) -> None: + reveal_type(movie.items()) # Revealed type is 'dict_items[str, str | int]' + reveal_type(movie.values()) # Revealed type is 'dict_values[str, str | int]' + +By extension of this assignability rule, type checkers may allow indexed accesses +with arbitrary str keys when ``extra_items`` or ``closed=True`` is specified. +For example:: + + def bar(movie: MovieExtraInt, key: str) -> None: + reveal_type(movie[key]) # Revealed type is 'str | int' + +.. _pep728-type-narrowing: + +Defining the type narrowing behavior for TypedDict is out-of-scope for this spec. +This leaves flexibility for a type checker to be more/less restrictive about +indexed accesses with arbitrary str keys. For example, a type checker may opt +for more restriction by requiring an explicit ``'x' in d`` check. + +Interaction with dict[str, VT] +------------------------------ + +Because the presence of ``extra_items`` on a closed TypedDict type +prohibits additional required keys in its :term:`structural` +:term:`subtypes `, we can determine if the TypedDict type and +its structural subtypes will ever have any required key during static analysis. + +The TypedDict type is :term:`assignable` to ``dict[str, VT]`` if all +items on the TypedDict type satisfy the following conditions: + +- The value type of the item is :term:`consistent` with ``VT``. + +- The item is not read-only. + +- The item is not required. + +For example:: + + class IntDict(TypedDict, extra_items=int): + pass + + class IntDictWithNum(IntDict): + num: NotRequired[int] + + def f(x: IntDict) -> None: + v: dict[str, int] = x # OK + v.clear() # OK + + not_required_num_dict: IntDictWithNum = {"num": 1, "bar": 2} + regular_dict: dict[str, int] = not_required_num_dict # OK + f(not_required_num_dict) # OK + +In this case, methods that are previously unavailable on a TypedDict are allowed, +with signatures matching ``dict[str, VT]`` +(e.g.: ``__setitem__(self, key: str, value: VT) -> None``):: + + not_required_num_dict.clear() # OK + + reveal_type(not_required_num_dict.popitem()) # OK. Revealed type is 'tuple[str, int]' + + def f(not_required_num_dict: IntDictWithNum, key: str): + not_required_num_dict[key] = 42 # OK + del not_required_num_dict[key] # OK + +:ref:`Notes on indexed accesses ` from the previous section +still apply. + +``dict[str, VT]`` is not assignable to a TypedDict type, +because such dict can be a subtype of dict:: + + class CustomDict(dict[str, int]): + pass + + def f(might_not_be_a_builtin_dict: dict[str, int]): + int_dict: IntDict = might_not_be_a_builtin_dict # Not OK + + not_a_builtin_dict = CustomDict({"num": 1}) + f(not_a_builtin_dict) From e03f30729167574c5e0c8507ecaf88ac535845df Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Aug 2025 08:26:07 -0700 Subject: [PATCH 2/5] zuban too --- .../results/zuban/typeddicts_extra_items.toml | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 conformance/results/zuban/typeddicts_extra_items.toml diff --git a/conformance/results/zuban/typeddicts_extra_items.toml b/conformance/results/zuban/typeddicts_extra_items.toml new file mode 100644 index 00000000..4dc622de --- /dev/null +++ b/conformance/results/zuban/typeddicts_extra_items.toml @@ -0,0 +1,139 @@ +conformant = "Fail" +notes = """ +Not supported. +""" +conformance_automated = "Fail" +errors_diff = """ +Line 22: Expected 1 errors +Line 181: Expected 1 errors +Line 184: Expected 1 errors +Line 206: Expected 1 errors +Line 213: Expected 1 errors +Line 233: Expected 1 errors +Line 247: Expected 1 errors +Line 248: Expected 1 errors +Line 259: Expected 1 errors +Lines 91, 92: Expected error (tag 'MovieC') +Lines 94, 95: Expected error (tag 'MovieD') +Line 11: Unexpected errors ['typeddicts_extra_items.py:11: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 14: Unexpected errors ['typeddicts_extra_items.py:14: error: Extra key "novel_adaptation" for TypedDict "Movie"'] +Line 19: Unexpected errors ['typeddicts_extra_items.py:19: error: Unexpected keyword argument "extra_items" for "TypedDict"'] +Line 29: Unexpected errors ['typeddicts_extra_items.py:29: error: Expression is of type "Any", not "bool"', 'typeddicts_extra_items.py:29: error: TypedDict "Movie" has no key "novel_adaptation"'] +Line 33: Unexpected errors ['typeddicts_extra_items.py:33: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 40: Unexpected errors ['typeddicts_extra_items.py:40: error: Extra key "other_extra_key" for TypedDict "InheritedMovie"'] +Line 55: Unexpected errors ['typeddicts_extra_items.py:55: error: Unexpected keyword argument "closed" for TypedDict'] +Line 64: Unexpected errors ['typeddicts_extra_items.py:64: error: Unexpected keyword argument "closed" for TypedDict'] +Line 70: Unexpected errors ['typeddicts_extra_items.py:70: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 82: Unexpected errors ['typeddicts_extra_items.py:82: error: Unexpected keyword argument "closed" for TypedDict'] +Line 88: Unexpected errors ['typeddicts_extra_items.py:88: error: Unexpected keyword argument "closed" for TypedDict'] +Line 100: Unexpected errors ['typeddicts_extra_items.py:100: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 103: Unexpected errors ['typeddicts_extra_items.py:103: error: Unexpected keyword argument "closed" for TypedDict'] +Line 106: Unexpected errors ['typeddicts_extra_items.py:106: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 121: Unexpected errors ['typeddicts_extra_items.py:121: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 126: Unexpected errors ['typeddicts_extra_items.py:126: error: TypedDict "MovieEI" has no key "year"'] +Line 134: Unexpected errors ['typeddicts_extra_items.py:134: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 141: Unexpected errors ['typeddicts_extra_items.py:141: error: Unexpected keyword argument "year" for "unpack_extra"'] +Line 146: Unexpected errors ['typeddicts_extra_items.py:146: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 149: Unexpected errors ['typeddicts_extra_items.py:149: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 152: Unexpected errors ['typeddicts_extra_items.py:152: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 162: Unexpected errors ['typeddicts_extra_items.py:162: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 168: Unexpected errors ['typeddicts_extra_items.py:168: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 178: Unexpected errors ['typeddicts_extra_items.py:178: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 201: Unexpected errors ['typeddicts_extra_items.py:201: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 208: Unexpected errors ['typeddicts_extra_items.py:208: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 219: Unexpected errors ['typeddicts_extra_items.py:219: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 222: Unexpected errors ['typeddicts_extra_items.py:222: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 226: Unexpected errors ['typeddicts_extra_items.py:226: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 239: Unexpected errors ['typeddicts_extra_items.py:239: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 242: Unexpected errors ['typeddicts_extra_items.py:242: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 245: Unexpected errors ['typeddicts_extra_items.py:245: error: Extra key "year" for TypedDict "MovieExtraInt"'] +Line 246: Unexpected errors ['typeddicts_extra_items.py:246: error: Extra key "description" for TypedDict "MovieExtraStr"'] +Line 257: Unexpected errors ['typeddicts_extra_items.py:257: error: Extra key "year" for TypedDict "MovieExtraInt"'] +Line 271: Unexpected errors ['typeddicts_extra_items.py:271: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 275: Unexpected errors ['typeddicts_extra_items.py:275: error: Extra key "year" for TypedDict "ExtraMovie"'] +Line 280: Unexpected errors ['typeddicts_extra_items.py:280: error: Unexpected keyword argument "closed" for TypedDict'] +Line 290: Unexpected errors ['typeddicts_extra_items.py:290: error: Extra key "summary" for TypedDict "MovieExtraStr"'] +Line 291: Unexpected errors ['typeddicts_extra_items.py:291: error: Incompatible types in assignment (expression has type "MovieExtraStr", variable has type "Mapping[str, str]")'] +Line 293: Unexpected errors ['typeddicts_extra_items.py:293: error: Extra key "year" for TypedDict "MovieExtraInt"'] +Line 295: Unexpected errors ['typeddicts_extra_items.py:295: error: Incompatible types in assignment (expression has type "MovieExtraInt", variable has type "Mapping[str, int | str]")'] +Line 301: Unexpected errors ['typeddicts_extra_items.py:301: error: Expression is of type "list[tuple[str, object]]", not "list[tuple[str, int | str]]"'] +Line 302: Unexpected errors ['typeddicts_extra_items.py:302: error: Expression is of type "list[object]", not "list[int | str]"'] +Line 310: Unexpected errors ['typeddicts_extra_items.py:310: error: Unexpected keyword argument "extra_items" for TypedDict'] +Line 317: Unexpected errors ['typeddicts_extra_items.py:317: error: Incompatible types in assignment (expression has type "IntDict", variable has type "dict[str, int]")'] +Line 320: Unexpected errors ['typeddicts_extra_items.py:320: error: Extra key "bar" for TypedDict "IntDictWithNum"'] +Line 321: Unexpected errors ['typeddicts_extra_items.py:321: error: Incompatible types in assignment (expression has type "IntDictWithNum", variable has type "dict[str, int]")'] +Line 328: Unexpected errors ['typeddicts_extra_items.py:328: error: "IntDictWithNum" has no attribute "clear"'] +Line 330: Unexpected errors ['typeddicts_extra_items.py:330: error: Expression is of type "Any", not "tuple[str, int]"', 'typeddicts_extra_items.py:330: error: "IntDictWithNum" has no attribute "popitem"'] +Line 333: Unexpected errors ['typeddicts_extra_items.py:333: error: TypedDict key must be a string literal; expected one of ("num")'] +Line 334: Unexpected errors ['typeddicts_extra_items.py:334: error: Expected TypedDict key to be string literal'] +""" +output = """ +typeddicts_extra_items.py:11: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:14: error: Extra key "novel_adaptation" for TypedDict "Movie" +typeddicts_extra_items.py:15: error: Extra key "year" for TypedDict "Movie" +typeddicts_extra_items.py:19: error: Unexpected keyword argument "extra_items" for "TypedDict" +typeddicts_extra_items.py:29: error: Expression is of type "Any", not "bool" +typeddicts_extra_items.py:29: error: TypedDict "Movie" has no key "novel_adaptation" +typeddicts_extra_items.py:33: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:39: error: Incompatible types (expression has type "None", TypedDict item "year" has type "int") +typeddicts_extra_items.py:40: error: Extra key "other_extra_key" for TypedDict "InheritedMovie" +typeddicts_extra_items.py:49: error: Unexpected keyword argument "closed" for TypedDict +typeddicts_extra_items.py:55: error: Unexpected keyword argument "closed" for TypedDict +typeddicts_extra_items.py:64: error: Unexpected keyword argument "closed" for TypedDict +typeddicts_extra_items.py:67: error: Unexpected keyword argument "closed" for TypedDict +typeddicts_extra_items.py:70: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:73: error: Unexpected keyword argument "closed" for TypedDict +typeddicts_extra_items.py:82: error: Unexpected keyword argument "closed" for TypedDict +typeddicts_extra_items.py:88: error: Unexpected keyword argument "closed" for TypedDict +typeddicts_extra_items.py:100: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:103: error: Unexpected keyword argument "closed" for TypedDict +typeddicts_extra_items.py:106: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:111: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:114: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:121: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:125: error: Key "name" of TypedDict "MovieEI" cannot be deleted +typeddicts_extra_items.py:126: error: TypedDict "MovieEI" has no key "year" +typeddicts_extra_items.py:134: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:140: error: Unexpected keyword argument "year" for "unpack_no_extra" +typeddicts_extra_items.py:141: error: Unexpected keyword argument "year" for "unpack_extra" +typeddicts_extra_items.py:146: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:149: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:152: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:162: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:168: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:171: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:178: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:201: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:208: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:219: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:222: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:226: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:239: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:242: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:245: error: Extra key "year" for TypedDict "MovieExtraInt" +typeddicts_extra_items.py:246: error: Extra key "description" for TypedDict "MovieExtraStr" +typeddicts_extra_items.py:257: error: Extra key "year" for TypedDict "MovieExtraInt" +typeddicts_extra_items.py:269: error: Extra key "year" for TypedDict "NonClosedMovie" +typeddicts_extra_items.py:271: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:275: error: Extra key "year" for TypedDict "ExtraMovie" +typeddicts_extra_items.py:276: error: Extra key "language" for TypedDict "ExtraMovie" +typeddicts_extra_items.py:280: error: Unexpected keyword argument "closed" for TypedDict +typeddicts_extra_items.py:284: error: Extra key "year" for TypedDict "ClosedMovie" +typeddicts_extra_items.py:290: error: Extra key "summary" for TypedDict "MovieExtraStr" +typeddicts_extra_items.py:291: error: Incompatible types in assignment (expression has type "MovieExtraStr", variable has type "Mapping[str, str]") +typeddicts_extra_items.py:293: error: Extra key "year" for TypedDict "MovieExtraInt" +typeddicts_extra_items.py:294: error: Incompatible types in assignment (expression has type "MovieExtraInt", variable has type "Mapping[str, int]") +typeddicts_extra_items.py:295: error: Incompatible types in assignment (expression has type "MovieExtraInt", variable has type "Mapping[str, int | str]") +typeddicts_extra_items.py:301: error: Expression is of type "list[tuple[str, object]]", not "list[tuple[str, int | str]]" +typeddicts_extra_items.py:302: error: Expression is of type "list[object]", not "list[int | str]" +typeddicts_extra_items.py:310: error: Unexpected keyword argument "extra_items" for TypedDict +typeddicts_extra_items.py:317: error: Incompatible types in assignment (expression has type "IntDict", variable has type "dict[str, int]") +typeddicts_extra_items.py:320: error: Extra key "bar" for TypedDict "IntDictWithNum" +typeddicts_extra_items.py:321: error: Incompatible types in assignment (expression has type "IntDictWithNum", variable has type "dict[str, int]") +typeddicts_extra_items.py:328: error: "IntDictWithNum" has no attribute "clear" +typeddicts_extra_items.py:330: error: Expression is of type "Any", not "tuple[str, int]" +typeddicts_extra_items.py:330: error: "IntDictWithNum" has no attribute "popitem" +typeddicts_extra_items.py:333: error: TypedDict key must be a string literal; expected one of ("num") +typeddicts_extra_items.py:334: error: Expected TypedDict key to be string literal +typeddicts_extra_items.py:343: error: Incompatible types in assignment (expression has type "dict[str, int]", variable has type "IntDict") +""" From 74cc55d64e049d8f90be622326365c3c6c8795f9 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Aug 2025 09:04:47 -0700 Subject: [PATCH 3/5] Apply suggestions from code review Co-authored-by: Brian Schubert --- conformance/results/mypy/typeddicts_extra_items.toml | 2 +- conformance/results/pyre/typeddicts_extra_items.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/conformance/results/mypy/typeddicts_extra_items.toml b/conformance/results/mypy/typeddicts_extra_items.toml index ef43d71c..9f1bde56 100644 --- a/conformance/results/mypy/typeddicts_extra_items.toml +++ b/conformance/results/mypy/typeddicts_extra_items.toml @@ -1,4 +1,4 @@ -conformant = "Fail" +conformant = "Unsupported" notes = """ Not supported. """ diff --git a/conformance/results/pyre/typeddicts_extra_items.toml b/conformance/results/pyre/typeddicts_extra_items.toml index 79a77d13..aa91c7f4 100644 --- a/conformance/results/pyre/typeddicts_extra_items.toml +++ b/conformance/results/pyre/typeddicts_extra_items.toml @@ -1,4 +1,4 @@ -conformant = "Fail" +conformant = "Unsupported" notes = """ Not supported. """ From 61a95d8b96fb6e611f4cddd1c1c64a77cdb9720e Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sat, 16 Aug 2025 09:06:40 -0700 Subject: [PATCH 4/5] regen on 3.12 --- conformance/results/mypy/version.toml | 2 +- conformance/results/pyre/version.toml | 2 +- conformance/results/pyright/version.toml | 2 +- conformance/results/results.html | 14 ++++++++++---- .../results/zuban/typeddicts_extra_items.toml | 2 +- conformance/results/zuban/version.toml | 2 +- 6 files changed, 15 insertions(+), 9 deletions(-) diff --git a/conformance/results/mypy/version.toml b/conformance/results/mypy/version.toml index 731bf6fb..acf5f323 100644 --- a/conformance/results/mypy/version.toml +++ b/conformance/results/mypy/version.toml @@ -1,2 +1,2 @@ version = "mypy 1.17.1" -test_duration = 2.96 +test_duration = 2.02 diff --git a/conformance/results/pyre/version.toml b/conformance/results/pyre/version.toml index 95cf3faa..a25bf06d 100644 --- a/conformance/results/pyre/version.toml +++ b/conformance/results/pyre/version.toml @@ -1,2 +1,2 @@ version = "pyre 0.9.25" -test_duration = 5.93 +test_duration = 10.22 diff --git a/conformance/results/pyright/version.toml b/conformance/results/pyright/version.toml index 2944d808..107640c1 100644 --- a/conformance/results/pyright/version.toml +++ b/conformance/results/pyright/version.toml @@ -1,2 +1,2 @@ version = "pyright 1.1.403" -test_duration = 3.15 +test_duration = 1.31 diff --git a/conformance/results/results.html b/conformance/results/results.html index 6203268b..963892b8 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -159,16 +159,16 @@

Python Type System Conformance Test Results

+ + + + + + diff --git a/conformance/results/zuban/typeddicts_extra_items.toml b/conformance/results/zuban/typeddicts_extra_items.toml index 4dc622de..0cc9199f 100644 --- a/conformance/results/zuban/typeddicts_extra_items.toml +++ b/conformance/results/zuban/typeddicts_extra_items.toml @@ -1,4 +1,4 @@ -conformant = "Fail" +conformant = "Unsupported" notes = """ Not supported. """ diff --git a/conformance/results/zuban/version.toml b/conformance/results/zuban/version.toml index 120befcc..d3d2104d 100644 --- a/conformance/results/zuban/version.toml +++ b/conformance/results/zuban/version.toml @@ -1,2 +1,2 @@ version = "zuban 0.0.19" -test_duration = 0.15 +test_duration = 0.32 From cd288d47a3117a80fec26aabfc2a89cdc166bd40 Mon Sep 17 00:00:00 2001 From: Jelle Zijlstra Date: Sun, 17 Aug 2025 06:36:42 -0700 Subject: [PATCH 5/5] Update conformance/tests/typeddicts_extra_items.py Co-authored-by: Rob Hand <146272+sinon@users.noreply.github.com> --- conformance/tests/typeddicts_extra_items.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conformance/tests/typeddicts_extra_items.py b/conformance/tests/typeddicts_extra_items.py index 040d543c..b0a797ee 100644 --- a/conformance/tests/typeddicts_extra_items.py +++ b/conformance/tests/typeddicts_extra_items.py @@ -122,7 +122,7 @@ class MovieEI(TypedDict, extra_items=int): name: str def del_items(movie: MovieEI) -> None: - del movie["name"] # E: The value type of 'name' is 'Required[int]' + del movie["name"] # E: The value type of 'name' is 'Required[str]' del movie["year"] # OK: The value type of 'year' is 'NotRequired[int]' # > For type checking purposes, ``Unpack[SomeTypedDict]`` with extra items should be
 
mypy 1.17.1
-
3.0sec
+
2.0sec
pyright 1.1.403
-
3.1sec
+
1.3sec
pyre 0.9.25
-
5.9sec
+
10.2sec
zuban 0.0.19
-
0.15sec
+
0.32sec
@@ -813,6 +813,12 @@

Python Type System Conformance Test Results

Partial

Does not reject methods within TypedDict class.

Does not report when metaclass is provided.

Does not report when other keyword argument is provided.

Does not support generic TypedDict class.

Pass
     typeddicts_extra_items
Unsupported

Not supported.

Pass
Unsupported

Not supported.

Unsupported

Not supported.

     typeddicts_final Pass Pass