Skip to content

Commit

Permalink
Do not allow TypedDict classes with extra keywords (#16438)
Browse files Browse the repository at this point in the history
  • Loading branch information
sobolevn committed Nov 18, 2023
1 parent 1cc62a2 commit 058f8fd
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 4 deletions.
15 changes: 11 additions & 4 deletions mypy/messages.py
Original file line number Diff line number Diff line change
Expand Up @@ -991,10 +991,17 @@ def maybe_note_about_special_args(self, callee: CallableType, context: Context)
context,
)

def unexpected_keyword_argument_for_function(
self, for_func: str, name: str, context: Context, *, matches: list[str] | None = None
) -> None:
msg = f'Unexpected keyword argument "{name}"' + for_func
if matches:
msg += f"; did you mean {pretty_seq(matches, 'or')}?"
self.fail(msg, context, code=codes.CALL_ARG)

def unexpected_keyword_argument(
self, callee: CallableType, name: str, arg_type: Type, context: Context
) -> None:
msg = f'Unexpected keyword argument "{name}"' + for_function(callee)
# Suggest intended keyword, look for type match else fallback on any match.
matching_type_args = []
not_matching_type_args = []
Expand All @@ -1008,9 +1015,9 @@ def unexpected_keyword_argument(
matches = best_matches(name, matching_type_args, n=3)
if not matches:
matches = best_matches(name, not_matching_type_args, n=3)
if matches:
msg += f"; did you mean {pretty_seq(matches, 'or')}?"
self.fail(msg, context, code=codes.CALL_ARG)
self.unexpected_keyword_argument_for_function(
for_function(callee), name, context, matches=matches
)
module = find_defining_module(self.modules, callee)
if module:
assert callee.definition is not None
Expand Down
6 changes: 6 additions & 0 deletions mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,12 @@ def analyze_typeddict_classdef_fields(
total: bool | None = True
if "total" in defn.keywords:
total = require_bool_literal_argument(self.api, defn.keywords["total"], "total", True)
if defn.keywords and defn.keywords.keys() != {"total"}:
for_function = ' for "__init_subclass__" of "TypedDict"'
for key in defn.keywords.keys():
if key == "total":
continue
self.msg.unexpected_keyword_argument_for_function(for_function, key, defn)
required_keys = {
field
for (field, t) in zip(fields, types)
Expand Down
30 changes: 30 additions & 0 deletions test-data/unit/check-typeddict.test
Original file line number Diff line number Diff line change
Expand Up @@ -3408,3 +3408,33 @@ B = TypedDict("B", { # E: Type of a TypedDict key becomes "Any" due to an unfol
})
[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]

[case testTypedDictWithClassLevelKeywords]
from typing import TypedDict, Generic, TypeVar

T = TypeVar('T')

class Meta(type): ...

class WithMetaKeyword(TypedDict, metaclass=Meta): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "TypedDict"
...

class GenericWithMetaKeyword(TypedDict, Generic[T], metaclass=Meta): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "TypedDict"
...

# We still don't allow this, because the implementation is much easier
# and it does not make any practical sense to do it:
class WithTypeMeta(TypedDict, metaclass=type): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "TypedDict"
...

class OtherKeywords(TypedDict, a=1, b=2, c=3, total=True): # E: Unexpected keyword argument "a" for "__init_subclass__" of "TypedDict" \
# E: Unexpected keyword argument "b" for "__init_subclass__" of "TypedDict" \
# E: Unexpected keyword argument "c" for "__init_subclass__" of "TypedDict"
...

class TotalInTheMiddle(TypedDict, a=1, total=True, b=2, c=3): # E: Unexpected keyword argument "a" for "__init_subclass__" of "TypedDict" \
# E: Unexpected keyword argument "b" for "__init_subclass__" of "TypedDict" \
# E: Unexpected keyword argument "c" for "__init_subclass__" of "TypedDict"
...
[builtins fixtures/dict.pyi]
[typing fixtures/typing-typeddict.pyi]

0 comments on commit 058f8fd

Please sign in to comment.