diff --git a/docs/source/error_code_list.rst b/docs/source/error_code_list.rst index 4d402226a589..0388cd2165dd 100644 --- a/docs/source/error_code_list.rst +++ b/docs/source/error_code_list.rst @@ -804,6 +804,35 @@ consistently when using the call-based syntax. Example: # Error: First argument to namedtuple() should be "Point2D", not "Point" Point2D = NamedTuple("Point", [("x", int), ("y", int)]) +Check that literal is used where expected [literal-required] +------------------------------------------------------------ + +There are some places where only a (string) literal value is expected for +the purposes of static type checking, for example a ``TypedDict`` key, or +a ``__match_args__`` item. Providing a ``str``-valued variable in such contexts +will result in an error. Note however, in many cases you can use ``Final``, +or ``Literal`` variables, for example: + +.. code-block:: python + + from typing import Final, Literal, TypedDict + + class Point(TypedDict): + x: int + y: int + + def test(p: Point) -> None: + X: Final = "x" + p[X] # OK + + Y: Literal["y"] = "y" + p[Y] # OK + + key = "x" # Inferred type of key is `str` + # Error: TypedDict key must be a string literal; + # expected one of ("x", "y") [literal-required] + p[key] + Check that overloaded functions have an implementation [no-overload-impl] ------------------------------------------------------------------------- diff --git a/mypy/checkexpr.py b/mypy/checkexpr.py index 569928fbd014..9992821f1b4a 100644 --- a/mypy/checkexpr.py +++ b/mypy/checkexpr.py @@ -724,7 +724,11 @@ def validate_typeddict_kwargs(self, kwargs: DictExpr) -> dict[str, Expression] | literal_value = values[0] if literal_value is None: key_context = item_name_expr or item_arg - self.chk.fail(message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, key_context) + self.chk.fail( + message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, + key_context, + code=codes.LITERAL_REQ, + ) return None else: item_names.append(literal_value) diff --git a/mypy/plugins/default.py b/mypy/plugins/default.py index 04971868e8f4..4d6f46860939 100644 --- a/mypy/plugins/default.py +++ b/mypy/plugins/default.py @@ -3,6 +3,7 @@ from functools import partial from typing import Callable +import mypy.errorcodes as codes from mypy import message_registry from mypy.nodes import DictExpr, IntExpr, StrExpr, UnaryExpr from mypy.plugin import ( @@ -264,7 +265,11 @@ def typed_dict_pop_callback(ctx: MethodContext) -> Type: ): keys = try_getting_str_literals(ctx.args[0][0], ctx.arg_types[0][0]) if keys is None: - ctx.api.fail(message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) + ctx.api.fail( + message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, + ctx.context, + code=codes.LITERAL_REQ, + ) return AnyType(TypeOfAny.from_error) value_types = [] @@ -319,7 +324,11 @@ def typed_dict_setdefault_callback(ctx: MethodContext) -> Type: ): keys = try_getting_str_literals(ctx.args[0][0], ctx.arg_types[0][0]) if keys is None: - ctx.api.fail(message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) + ctx.api.fail( + message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, + ctx.context, + code=codes.LITERAL_REQ, + ) return AnyType(TypeOfAny.from_error) default_type = ctx.arg_types[1][0] @@ -357,7 +366,11 @@ def typed_dict_delitem_callback(ctx: MethodContext) -> Type: ): keys = try_getting_str_literals(ctx.args[0][0], ctx.arg_types[0][0]) if keys is None: - ctx.api.fail(message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, ctx.context) + ctx.api.fail( + message_registry.TYPEDDICT_KEY_MUST_BE_STRING_LITERAL, + ctx.context, + code=codes.LITERAL_REQ, + ) return AnyType(TypeOfAny.from_error) for key in keys: