Skip to content

Commit 35b2926

Browse files
authored
Consistently use literal-required error code for TypedDicts (#14621)
Ref #7178 This code is used for some TypedDict errors, but `misc` was used for others. I make it more consistent. Also this code looks undocumented, so I add some basic docs.
1 parent 8d93b67 commit 35b2926

File tree

3 files changed

+50
-4
lines changed

3 files changed

+50
-4
lines changed

docs/source/error_code_list.rst

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -804,6 +804,35 @@ consistently when using the call-based syntax. Example:
804804
# Error: First argument to namedtuple() should be "Point2D", not "Point"
805805
Point2D = NamedTuple("Point", [("x", int), ("y", int)])
806806
807+
Check that literal is used where expected [literal-required]
808+
------------------------------------------------------------
809+
810+
There are some places where only a (string) literal value is expected for
811+
the purposes of static type checking, for example a ``TypedDict`` key, or
812+
a ``__match_args__`` item. Providing a ``str``-valued variable in such contexts
813+
will result in an error. Note however, in many cases you can use ``Final``,
814+
or ``Literal`` variables, for example:
815+
816+
.. code-block:: python
817+
818+
from typing import Final, Literal, TypedDict
819+
820+
class Point(TypedDict):
821+
x: int
822+
y: int
823+
824+
def test(p: Point) -> None:
825+
X: Final = "x"
826+
p[X] # OK
827+
828+
Y: Literal["y"] = "y"
829+
p[Y] # OK
830+
831+
key = "x" # Inferred type of key is `str`
832+
# Error: TypedDict key must be a string literal;
833+
# expected one of ("x", "y") [literal-required]
834+
p[key]
835+
807836
Check that overloaded functions have an implementation [no-overload-impl]
808837
-------------------------------------------------------------------------
809838

mypy/checkexpr.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,11 @@ def validate_typeddict_kwargs(self, kwargs: DictExpr) -> dict[str, Expression] |
724724
literal_value = values[0]
725725
if literal_value is None:
726726
key_context = item_name_expr or item_arg
727-
self.chk.fail(message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, key_context)
727+
self.chk.fail(
728+
message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL,
729+
key_context,
730+
code=codes.LITERAL_REQ,
731+
)
728732
return None
729733
else:
730734
item_names.append(literal_value)

mypy/plugins/default.py

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from functools import partial
44
from typing import Callable
55

6+
import mypy.errorcodes as codes
67
from mypy import message_registry
78
from mypy.nodes import DictExpr, IntExpr, StrExpr, UnaryExpr
89
from mypy.plugin import (
@@ -264,7 +265,11 @@ def typed_dict_pop_callback(ctx: MethodContext) -> Type:
264265
):
265266
keys = try_getting_str_literals(ctx.args[0][0], ctx.arg_types[0][0])
266267
if keys is None:
267-
ctx.api.fail(message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context)
268+
ctx.api.fail(
269+
message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL,
270+
ctx.context,
271+
code=codes.LITERAL_REQ,
272+
)
268273
return AnyType(TypeOfAny.from_error)
269274

270275
value_types = []
@@ -319,7 +324,11 @@ def typed_dict_setdefault_callback(ctx: MethodContext) -> Type:
319324
):
320325
keys = try_getting_str_literals(ctx.args[0][0], ctx.arg_types[0][0])
321326
if keys is None:
322-
ctx.api.fail(message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context)
327+
ctx.api.fail(
328+
message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL,
329+
ctx.context,
330+
code=codes.LITERAL_REQ,
331+
)
323332
return AnyType(TypeOfAny.from_error)
324333

325334
default_type = ctx.arg_types[1][0]
@@ -357,7 +366,11 @@ def typed_dict_delitem_callback(ctx: MethodContext) -> Type:
357366
):
358367
keys = try_getting_str_literals(ctx.args[0][0], ctx.arg_types[0][0])
359368
if keys is None:
360-
ctx.api.fail(message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context)
369+
ctx.api.fail(
370+
message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL,
371+
ctx.context,
372+
code=codes.LITERAL_REQ,
373+
)
361374
return AnyType(TypeOfAny.from_error)
362375

363376
for key in keys:

0 commit comments

Comments
 (0)