Skip to content

Commit

Permalink
Consistently use literal-required error code for TypedDicts (#14621)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
ilevkivskyi committed Feb 7, 2023
1 parent 8d93b67 commit 35b2926
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 4 deletions.
29 changes: 29 additions & 0 deletions docs/source/error_code_list.rst
Expand Up @@ -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]
-------------------------------------------------------------------------

Expand Down
6 changes: 5 additions & 1 deletion mypy/checkexpr.py
Expand Up @@ -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)
Expand Down
19 changes: 16 additions & 3 deletions mypy/plugins/default.py
Expand Up @@ -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 (
Expand Down Expand Up @@ -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 = []
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -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:
Expand Down

0 comments on commit 35b2926

Please sign in to comment.