Skip to content

Commit

Permalink
Do not allow class-level keywords for NamedTuple (#16526)
Browse files Browse the repository at this point in the history
Refs #16521
  • Loading branch information
sobolevn authored Nov 25, 2023
1 parent 5b1a231 commit 50d6d0b
Show file tree
Hide file tree
Showing 4 changed files with 28 additions and 3 deletions.
2 changes: 1 addition & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -775,7 +775,7 @@ def file_context(
self.globals = file_node.names
self.tvar_scope = TypeVarLikeScope()

self.named_tuple_analyzer = NamedTupleAnalyzer(options, self)
self.named_tuple_analyzer = NamedTupleAnalyzer(options, self, self.msg)
self.typed_dict_analyzer = TypedDictAnalyzer(options, self, self.msg)
self.enum_call_analyzer = EnumCallAnalyzer(options, self)
self.newtype_analyzer = NewTypeAnalyzer(options, self, self.msg)
Expand Down
10 changes: 9 additions & 1 deletion mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from typing import Final, Iterator, List, Mapping, cast

from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
from mypy.messages import MessageBuilder
from mypy.nodes import (
ARG_NAMED_OPT,
ARG_OPT,
Expand Down Expand Up @@ -91,9 +92,12 @@


class NamedTupleAnalyzer:
def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None:
def __init__(
self, options: Options, api: SemanticAnalyzerInterface, msg: MessageBuilder
) -> None:
self.options = options
self.api = api
self.msg = msg

def analyze_namedtuple_classdef(
self, defn: ClassDef, is_stub_file: bool, is_func_scope: bool
Expand Down Expand Up @@ -204,6 +208,10 @@ def check_namedtuple_classdef(
)
else:
default_items[name] = stmt.rvalue
if defn.keywords:
for_function = ' for "__init_subclass__" of "NamedTuple"'
for key in defn.keywords:
self.msg.unexpected_keyword_argument_for_function(for_function, key, defn)
return items, types, default_items, statements

def check_namedtuple(
Expand Down
2 changes: 1 addition & 1 deletion mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ def analyze_typeddict_classdef_fields(
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():
for key in defn.keywords:
if key == "total":
continue
self.msg.unexpected_keyword_argument_for_function(for_function, key, defn)
Expand Down
17 changes: 17 additions & 0 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -1354,3 +1354,20 @@ class Test:
self.item: self.Item # E: Name "self.Item" is not defined
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-namedtuple.pyi]

[case testNoClassKeywordsForNamedTuple]
from typing import NamedTuple
class Test1(NamedTuple, x=1, y=2): # E: Unexpected keyword argument "x" for "__init_subclass__" of "NamedTuple" \
# E: Unexpected keyword argument "y" for "__init_subclass__" of "NamedTuple"
...

class Meta(type): ...

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

# Technically this would work, but it is just easier for the implementation:
class Test3(NamedTuple, metaclass=type): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "NamedTuple"
...
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-namedtuple.pyi]

0 comments on commit 50d6d0b

Please sign in to comment.