Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 17 additions & 12 deletions mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -262,16 +262,17 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str

Return None if the definition didn't typecheck.
"""
type_name = 'NamedTuple' if fullname == 'typing.NamedTuple' else 'namedtuple'
# TODO: Share code with check_argument_count in checkexpr.py?
args = call.args
if len(args) < 2:
self.fail("Too few arguments for namedtuple()", call)
self.fail('Too few arguments for "{}()"'.format(type_name), call)
return None
defaults: List[Expression] = []
if len(args) > 2:
# Typed namedtuple doesn't support additional arguments.
if fullname == 'typing.NamedTuple':
self.fail("Too many arguments for NamedTuple()", call)
self.fail('Too many arguments for "NamedTuple()"', call)
return None
for i, arg_name in enumerate(call.arg_names[2:], 2):
if arg_name == 'defaults':
Expand All @@ -283,16 +284,16 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str
else:
self.fail(
"List or tuple literal expected as the defaults argument to "
"namedtuple()",
"{}()".format(type_name),
arg
)
break
if call.arg_kinds[:2] != [ARG_POS, ARG_POS]:
self.fail("Unexpected arguments to namedtuple()", call)
self.fail('Unexpected arguments to "{}()"'.format(type_name), call)
return None
if not isinstance(args[0], (StrExpr, BytesExpr, UnicodeExpr)):
self.fail(
"namedtuple() expects a string literal as the first argument", call)
'"{}()" expects a string literal as the first argument'.format(type_name), call)
return None
typename = cast(Union[StrExpr, BytesExpr, UnicodeExpr], call.args[0]).value
types: List[Type] = []
Expand All @@ -303,15 +304,19 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str
items = str_expr.value.replace(',', ' ').split()
else:
self.fail(
"List or tuple literal expected as the second argument to namedtuple()", call)
'List or tuple literal expected as the second argument to "{}()"'.format(
type_name,
),
call,
)
return None
else:
listexpr = args[1]
if fullname == 'collections.namedtuple':
# The fields argument contains just names, with implicit Any types.
if any(not isinstance(item, (StrExpr, BytesExpr, UnicodeExpr))
for item in listexpr.items):
self.fail("String literal expected as namedtuple() item", call)
self.fail('String literal expected as "namedtuple()" item', call)
return None
items = [cast(Union[StrExpr, BytesExpr, UnicodeExpr], item).value
for item in listexpr.items]
Expand All @@ -328,10 +333,10 @@ def parse_namedtuple_args(self, call: CallExpr, fullname: str
types = [AnyType(TypeOfAny.unannotated) for _ in items]
underscore = [item for item in items if item.startswith('_')]
if underscore:
self.fail("namedtuple() field names cannot start with an underscore: "
self.fail('"{}()" field names cannot start with an underscore: '.format(type_name)
+ ', '.join(underscore), call)
if len(defaults) > len(items):
self.fail("Too many defaults given in call to namedtuple()", call)
self.fail('Too many defaults given in call to "{}()"'.format(type_name), call)
defaults = defaults[:len(items)]
return items, types, defaults, typename, True

Expand All @@ -347,13 +352,13 @@ def parse_namedtuple_fields_with_types(self, nodes: List[Expression], context: C
for item in nodes:
if isinstance(item, TupleExpr):
if len(item.items) != 2:
self.fail("Invalid NamedTuple field definition", item)
self.fail('Invalid "NamedTuple()" field definition', item)
return None
name, type_node = item.items
if isinstance(name, (StrExpr, BytesExpr, UnicodeExpr)):
items.append(name.value)
else:
self.fail("Invalid NamedTuple() field name", item)
self.fail('Invalid "NamedTuple()" field name', item)
return None
try:
type = expr_to_unanalyzed_type(type_node, self.options, self.api.is_stub_file)
Expand All @@ -369,7 +374,7 @@ def parse_namedtuple_fields_with_types(self, nodes: List[Expression], context: C
return [], [], [], False
types.append(analyzed)
else:
self.fail("Tuple expected as NamedTuple() field", item)
self.fail('Tuple expected as "NamedTuple()" field', item)
return None
return items, types, [], True

Expand Down
4 changes: 2 additions & 2 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ X = namedtuple('X', ('x', 'y')) # type: ignore
[case testNamedTupleNoUnderscoreFields]
from collections import namedtuple

X = namedtuple('X', 'x, _y, _z') # E: namedtuple() field names cannot start with an underscore: _y, _z
X = namedtuple('X', 'x, _y, _z') # E: "namedtuple()" field names cannot start with an underscore: _y, _z

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you add the extra quotes? They feel unnecessary.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most of the time mypy highlights the most important code part with "", this feels more similar to other cases. For example, here's how it renders in my terminal (with / without ""):
Снимок экрана 2021-09-18 в 1 20 34

In my opinion, the first one looks and reads better.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The double quotes look reasonable to me. Previously we didn't use them probably because the () suffix makes it look like code, it seems okay to add them.

[builtins fixtures/tuple.pyi]

[case testNamedTupleAccessingAttributes]
Expand Down Expand Up @@ -162,7 +162,7 @@ X(0) # ok
X(0, 1) # ok
X(0, 1, 2) # E: Too many arguments for "X"

Y = namedtuple('Y', ['x', 'y'], defaults=(1, 2, 3)) # E: Too many defaults given in call to namedtuple()
Y = namedtuple('Y', ['x', 'y'], defaults=(1, 2, 3)) # E: Too many defaults given in call to "namedtuple()"
Z = namedtuple('Z', ['x', 'y'], defaults='not a tuple') # E: List or tuple literal expected as the defaults argument to namedtuple() # E: Argument "defaults" to "namedtuple" has incompatible type "str"; expected "Optional[Iterable[Any]]"

[builtins fixtures/list.pyi]
Expand Down
51 changes: 44 additions & 7 deletions test-data/unit/semanal-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -145,39 +145,76 @@ MypyFile:1(

[case testNamedTupleWithTooFewArguments]
from collections import namedtuple
N = namedtuple('N') # E: Too few arguments for namedtuple()
N = namedtuple('N') # E: Too few arguments for "namedtuple()"
[builtins fixtures/tuple.pyi]

[case testNamedTupleWithInvalidName]
from collections import namedtuple
N = namedtuple(1, ['x']) # E: namedtuple() expects a string literal as the first argument
N = namedtuple(1, ['x']) # E: "namedtuple()" expects a string literal as the first argument
[builtins fixtures/tuple.pyi]

[case testNamedTupleWithInvalidItems]
from collections import namedtuple
N = namedtuple('N', 1) # E: List or tuple literal expected as the second argument to namedtuple()
N = namedtuple('N', 1) # E: List or tuple literal expected as the second argument to "namedtuple()"
[builtins fixtures/tuple.pyi]

[case testNamedTupleWithInvalidItems2]
from collections import namedtuple
N = namedtuple('N', ['x', 1]) # E: String literal expected as namedtuple() item
N = namedtuple('N', ['x', 1]) # E: String literal expected as "namedtuple()" item
[builtins fixtures/tuple.pyi]

[case testNamedTupleWithUnderscoreItemName]
from collections import namedtuple
N = namedtuple('N', ['_fallback']) # E: namedtuple() field names cannot start with an underscore: _fallback
N = namedtuple('N', ['_fallback']) # E: "namedtuple()" field names cannot start with an underscore: _fallback
[builtins fixtures/tuple.pyi]

-- NOTE: The following code works at runtime but is not yet supported by mypy.
-- Keyword arguments may potentially be supported in the future.
[case testNamedTupleWithNonpositionalArgs]
from collections import namedtuple
N = namedtuple(typename='N', field_names=['x']) # E: Unexpected arguments to namedtuple()
N = namedtuple(typename='N', field_names=['x']) # E: Unexpected arguments to "namedtuple()"
[builtins fixtures/tuple.pyi]

[case testTypingNamedTupleWithTooFewArguments]
from typing import NamedTuple
N = NamedTuple('N') # E: Too few arguments for "NamedTuple()"
[builtins fixtures/tuple.pyi]

[case testTypingNamedTupleWithManyArguments]
from typing import NamedTuple
N = NamedTuple('N', [], []) # E: Too many arguments for "NamedTuple()"
[builtins fixtures/tuple.pyi]

[case testTypingNamedTupleWithInvalidName]
from typing import NamedTuple
N = NamedTuple(1, ['x']) # E: "NamedTuple()" expects a string literal as the first argument
[builtins fixtures/tuple.pyi]

[case testTypingNamedTupleWithInvalidItems]
from typing import NamedTuple
N = NamedTuple('N', 1) # E: List or tuple literal expected as the second argument to "NamedTuple()"
[builtins fixtures/tuple.pyi]

[case testTypingNamedTupleWithUnderscoreItemName]
from typing import NamedTuple
N = NamedTuple('N', [('_fallback', int)]) # E: "NamedTuple()" field names cannot start with an underscore: _fallback
[builtins fixtures/tuple.pyi]

[case testTypingNamedTupleWithUnexpectedNames]
from typing import NamedTuple
N = NamedTuple(name='N', fields=[]) # E: Unexpected arguments to "NamedTuple()"
[builtins fixtures/tuple.pyi]

-- NOTE: The following code works at runtime but is not yet supported by mypy.
-- Keyword arguments may potentially be supported in the future.
[case testNamedTupleWithNonpositionalArgs]
from collections import namedtuple
N = namedtuple(typename='N', field_names=['x']) # E: Unexpected arguments to "namedtuple()"
[builtins fixtures/tuple.pyi]

[case testInvalidNamedTupleBaseClass]
from typing import NamedTuple
class A(NamedTuple('N', [1])): pass # E: Tuple expected as NamedTuple() field
class A(NamedTuple('N', [1])): pass # E: Tuple expected as "NamedTuple()" field
class B(A): pass
[builtins fixtures/tuple.pyi]

Expand Down