Skip to content

Commit 990b6a6

Browse files
authored
Add error code for name mistaches in named tuples and TypedDicts (#9811)
This gives a new error code for code like this: ``` # Foo and Bar don't match Foo = NamedTuple("Bar", [...]) ``` Since these errors generally don't cause runtime issues, some users may want to disable these errors. It's now easy to do using the error code name-match.
1 parent 05d9fb1 commit 990b6a6

File tree

5 files changed

+33
-4
lines changed

5 files changed

+33
-4
lines changed

docs/source/error_code_list.rst

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -648,6 +648,18 @@ You can also use ``None``:
648648
def __exit__(self, exc, value, tb) -> None: # Also OK
649649
print('exit')
650650
651+
Check that naming is consistent [name-match]
652+
--------------------------------------------
653+
654+
The definition of a named tuple or a TypedDict must be named
655+
consistently when using the call-based syntax. Example:
656+
657+
.. code-block:: python
658+
659+
from typing import NamedTuple
660+
661+
# Error: First argument to namedtuple() should be 'Point2D', not 'Point'
662+
Point2D = NamedTuple("Point", [("x", int), ("y", int)])
651663
652664
Report syntax errors [syntax]
653665
-----------------------------

mypy/errorcodes.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,9 @@ def __str__(self) -> str:
117117
"Warn about redundant expressions",
118118
'General',
119119
default_enabled=False) # type: Final
120+
NAME_MATCH = ErrorCode(
121+
'name-match', "Check that type definition has consistent naming", 'General') # type: Final
122+
120123

121124
# Syntax errors are often blocking.
122125
SYNTAX = ErrorCode(

mypy/semanal.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2205,7 +2205,7 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
22052205
return False
22062206
if internal_name != name:
22072207
self.fail("First argument to namedtuple() should be '{}', not '{}'".format(
2208-
name, internal_name), s.rvalue)
2208+
name, internal_name), s.rvalue, code=codes.NAME_MATCH)
22092209
return True
22102210
# Yes, it's a valid namedtuple, but defer if it is not ready.
22112211
if not info:

mypy/semanal_typeddict.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from mypy.options import Options
1616
from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type
1717
from mypy.messages import MessageBuilder
18+
from mypy.errorcodes import ErrorCode
19+
from mypy import errorcodes as codes
1820

1921
TPDICT_CLASS_ERROR = ('Invalid statement in TypedDict definition; '
2022
'expected "field_name: field_type"') # type: Final
@@ -199,7 +201,7 @@ def check_typeddict(self,
199201
if var_name is not None and name != var_name:
200202
self.fail(
201203
"First argument '{}' to TypedDict() does not match variable name '{}'".format(
202-
name, var_name), node)
204+
name, var_name), node, code=codes.NAME_MATCH)
203205
if name != var_name or is_func_scope:
204206
# Give it a unique name derived from the line number.
205207
name += '@' + str(call.line)
@@ -320,5 +322,5 @@ def is_typeddict(self, expr: Expression) -> bool:
320322
return (isinstance(expr, RefExpr) and isinstance(expr.node, TypeInfo) and
321323
expr.node.typeddict_type is not None)
322324

323-
def fail(self, msg: str, ctx: Context) -> None:
324-
self.api.fail(msg, ctx)
325+
def fail(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None:
326+
self.api.fail(msg, ctx, code=code)

test-data/unit/check-errorcodes.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -786,3 +786,15 @@ i = [x for x in lst if True] # E: If condition in comprehension is a
786786
j = [x for x in lst if False] # E: If condition in comprehension is always false [redundant-expr]
787787
k = [x for x in lst if isinstance(x, int) or foo()] # E: If condition in comprehension is always true [redundant-expr]
788788
[builtins fixtures/isinstancelist.pyi]
789+
790+
[case testNamedTupleNameMismatch]
791+
from typing import NamedTuple
792+
793+
Foo = NamedTuple("Bar", []) # E: First argument to namedtuple() should be 'Foo', not 'Bar' [name-match]
794+
[builtins fixtures/tuple.pyi]
795+
796+
[case testTypedDictNameMismatch]
797+
from typing_extensions import TypedDict
798+
799+
Foo = TypedDict("Bar", {}) # E: First argument 'Bar' to TypedDict() does not match variable name 'Foo' [name-match]
800+
[builtins fixtures/dict.pyi]

0 commit comments

Comments
 (0)