Skip to content

Commit

Permalink
Prohibit bytes literals in named tuples (#13271)
Browse files Browse the repository at this point in the history
Co-authored-by: Ivan Levkivskyi <ilevkivskyi@dropbox.com>
  • Loading branch information
ilevkivskyi and Ivan Levkivskyi authored Jul 28, 2022
1 parent b0c2a72 commit 35ab579
Show file tree
Hide file tree
Showing 2 changed files with 22 additions and 8 deletions.
15 changes: 7 additions & 8 deletions mypy/semanal_namedtuple.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"""

from contextlib import contextmanager
from typing import Dict, Iterator, List, Mapping, Optional, Tuple, Union, cast
from typing import Dict, Iterator, List, Mapping, Optional, Tuple, cast

from typing_extensions import Final

Expand All @@ -17,7 +17,6 @@
Argument,
AssignmentStmt,
Block,
BytesExpr,
CallExpr,
ClassDef,
Context,
Expand Down Expand Up @@ -338,13 +337,13 @@ def parse_namedtuple_args(
if call.arg_kinds[:2] != [ARG_POS, ARG_POS]:
self.fail(f'Unexpected arguments to "{type_name}()"', call)
return None
if not isinstance(args[0], (StrExpr, BytesExpr)):
if not isinstance(args[0], StrExpr):
self.fail(f'"{type_name}()" expects a string literal as the first argument', call)
return None
typename = cast(Union[StrExpr, BytesExpr], call.args[0]).value
typename = cast(StrExpr, call.args[0]).value
types: List[Type] = []
if not isinstance(args[1], (ListExpr, TupleExpr)):
if fullname == "collections.namedtuple" and isinstance(args[1], (StrExpr, BytesExpr)):
if fullname == "collections.namedtuple" and isinstance(args[1], StrExpr):
str_expr = args[1]
items = str_expr.value.replace(",", " ").split()
else:
Expand All @@ -359,10 +358,10 @@ def parse_namedtuple_args(
listexpr = args[1]
if fullname == "collections.namedtuple":
# The fields argument contains just names, with implicit Any types.
if any(not isinstance(item, (StrExpr, BytesExpr)) for item in listexpr.items):
if any(not isinstance(item, StrExpr) for item in listexpr.items):
self.fail('String literal expected as "namedtuple()" item', call)
return None
items = [cast(Union[StrExpr, BytesExpr], item).value for item in listexpr.items]
items = [cast(StrExpr, item).value for item in listexpr.items]
else:
# The fields argument contains (name, type) tuples.
result = self.parse_namedtuple_fields_with_types(listexpr.items, call)
Expand Down Expand Up @@ -401,7 +400,7 @@ def parse_namedtuple_fields_with_types(
self.fail('Invalid "NamedTuple()" field definition', item)
return None
name, type_node = item.items
if isinstance(name, (StrExpr, BytesExpr)):
if isinstance(name, StrExpr):
items.append(name.value)
else:
self.fail('Invalid "NamedTuple()" field name', item)
Expand Down
15 changes: 15 additions & 0 deletions test-data/unit/check-namedtuple.test
Original file line number Diff line number Diff line change
Expand Up @@ -1147,3 +1147,18 @@ reveal_type(o.__match_args__) # E: "One" has no attribute "__match_args__" \
# N: Revealed type is "Any"
[builtins fixtures/tuple.pyi]
[typing fixtures/typing-namedtuple.pyi]

[case testNamedTupleNoBytes]
from collections import namedtuple
from typing import NamedTuple

NT1 = namedtuple('NT1', b'x y z') # E: List or tuple literal expected as the second argument to "namedtuple()"
NT2 = namedtuple(b'NT2', 'x y z') # E: "namedtuple()" expects a string literal as the first argument \
# E: Argument 1 to "namedtuple" has incompatible type "bytes"; expected "str"
NT3 = namedtuple('NT3', [b'x', 'y']) # E: String literal expected as "namedtuple()" item

NT4 = NamedTuple('NT4', [('x', int), (b'y', int)]) # E: Invalid "NamedTuple()" field name
NT5 = NamedTuple(b'NT5', [('x', int), ('y', int)]) # E: "NamedTuple()" expects a string literal as the first argument

[builtins fixtures/tuple.pyi]
[typing fixtures/typing-namedtuple.pyi]

0 comments on commit 35ab579

Please sign in to comment.