Skip to content

Commit

Permalink
Add error code for name mistaches in named tuples and TypedDicts (#9811)
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
JukkaL committed Dec 16, 2020
1 parent 05d9fb1 commit 990b6a6
Show file tree
Hide file tree
Showing 5 changed files with 33 additions and 4 deletions.
12 changes: 12 additions & 0 deletions docs/source/error_code_list.rst
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,18 @@ You can also use ``None``:
def __exit__(self, exc, value, tb) -> None: # Also OK
print('exit')
Check that naming is consistent [name-match]
--------------------------------------------

The definition of a named tuple or a TypedDict must be named
consistently when using the call-based syntax. Example:

.. code-block:: python
from typing import NamedTuple
# Error: First argument to namedtuple() should be 'Point2D', not 'Point'
Point2D = NamedTuple("Point", [("x", int), ("y", int)])
Report syntax errors [syntax]
-----------------------------
Expand Down
3 changes: 3 additions & 0 deletions mypy/errorcodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,9 @@ def __str__(self) -> str:
"Warn about redundant expressions",
'General',
default_enabled=False) # type: Final
NAME_MATCH = ErrorCode(
'name-match', "Check that type definition has consistent naming", 'General') # type: Final


# Syntax errors are often blocking.
SYNTAX = ErrorCode(
Expand Down
2 changes: 1 addition & 1 deletion mypy/semanal.py
Original file line number Diff line number Diff line change
Expand Up @@ -2205,7 +2205,7 @@ def analyze_namedtuple_assign(self, s: AssignmentStmt) -> bool:
return False
if internal_name != name:
self.fail("First argument to namedtuple() should be '{}', not '{}'".format(
name, internal_name), s.rvalue)
name, internal_name), s.rvalue, code=codes.NAME_MATCH)
return True
# Yes, it's a valid namedtuple, but defer if it is not ready.
if not info:
Expand Down
8 changes: 5 additions & 3 deletions mypy/semanal_typeddict.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
from mypy.options import Options
from mypy.typeanal import check_for_explicit_any, has_any_from_unimported_type
from mypy.messages import MessageBuilder
from mypy.errorcodes import ErrorCode
from mypy import errorcodes as codes

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

def fail(self, msg: str, ctx: Context) -> None:
self.api.fail(msg, ctx)
def fail(self, msg: str, ctx: Context, *, code: Optional[ErrorCode] = None) -> None:
self.api.fail(msg, ctx, code=code)
12 changes: 12 additions & 0 deletions test-data/unit/check-errorcodes.test
Original file line number Diff line number Diff line change
Expand Up @@ -786,3 +786,15 @@ i = [x for x in lst if True] # E: If condition in comprehension is a
j = [x for x in lst if False] # E: If condition in comprehension is always false [redundant-expr]
k = [x for x in lst if isinstance(x, int) or foo()] # E: If condition in comprehension is always true [redundant-expr]
[builtins fixtures/isinstancelist.pyi]

[case testNamedTupleNameMismatch]
from typing import NamedTuple

Foo = NamedTuple("Bar", []) # E: First argument to namedtuple() should be 'Foo', not 'Bar' [name-match]
[builtins fixtures/tuple.pyi]

[case testTypedDictNameMismatch]
from typing_extensions import TypedDict

Foo = TypedDict("Bar", {}) # E: First argument 'Bar' to TypedDict() does not match variable name 'Foo' [name-match]
[builtins fixtures/dict.pyi]

0 comments on commit 990b6a6

Please sign in to comment.