From 98e5cab19f0e33133eefcfa3689c4824d1d9ab3f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 13 Oct 2016 13:16:55 +0200 Subject: [PATCH 1/5] Add unit tests for new named tuple --- mypy/test/testcheck.py | 1 + test-data/unit/check-class-namedtuple.test | 358 +++++++++++++++++++++ 2 files changed, 359 insertions(+) create mode 100644 test-data/unit/check-class-namedtuple.test diff --git a/mypy/test/testcheck.py b/mypy/test/testcheck.py index 209076ecddf2..c5fe0c6316e4 100644 --- a/mypy/test/testcheck.py +++ b/mypy/test/testcheck.py @@ -66,6 +66,7 @@ 'check-warnings.test', 'check-async-await.test', 'check-newtype.test', + 'check-class-namedtuple.test', 'check-columns.test', ] diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test new file mode 100644 index 000000000000..a5c837314dfb --- /dev/null +++ b/test-data/unit/check-class-namedtuple.test @@ -0,0 +1,358 @@ +[case testNewNamedTupleOldPythonVersion] +# flags: --fast-parser --python-version 3.5 +from typing import NamedTuple + +class E(NamedTuple): # E: NamedTuple class syntax is only supported in Python 3.6+ + x: int + y: str + +[case testNewNamedTupleNoUnderscoreFields] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): # E: NamedTuple field names cannot start with an underscore: _y, _z + x: int + _y: int + _z: int + +[case testNewNamedTupleAccessingAttributes] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + +x: X +x.x +x.y +x.z # E: "X" has no attribute "z" + +[case testNewNamedTupleAttributesAreReadOnly] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + +x: X +x.x = 5 # E: Property "x" defined in "X" is read-only +x.y = 5 # E: Property "y" defined in "X" is read-only +x.z = 5 # E: "X" has no attribute "z" + +class A(X): pass +a: A +a.x = 5 # E: Property "x" defined in "A" is read-only +a.y = 5 # E: Property "y" defined in "A" is read-only +-- a.z = 5 # not supported yet + +[case testNewNamedTupleCreateWithPositionalArguments] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + +x = X(1, 'x') +x.x +x.z # E: "X" has no attribute "z" +x = X(1) # E: Too few arguments for "X" +x = X(1, 2, 3) # E: Too many arguments for "X" + +[case testCreateNewNamedTupleWithKeywordArguments] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + +x = X(x=1, y='x') +x = X(1, y='x') +x = X(x=1, z=1) # E: Unexpected keyword argument "z" for "X" +x = X(y=1) # E: Missing positional argument "x" in call to "X" + +[case testNewNamedTupleCreateAndUseAsTuple] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + +x = X(1, 'x') +a, b = x +a, b, c = x # E: Need more than 2 values to unpack (3 expected) + +[case testNewNamedTupleWithItemTypes] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class N(NamedTuple): + a: int + b: str + +n = N(1, 'x') +s: str = n.a # E: Incompatible types in assignment (expression has type "int", \ + variable has type "str") +i: int = n.b # E: Incompatible types in assignment (expression has type "str", \ + variable has type "int") +x, y = n +x = y # E: Incompatible types in assignment (expression has type "str", variable has type "int") + +[case testNewNamedTupleConstructorArgumentTypes] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class N(NamedTuple): + a: int + b: str + +n = N('x', 'x') # E: Argument 1 to "N" has incompatible type "str"; expected "int" +n = N(1, b=2) # E: Argument 2 to "N" has incompatible type "int"; expected "str" +N(1, 'x') +N(b='x', a=1) + +[case testNewNamedTupleAsBaseClass] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class N(NamedTuple): + a: int + b: str + +class X(N): + pass +x = X(1, 2) # E: Argument 2 to "X" has incompatible type "int"; expected "str" +s = '' +i = 0 +s = x.a # E: Incompatible types in assignment (expression has type "int", variable has type "str") +i, s = x +s, s = x # E: Incompatible types in assignment (expression has type "int", variable has type "str") + +[case testNewNamedTupleSelfTypeWithNamedTupleAsBase] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class A(NamedTuple): + a: int + b: str + +class B(A): + def f(self, x: int) -> None: + self.f(self.a) + self.f(self.b) # E: Argument 1 to "f" of "B" has incompatible type "str"; expected "int" + i = 0 + s = '' + i, s = self + i, i = self # E: Incompatible types in assignment (expression has type "str", \ + variable has type "int") +[out] +main: note: In member "f" of class "B": + +[case testNewNamedTupleTypeReferenceToClassDerivedFrom] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class A(NamedTuple): + a: int + b: str + +class B(A): + def f(self, x: 'B') -> None: + i = 0 + s = '' + self = x + i, s = x + i, s = x.a, x.b + i, s = x.a, x.a # E: Incompatible types in assignment (expression has type "int", \ + variable has type "str") + i, i = self # E: Incompatible types in assignment (expression has type "str", \ + variable has type "int") + +[out] +main: note: In member "f" of class "B": + +[case testNewNamedTupleSubtyping] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple, Tuple + +class A(NamedTuple): + a: int + b: str + +class B(A): pass +a = A(1, '') +b = B(1, '') +t: Tuple[int, str] +b = a # E: Incompatible types in assignment (expression has type "A", variable has type "B") +a = t # E: Incompatible types in assignment (expression has type "Tuple[int, str]", variable has type "A") +b = t # E: Incompatible types in assignment (expression has type "Tuple[int, str]", variable has type "B") +t = a +t = (1, '') +t = b +a = b + +[case testNewNamedTupleSimpleTypeInference] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple, Tuple + +class A(NamedTuple): + a: int + +l = [A(1), A(2)] +a = A(1) +a = l[0] +(i,) = l[0] +i, i = l[0] # E: Need more than 1 value to unpack (2 expected) +l = [A(1)] +a = (1,) # E: Incompatible types in assignment (expression has type "Tuple[int]", \ + variable has type "A") +[builtins fixtures/list.pyi] + +[case testNewNamedTupleMissingClassAttribute] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class MyNamedTuple(NamedTuple): + a: int + b: str + +MyNamedTuple.x # E: "MyNamedTuple" has no attribute "x" + +[case testNewNamedTupleEmptyItems] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class A(NamedTuple): + ... + +[case testNewNamedTupleProperty] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class A(NamedTuple): + a: int + b: str + +class B(A): + @property + def b(self) -> int: + return self.a +class C(B): pass +B(1).b +C(2).b + +[builtins fixtures/property.pyi] + +[case testNewNamedTupleAsDict] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple, Any + +class X(NamedTuple): + x: Any + y: Any + +x: X +reveal_type(x._asdict()) # E: Revealed type is 'builtins.dict[builtins.str, Any]' + +[builtins fixtures/dict.pyi] + +[case testNewNamedTupleReplaceTyped] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + +x: X +reveal_type(x._replace()) # E: Revealed type is 'Tuple[builtins.int, builtins.str, fallback=__main__.X]' +x._replace(x=5) +x._replace(y=5) # E: Argument 1 to X._replace has incompatible type "int"; expected "str" + +[case testNewNamedTupleFields] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + +reveal_type(X._fields) # E: Revealed type is 'Tuple[builtins.str, builtins.str]' + +[case testNewNamedTupleUnit] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + ... + +x: X = X() +x._replace() +x._fields[0] # E: Tuple index out of range + +[case testNewNamedTupleJoinNamedTuple] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str +class Y(NamedTuple): + x: int + y: str + +reveal_type([X(3, 'b'), Y(1, 'a')]) # E: Revealed type is 'builtins.list[Tuple[builtins.int, builtins.str]]' + +[builtins fixtures/list.pyi] + +[case testNewNamedTupleJoinTuple] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + +reveal_type([(3, 'b'), X(1, 'a')]) # E: Revealed type is 'builtins.list[Tuple[builtins.int, builtins.str]]' +reveal_type([X(1, 'a'), (3, 'b')]) # E: Revealed type is 'builtins.list[Tuple[builtins.int, builtins.str]]' + +[builtins fixtures/list.pyi] + +[case testNewNamedTupleWithTooManyArguments] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + def f(self): pass # E: NamedTuple only allows names as fields + +[case testNewNamedTupleWithInvalidItems2] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y: str + [0][0]: int # E: NamedTuple only allows names as fields + +[builtins fixtures/list.pyi] + +[case testTypeUsingTypeCNamedTuple] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class N(NamedTuple): + x: int + y: str + +def f(a: Type[N]): + a() +[builtins fixtures/list.pyi] +[out] +main: note: In function "f": +main:3: error: Unsupported type Type["N"] From 409abb668c5a2966a15790bfa7867b725e2a895f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 13 Oct 2016 15:37:42 +0200 Subject: [PATCH 2/5] Implement new NamedTuple syntax --- mypy/semanal.py | 51 +++++++++++++++++++++- test-data/unit/check-class-namedtuple.test | 36 +++++++-------- 2 files changed, 64 insertions(+), 23 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index ea195b5fe5ca..782fc7cba40f 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -53,7 +53,7 @@ ImportFrom, ImportAll, Block, LDEF, NameExpr, MemberExpr, IndexExpr, TupleExpr, ListExpr, ExpressionStmt, ReturnStmt, RaiseStmt, AssertStmt, OperatorAssignmentStmt, WhileStmt, - ForStmt, BreakStmt, ContinueStmt, IfStmt, TryStmt, WithStmt, DelStmt, + ForStmt, BreakStmt, ContinueStmt, IfStmt, TryStmt, WithStmt, DelStmt, PassStmt, GlobalDecl, SuperExpr, DictExpr, CallExpr, RefExpr, OpExpr, UnaryExpr, SliceExpr, CastExpr, RevealTypeExpr, TypeApplication, Context, SymbolTable, SymbolTableNode, BOUND_TVAR, UNBOUND_TVAR, ListComprehension, GeneratorExpr, @@ -63,7 +63,7 @@ YieldFromExpr, NamedTupleExpr, NonlocalDecl, SymbolNode, SetComprehension, DictionaryComprehension, TYPE_ALIAS, TypeAliasExpr, YieldExpr, ExecStmt, Argument, BackquoteExpr, ImportBase, AwaitExpr, - IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, + IntExpr, FloatExpr, UnicodeExpr, EllipsisExpr, TempNode, COVARIANT, CONTRAVARIANT, INVARIANT, UNBOUND_IMPORTED, LITERAL_YES, ) from mypy.visitor import NodeVisitor @@ -545,7 +545,54 @@ def check_function_signature(self, fdef: FuncItem) -> None: elif len(sig.arg_types) > len(fdef.arguments): self.fail('Type signature has too many arguments', fdef, blocker=True) + def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]: + if self.options.python_version < (3, 6): + self.fail('NamedTuple class syntax is only supported in Python 3.6', defn) + return [], [] + if len(defn.base_type_exprs) > 1: + self.fail('NamedTuple should be a single base', defn) + return [], [] + items = [] # type: List[str] + types = [] # type: List[Type] + for stmt in defn.defs.body: + if isinstance(stmt, AssignmentStmt): + if len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr): + self.fail('NamedTuple supports only simple fields', stmt) + return [], [] + name = stmt.lvalues[0].name + if not isinstance(stmt.rvalue, TempNode): + # x: int assigns rvalue to TempNode(AnyType()) + self.fail('NamedTuple does not support initial values', stmt) + if stmt.type is None: + self.fail('NamedTuple field type must be specified', stmt) + types.append(AnyType()) + else: + types.append(self.anal_type(stmt.type)) + if name.startswith('_'): + self.fail('NamedTuple field name cannot start with an underscore: {}' + .format(name), stmt) + items.append(name) + else: + if not isinstance(stmt, PassStmt): + self.fail('Invalid definition in NamedTuple', stmt) + return items, types + + def analyze_namedtuple_classdef(self, defn: ClassDef) -> None: + node = self.lookup(defn.name, defn) + node.kind = GDEF # TODO in process_namedtuple_definition also applies here + items, types = self.check_namedtuple_classdef(defn) + node.node = self.build_namedtuple_typeinfo(defn.name, items, types) + def visit_class_def(self, defn: ClassDef) -> None: + # special case for NamedTuple + for base_expr in defn.base_type_exprs: + if isinstance(base_expr, NameExpr): + sym = self.lookup_qualified(base_expr.name, base_expr) + if sym is None or sym.node is None: + continue + if sym.node.fullname() == 'typing.NamedTuple': + self.analyze_namedtuple_classdef(defn) + return self.clean_up_bases_and_infer_type_variables(defn) self.setup_class_def_analysis(defn) diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index a5c837314dfb..9f3d44228ab9 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -2,18 +2,17 @@ # flags: --fast-parser --python-version 3.5 from typing import NamedTuple -class E(NamedTuple): # E: NamedTuple class syntax is only supported in Python 3.6+ - x: int - y: str +class E(NamedTuple): # E: NamedTuple class syntax is only supported in Python 3.6 + pass [case testNewNamedTupleNoUnderscoreFields] # flags: --fast-parser --python-version 3.6 from typing import NamedTuple -class X(NamedTuple): # E: NamedTuple field names cannot start with an underscore: _y, _z +class X(NamedTuple): x: int - _y: int - _z: int + _y: int # E: NamedTuple field name cannot start with an underscore: _y + _z: int # E: NamedTuple field name cannot start with an underscore: _z [case testNewNamedTupleAccessingAttributes] # flags: --fast-parser --python-version 3.6 @@ -34,18 +33,14 @@ from typing import NamedTuple class X(NamedTuple): x: int - y: str x: X x.x = 5 # E: Property "x" defined in "X" is read-only -x.y = 5 # E: Property "y" defined in "X" is read-only -x.z = 5 # E: "X" has no attribute "z" +x.y = 5 # E: "X" has no attribute "y" class A(X): pass a: A a.x = 5 # E: Property "x" defined in "A" is read-only -a.y = 5 # E: Property "y" defined in "A" is read-only --- a.z = 5 # not supported yet [case testNewNamedTupleCreateWithPositionalArguments] # flags: --fast-parser --python-version 3.6 @@ -55,11 +50,11 @@ class X(NamedTuple): x: int y: str -x = X(1, 'x') +x = X(1, '2') x.x x.z # E: "X" has no attribute "z" x = X(1) # E: Too few arguments for "X" -x = X(1, 2, 3) # E: Too many arguments for "X" +x = X(1, '2', 3) # E: Too many arguments for "X" [case testCreateNewNamedTupleWithKeywordArguments] # flags: --fast-parser --python-version 3.6 @@ -72,7 +67,7 @@ class X(NamedTuple): x = X(x=1, y='x') x = X(1, y='x') x = X(x=1, z=1) # E: Unexpected keyword argument "z" for "X" -x = X(y=1) # E: Missing positional argument "x" in call to "X" +x = X(y='x') # E: Missing positional argument "x" in call to "X" [case testNewNamedTupleCreateAndUseAsTuple] # flags: --fast-parser --python-version 3.6 @@ -227,7 +222,7 @@ MyNamedTuple.x # E: "MyNamedTuple" has no attribute "x" from typing import NamedTuple class A(NamedTuple): - ... + pass [case testNewNamedTupleProperty] # flags: --fast-parser --python-version 3.6 @@ -235,7 +230,6 @@ from typing import NamedTuple class A(NamedTuple): a: int - b: str class B(A): @property @@ -288,7 +282,7 @@ reveal_type(X._fields) # E: Revealed type is 'Tuple[builtins.str, builtins.str] from typing import NamedTuple class X(NamedTuple): - ... + pass x: X = X() x._replace() @@ -329,7 +323,7 @@ from typing import NamedTuple class X(NamedTuple): x: int y: str - def f(self): pass # E: NamedTuple only allows names as fields + def f(self): pass # E: Invalid definition in NamedTuple [case testNewNamedTupleWithInvalidItems2] # flags: --fast-parser --python-version 3.6 @@ -338,13 +332,13 @@ from typing import NamedTuple class X(NamedTuple): x: int y: str - [0][0]: int # E: NamedTuple only allows names as fields + x[0]: int # E: NamedTuple supports only simple fields [builtins fixtures/list.pyi] [case testTypeUsingTypeCNamedTuple] # flags: --fast-parser --python-version 3.6 -from typing import NamedTuple +from typing import NamedTuple, Type class N(NamedTuple): x: int @@ -355,4 +349,4 @@ def f(a: Type[N]): [builtins fixtures/list.pyi] [out] main: note: In function "f": -main:3: error: Unsupported type Type["N"] +main:8: error: Unsupported type Type["N"] From aa71fda1ad0fa429d4e5e168ee65125d147a46c5 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Thu, 13 Oct 2016 16:23:14 +0200 Subject: [PATCH 3/5] More checks and more tests --- mypy/semanal.py | 13 ++++--- test-data/unit/check-class-namedtuple.test | 42 +++++++++++++++++++--- 2 files changed, 45 insertions(+), 10 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index 782fc7cba40f..f18a414ebfdc 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -546,19 +546,20 @@ def check_function_signature(self, fdef: FuncItem) -> None: self.fail('Type signature has too many arguments', fdef, blocker=True) def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]: + NAMEDTUP_CLASS_ERROR = 'Invalid statement in NamedTuple definition; ' \ + 'expected "field_name: field_type"' if self.options.python_version < (3, 6): self.fail('NamedTuple class syntax is only supported in Python 3.6', defn) return [], [] if len(defn.base_type_exprs) > 1: self.fail('NamedTuple should be a single base', defn) - return [], [] items = [] # type: List[str] types = [] # type: List[Type] for stmt in defn.defs.body: if isinstance(stmt, AssignmentStmt): if len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr): - self.fail('NamedTuple supports only simple fields', stmt) - return [], [] + self.fail(NAMEDTUP_CLASS_ERROR, stmt) + continue name = stmt.lvalues[0].name if not isinstance(stmt.rvalue, TempNode): # x: int assigns rvalue to TempNode(AnyType()) @@ -573,8 +574,10 @@ def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Typ .format(name), stmt) items.append(name) else: - if not isinstance(stmt, PassStmt): - self.fail('Invalid definition in NamedTuple', stmt) + if (not isinstance(stmt, PassStmt) and + not (isinstance(stmt, ExpressionStmt) and + isinstance(stmt.expr, EllipsisExpr))): + self.fail(NAMEDTUP_CLASS_ERROR, stmt) return items, types def analyze_namedtuple_classdef(self, defn: ClassDef) -> None: diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index 9f3d44228ab9..1e69d9c88e6b 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -56,6 +56,14 @@ x.z # E: "X" has no attribute "z" x = X(1) # E: Too few arguments for "X" x = X(1, '2', 3) # E: Too many arguments for "X" +[case testNewNamedTupleShouldBeSingleBase] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class A: ... +class X(NamedTuple, A): # E: NamedTuple should be a single base + pass + [case testCreateNewNamedTupleWithKeywordArguments] # flags: --fast-parser --python-version 3.6 from typing import NamedTuple @@ -222,7 +230,19 @@ MyNamedTuple.x # E: "MyNamedTuple" has no attribute "x" from typing import NamedTuple class A(NamedTuple): - pass + ... + +[case testNewNamedTupleForwardRef] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class A(NamedTuple): + b: 'B' + +class B: ... + +a = A(B()) +a = A(1) # E: Argument 1 to "A" has incompatible type "int"; expected "B" [case testNewNamedTupleProperty] # flags: --fast-parser --python-version 3.6 @@ -322,8 +342,8 @@ from typing import NamedTuple class X(NamedTuple): x: int - y: str - def f(self): pass # E: Invalid definition in NamedTuple + y = z = 2 # E: Invalid statement in NamedTuple definition; expected "field_name: field_type" + def f(self): pass # E: Invalid statement in NamedTuple definition; expected "field_name: field_type" [case testNewNamedTupleWithInvalidItems2] # flags: --fast-parser --python-version 3.6 @@ -331,11 +351,23 @@ from typing import NamedTuple class X(NamedTuple): x: int - y: str - x[0]: int # E: NamedTuple supports only simple fields + y: str = 'y' # E: NamedTuple does not support initial values + x[0]: int # E: Invalid statement in NamedTuple definition; expected "field_name: field_type" [builtins fixtures/list.pyi] +[case testNewNamedTupleWithoutTypesSpecified] +# flags: --fast-parser --python-version 3.6 +from typing import NamedTuple + +class X(NamedTuple): + x: int + y = 2 + +[out] +main:6: error: NamedTuple does not support initial values +main:6: error: NamedTuple field type must be specified + [case testTypeUsingTypeCNamedTuple] # flags: --fast-parser --python-version 3.6 from typing import NamedTuple, Type From a8299878b38852aaf78f647a4134c95d27646f5f Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Oct 2016 21:26:13 +0200 Subject: [PATCH 4/5] First part of response to comments --- mypy/semanal.py | 82 +++++++++++----------- test-data/unit/check-class-namedtuple.test | 13 ++-- 2 files changed, 45 insertions(+), 50 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index f18a414ebfdc..c26613bfbf78 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -545,51 +545,10 @@ def check_function_signature(self, fdef: FuncItem) -> None: elif len(sig.arg_types) > len(fdef.arguments): self.fail('Type signature has too many arguments', fdef, blocker=True) - def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]: - NAMEDTUP_CLASS_ERROR = 'Invalid statement in NamedTuple definition; ' \ - 'expected "field_name: field_type"' - if self.options.python_version < (3, 6): - self.fail('NamedTuple class syntax is only supported in Python 3.6', defn) - return [], [] - if len(defn.base_type_exprs) > 1: - self.fail('NamedTuple should be a single base', defn) - items = [] # type: List[str] - types = [] # type: List[Type] - for stmt in defn.defs.body: - if isinstance(stmt, AssignmentStmt): - if len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr): - self.fail(NAMEDTUP_CLASS_ERROR, stmt) - continue - name = stmt.lvalues[0].name - if not isinstance(stmt.rvalue, TempNode): - # x: int assigns rvalue to TempNode(AnyType()) - self.fail('NamedTuple does not support initial values', stmt) - if stmt.type is None: - self.fail('NamedTuple field type must be specified', stmt) - types.append(AnyType()) - else: - types.append(self.anal_type(stmt.type)) - if name.startswith('_'): - self.fail('NamedTuple field name cannot start with an underscore: {}' - .format(name), stmt) - items.append(name) - else: - if (not isinstance(stmt, PassStmt) and - not (isinstance(stmt, ExpressionStmt) and - isinstance(stmt.expr, EllipsisExpr))): - self.fail(NAMEDTUP_CLASS_ERROR, stmt) - return items, types - - def analyze_namedtuple_classdef(self, defn: ClassDef) -> None: - node = self.lookup(defn.name, defn) - node.kind = GDEF # TODO in process_namedtuple_definition also applies here - items, types = self.check_namedtuple_classdef(defn) - node.node = self.build_namedtuple_typeinfo(defn.name, items, types) - def visit_class_def(self, defn: ClassDef) -> None: # special case for NamedTuple for base_expr in defn.base_type_exprs: - if isinstance(base_expr, NameExpr): + if isinstance(base_expr, RefExpr): sym = self.lookup_qualified(base_expr.name, base_expr) if sym is None or sym.node is None: continue @@ -774,6 +733,45 @@ def analyze_unbound_tvar(self, t: Type) -> Tuple[str, TypeVarExpr]: return unbound.name, cast(TypeVarExpr, sym.node) return None + def analyze_namedtuple_classdef(self, defn: ClassDef) -> None: + node = self.lookup(defn.name, defn) + node.kind = GDEF # TODO in process_namedtuple_definition also applies here + items, types = self.check_namedtuple_classdef(defn) + node.node = self.build_namedtuple_typeinfo(defn.name, items, types) + + def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]: + NAMEDTUP_CLASS_ERROR = ('Invalid statement in NamedTuple definition; ' + 'expected "field_name: field_type"') + if self.options.python_version < (3, 6): + self.fail('NamedTuple class syntax is only supported in Python 3.6', defn) + return [], [] + if len(defn.base_type_exprs) > 1: + self.fail('NamedTuple should be a single base', defn) + items = [] # type: List[str] + types = [] # type: List[Type] + for stmt in defn.defs.body: + if not isinstance(stmt, AssignmentStmt): + # Still allow pass or ... (for empty namedtuples). + if (not isinstance(stmt, PassStmt) and + not (isinstance(stmt, ExpressionStmt) and + isinstance(stmt.expr, EllipsisExpr))): + self.fail(NAMEDTUP_CLASS_ERROR, stmt) + elif len(stmt.lvalues) > 1 or not isinstance(stmt.lvalues[0], NameExpr): + # An assignment, but an invalid one. + self.fail(NAMEDTUP_CLASS_ERROR, stmt) + else: + # Always append name and type in this case... + name = stmt.lvalues[0].name + items.append(name) + types.append(AnyType() if stmt.type is None else self.anal_type(stmt.type)) + # ...despite possible minor failures that allow further analyzis. + if name.startswith('_'): + self.fail('NamedTuple field name cannot start with an underscore: {}' + .format(name), stmt) + if stmt.type is None or hasattr(stmt, 'new_syntax') and not stmt.new_syntax: + self.fail(NAMEDTUP_CLASS_ERROR, stmt) + return items, types + def setup_class_def_analysis(self, defn: ClassDef) -> None: """Prepare for the analysis of a class definition.""" if not defn.info: diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index 1e69d9c88e6b..accf947ce58b 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -347,11 +347,12 @@ class X(NamedTuple): [case testNewNamedTupleWithInvalidItems2] # flags: --fast-parser --python-version 3.6 -from typing import NamedTuple +import typing -class X(NamedTuple): +class X(typing.NamedTuple): x: int - y: str = 'y' # E: NamedTuple does not support initial values + y: str = 'y' + z = None # type: int # E: Invalid statement in NamedTuple definition; expected "field_name: field_type" x[0]: int # E: Invalid statement in NamedTuple definition; expected "field_name: field_type" [builtins fixtures/list.pyi] @@ -362,11 +363,7 @@ from typing import NamedTuple class X(NamedTuple): x: int - y = 2 - -[out] -main:6: error: NamedTuple does not support initial values -main:6: error: NamedTuple field type must be specified + y = 2 # E: Invalid statement in NamedTuple definition; expected "field_name: field_type" [case testTypeUsingTypeCNamedTuple] # flags: --fast-parser --python-version 3.6 From 3be0166ce2d0101e31f8240bd029b342c040b802 Mon Sep 17 00:00:00 2001 From: Ivan Levkivskyi Date: Sun, 16 Oct 2016 22:27:30 +0200 Subject: [PATCH 5/5] Second part of responses to comments --- mypy/semanal.py | 34 ++++++++++++---------- test-data/unit/check-class-namedtuple.test | 2 +- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/mypy/semanal.py b/mypy/semanal.py index c26613bfbf78..0551506582ce 100644 --- a/mypy/semanal.py +++ b/mypy/semanal.py @@ -546,16 +546,9 @@ def check_function_signature(self, fdef: FuncItem) -> None: self.fail('Type signature has too many arguments', fdef, blocker=True) def visit_class_def(self, defn: ClassDef) -> None: - # special case for NamedTuple - for base_expr in defn.base_type_exprs: - if isinstance(base_expr, RefExpr): - sym = self.lookup_qualified(base_expr.name, base_expr) - if sym is None or sym.node is None: - continue - if sym.node.fullname() == 'typing.NamedTuple': - self.analyze_namedtuple_classdef(defn) - return self.clean_up_bases_and_infer_type_variables(defn) + if self.analyze_namedtuple_classdef(defn): + return self.setup_class_def_analysis(defn) self.bind_class_type_vars(defn) @@ -733,11 +726,19 @@ def analyze_unbound_tvar(self, t: Type) -> Tuple[str, TypeVarExpr]: return unbound.name, cast(TypeVarExpr, sym.node) return None - def analyze_namedtuple_classdef(self, defn: ClassDef) -> None: - node = self.lookup(defn.name, defn) - node.kind = GDEF # TODO in process_namedtuple_definition also applies here - items, types = self.check_namedtuple_classdef(defn) - node.node = self.build_namedtuple_typeinfo(defn.name, items, types) + def analyze_namedtuple_classdef(self, defn: ClassDef) -> bool: + # special case for NamedTuple + for base_expr in defn.base_type_exprs: + if isinstance(base_expr, RefExpr): + base_expr.accept(self) + if base_expr.fullname == 'typing.NamedTuple': + node = self.lookup(defn.name, defn) + if node is not None: + node.kind = GDEF # TODO in process_namedtuple_definition also applies here + items, types = self.check_namedtuple_classdef(defn) + node.node = self.build_namedtuple_typeinfo(defn.name, items, types) + return True + return False def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Type]]: NAMEDTUP_CLASS_ERROR = ('Invalid statement in NamedTuple definition; ' @@ -760,7 +761,7 @@ def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Typ # An assignment, but an invalid one. self.fail(NAMEDTUP_CLASS_ERROR, stmt) else: - # Always append name and type in this case... + # Append name and type in this case... name = stmt.lvalues[0].name items.append(name) types.append(AnyType() if stmt.type is None else self.anal_type(stmt.type)) @@ -770,6 +771,9 @@ def check_namedtuple_classdef(self, defn: ClassDef) -> Tuple[List[str], List[Typ .format(name), stmt) if stmt.type is None or hasattr(stmt, 'new_syntax') and not stmt.new_syntax: self.fail(NAMEDTUP_CLASS_ERROR, stmt) + elif not isinstance(stmt.rvalue, TempNode): + # x: int assigns rvalue to TempNode(AnyType()) + self.fail('Right hand side values are not supported in NamedTuple', stmt) return items, types def setup_class_def_analysis(self, defn: ClassDef) -> None: diff --git a/test-data/unit/check-class-namedtuple.test b/test-data/unit/check-class-namedtuple.test index accf947ce58b..d5575fcb8649 100644 --- a/test-data/unit/check-class-namedtuple.test +++ b/test-data/unit/check-class-namedtuple.test @@ -351,7 +351,7 @@ import typing class X(typing.NamedTuple): x: int - y: str = 'y' + y: str = 'y' # E: Right hand side values are not supported in NamedTuple z = None # type: int # E: Invalid statement in NamedTuple definition; expected "field_name: field_type" x[0]: int # E: Invalid statement in NamedTuple definition; expected "field_name: field_type"