8000 Implement new NamedTuple syntax (#2245) · python/mypy@ec5c476 · GitHub
[go: up one dir, main page]

Skip to content

Commit ec5c476

Browse files
ilevkivskyigvanrossum
authored andcommitted
Implement new NamedTuple syntax (#2245)
Starting from Python 3.6b1, typing.NamedTuple supports the PEP 526 syntax: from typing import NamedTuple class Employee(NamedTuple): name: str salary: int manager = Employee('John Doe', 9000) Here is the support of this feature for mypy. It works only with --fast-parser, since it is required to parse PEP 526 syntax. Roughly half of the tests are simply adapted from tests for function NamedTuple syntax, plus another half are specific for the new syntax.
1 parent d97eaaa commit ec5c476

File tree

3 files changed

+436
-2
lines changed

3 files changed

+436
-2
lines changed

mypy/semanal.py

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
ImportFrom, ImportAll, Block, LDEF, NameExpr, MemberExpr,
5454
IndexExpr, TupleExpr, ListExpr, ExpressionStmt, ReturnStmt,
5555
RaiseStmt, AssertStmt, OperatorAssignmentStmt, WhileStmt,
56-
ForStmt, BreakStmt, ContinueStmt, IfStmt, TryStmt, WithStmt, DelStmt,
56+
ForStmt, BreakStmt, ContinueStmt, IfStmt, TryStmt, WithStmt, DelStmt, PassStmt,
5757
GlobalDecl, SuperExpr, DictExpr, CallExpr, RefExpr, OpExpr, UnaryExpr,
5858
SliceExpr, CastExpr, RevealTypeExpr, TypeApplication, Context, SymbolTable,
5959
SymbolTableNode, BOUND_TVAR, UNBOUND_TVAR, ListComprehension, GeneratorExpr,
@@ -63,7 +63,7 @@
6363
YieldFromExpr, NamedTupleExpr, TypedDictExpr, NonlocalDecl, SymbolNode,
6464
SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr,
6565
YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr,
66-
IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr,
66+
IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode,
6767
COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES,
6868
)
6969
from mypy.visitor import NodeVisitor
@@ -547,6 +547,8 @@ def check_function_signature(self, fdef: FuncItem) -> None:
547547

548548
def visit_class_def(self, defn: ClassDef) -> None:
549549
self.clean_up_bases_and_infer_type_variables(defn)
550+
if self.analyze_namedtuple_classdef(defn):
551+
return
550552
self.setup_class_def_analysis(defn)
551553

552554
self.bind_class_type_vars(defn)
@@ -713,6 +715,56 @@ def analyze_unbound_tvar(self, t: Type) -> Tuple[str, TypeVarExpr]:
713715
return unbound.name, cast(TypeVarExpr, sym.node)
714716
return None
715717

718+
def analyze_namedtuple_classdef(self, defn: ClassDef) -> bool:
719+
# special case for NamedTuple
720+
for base_expr in defn.base_type_exprs:
721+
if isinstance(base_expr, RefExpr):
722+
base_expr.accept(self)
723+
if base_expr.fullname == 'typing.NamedTuple':
724+
node = self.lookup(defn.name, defn)
725+
if node is not None:
726+
node.kind = GDEF # TODO in process_namedtuple_definition also applies here
727+
items, types = self.check_namedtuple_classdef(defn)
728+
node.node = self.build_namedtuple_typeinfo(defn.name, items, types)
729+
return True
730+
return False
731+
732+
def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]:
733+
NAMEDTUP_CLASS_ERROR = ('Invalid statement in NamedTuple definition; '
734+
'expected "field_name: field_type"')
735+
if self.options.python_version < (3, 6):
736+
self.fail('NamedTuple class syntax is only supported in Python 3.6', defn)
737+
return [], []
738+
if len(defn.base_type_exprs) > 1:
739+
self.fail('NamedTuple should be a single base', defn)
740+
items = [] # type: List[str]
741+
types = [] # type: List[Type]
742+
for stmt in defn.defs.body:
743+
if< 8000 /span> not isinstance(stmt, AssignmentStmt):
744+
# Still allow pass or ... (for empty namedtuples).
745+
if (not isinstance(stmt, PassStmt) and
746+
not (isinstance(stmt, ExpressionStmt) and
747+
isinstance(stmt.expr, EllipsisExpr))):
748+
self.fail(NAMEDTUP_CLASS_ERROR, stmt)
749+
elif len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr):
750+
# An assignment, but an invalid one.
751+
self.fail(NAMEDTUP_CLASS_ERROR, stmt)
752+
else:
753+
# Append name and type in this case...
754+
name = stmt.lvalues[0].name
755+
items.append(name)
756+
types.append(AnyType() if stmt.type is None else self.anal_type(stmt.type))
757+
# ...despite possible minor failures that allow further analyzis.
758+
if name.startswith('_'):
759+
self.fail('NamedTuple field name cannot start with an underscore: {}'
760+
.format(name), stmt)
761+
if stmt.type is None or hasattr(stmt, 'new_syntax') and not stmt.new_syntax:
762+
self.fail(NAMEDTUP_CLASS_ERROR, stmt)
763+
elif not isinstance(stmt.rvalue, TempNode):
764+
# x: int assigns rvalue to TempNode(AnyType())
765+
self.fail('Right hand side values are not supported in NamedTuple', stmt)
766+
return items, types
767+
716768
def setup_class_def_analysis(self, defn: ClassDef) -> None:
717769
"""Prepare for the analysis of a class definition."""
718770
if not defn.info:

mypy/test/testcheck.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
'check-warnings.test',
6767
'check-async-await.test',
6868
'check-newtype.test',
69+
'check-class-namedtuple.test',
6970
'check-columns.test',
7071
]
7172

0 commit comments

Comments
 (0)
0