Skip to content

Commit 50d6d0b

Browse files
authored
Do not allow class-level keywords for NamedTuple (#16526)
Refs #16521
1 parent 5b1a231 commit 50d6d0b

File tree

4 files changed

+28
-3
lines changed

4 files changed

+28
-3
lines changed

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -775,7 +775,7 @@ def file_context(
775775
self.globals = file_node.names
776776
self.tvar_scope = TypeVarLikeScope()
777777

778-
self.named_tuple_analyzer = NamedTupleAnalyzer(options, self)
778+
self.named_tuple_analyzer = NamedTupleAnalyzer(options, self, self.msg)
779779
self.typed_dict_analyzer = TypedDictAnalyzer(options, self, self.msg)
780780
self.enum_call_analyzer = EnumCallAnalyzer(options, self)
781781
self.newtype_analyzer = NewTypeAnalyzer(options, self, self.msg)

mypy/semanal_namedtuple.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
from typing import Final, Iterator, List, Mapping, cast
1010

1111
from mypy.exprtotype import TypeTranslationError, expr_to_unanalyzed_type
12+
from mypy.messages import MessageBuilder
1213
from mypy.nodes import (
1314
ARG_NAMED_OPT,
1415
ARG_OPT,
@@ -91,9 +92,12 @@
9192

9293

9394
class NamedTupleAnalyzer:
94-
def __init__(self, options: Options, api: SemanticAnalyzerInterface) -> None:
95+
def __init__(
96+
self, options: Options, api: SemanticAnalyzerInterface, msg: MessageBuilder
97+
) -> None:
9598
self.options = options
9699
self.api = api
100+
self.msg = msg
97101

98102
def analyze_namedtuple_classdef(
99103
self, defn: ClassDef, is_stub_file: bool, is_func_scope: bool
@@ -204,6 +208,10 @@ def check_namedtuple_classdef(
204208
)
205209
else:
206210
default_items[name] = stmt.rvalue
211+
if defn.keywords:
212+
for_function = ' for "__init_subclass__" of "NamedTuple"'
213+
for key in defn.keywords:
214+
self.msg.unexpected_keyword_argument_for_function(for_function, key, defn)
207215
return items, types, default_items, statements
208216

209217
def check_namedtuple(

mypy/semanal_typeddict.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -325,7 +325,7 @@ def analyze_typeddict_classdef_fields(
325325
total = require_bool_literal_argument(self.api, defn.keywords["total"], "total", True)
326326
if defn.keywords and defn.keywords.keys() != {"total"}:
327327
for_function = ' for "__init_subclass__" of "TypedDict"'
328-
for key in defn.keywords.keys():
328+
for key in defn.keywords:
329329
if key == "total":
330330
continue
331331
self.msg.unexpected_keyword_argument_for_function(for_function, key, defn)

test-data/unit/check-namedtuple.test

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1354,3 +1354,20 @@ class Test:
13541354
self.item: self.Item # E: Name "self.Item" is not defined
13551355
[builtins fixtures/tuple.pyi]
13561356
[typing fixtures/typing-namedtuple.pyi]
1357+
1358+
[case testNoClassKeywordsForNamedTuple]
1359+
from typing import NamedTuple
1360+
class Test1(NamedTuple, x=1, y=2): # E: Unexpected keyword argument "x" for "__init_subclass__" of "NamedTuple" \
1361+
# E: Unexpected keyword argument "y" for "__init_subclass__" of "NamedTuple"
1362+
...
1363+
1364+
class Meta(type): ...
1365+
1366+
class Test2(NamedTuple, metaclass=Meta): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "NamedTuple"
1367+
...
1368+
1369+
# Technically this would work, but it is just easier for the implementation:
1370+
class Test3(NamedTuple, metaclass=type): # E: Unexpected keyword argument "metaclass" for "__init_subclass__" of "NamedTuple"
1371+
...
1372+
[builtins fixtures/tuple.pyi]
1373+
[typing fixtures/typing-namedtuple.pyi]

0 commit comments

Comments
 (0)