Repository: python/mypy

Issue:
NamedTuple with no fields causes `"object" not callable` error
``` python
Blue = NamedTuple("Blue", [])
```

Fails with:

``` python
example.py:5: error: "object" not callable
```

But this code runs fine in python2/python3.

**Edit**:  
Seems like it's falling out to [here](https://github.com/python/mypy/blob/e308f2f786d432577286e0d84abaca505d02ec81/mypy/checkmember.py#L160)
a bit odd to me because NamedTuple does indeed have a `__call__` attribute

Interesting. The following fails:

``` python
alist = [('afield', str)]
Green = NamedTuple("Green", alist)

example.py:3: error: List literal expected as the second argument to namedtuple()
example.py:3: error: "object" not callable
```

But this works:

``` python
Green = NamedTuple("Green", [('afields', str)])
```

I think the `type_map` is storing `NameExpr(NamedTuple [typing.NamedTuple])` as an `object`, and it looks like the in the latter case it's skipping a check . . . I remember reading in an issue that NamedTuple wasn't fully supported, so I assume that's what's going on.



Discussion:
I think I know what this is. Because there are no items, the code [here](https://github.com/python/mypy/blob/e308f2f786d432577286e0d84abaca505d02ec81/mypy/semanal.py#L1355) thinks there's an error. It then skips the assignment to `call.analyzed` a few lines later. Maybe it can be fixed by changing "return" to "info =" in the error branch.

FWIW the thing you noted with using a variable for the list of fields is unrelated -- it's a general thing that mypy doesn't understand dynamically constructed types.


Resolving patch:
diff --git a/mypy/semanal.py b/mypy/semanal.py
--- a/mypy/semanal.py
+++ b/mypy/semanal.py
@@ -1351,8 +1351,8 @@ def check_namedtuple(self, node: Node) -> TypeInfo:
         fullname = callee.fullname
         if fullname not in ('collections.namedtuple', 'typing.NamedTuple'):
             return None
-        items, types = self.parse_namedtuple_args(call, fullname)
-        if not items:
+        items, types, ok = self.parse_namedtuple_args(call, fullname)
+        if not ok:
             # Error. Construct dummy return value.
             return self.build_namedtuple_typeinfo('namedtuple', [], [])
         else:
@@ -1362,7 +1362,7 @@ def check_namedtuple(self, node: Node) -> TypeInfo:
         return info
 
     def parse_namedtuple_args(self, call: CallExpr,
-                              fullname: str) -> Tuple[List[str], List[Type]]:
+                              fullname: str) -> Tuple[List[str], List[Type], bool]:
         # TODO Share code with check_argument_count in checkexpr.py?
         args = call.args
         if len(args) < 2:
@@ -1375,6 +1375,7 @@ def parse_namedtuple_args(self, call: CallExpr,
             return self.fail_namedtuple_arg(
                 "namedtuple() expects a string literal as the first argument", call)
         types = []  # type: List[Type]
+        ok = True
         if not isinstance(args[1], ListExpr):
             if fullname == 'collections.namedtuple' and isinstance(args[1], StrExpr):
                 str_expr = cast(StrExpr, args[1])
@@ -1392,13 +1393,13 @@ def parse_namedtuple_args(self, call: CallExpr,
                 items = [cast(StrExpr, item).value for item in listexpr.items]
             else:
                 # The fields argument contains (name, type) tuples.
-                items, types = self.parse_namedtuple_fields_with_types(listexpr.items, call)
+                items, types, ok = self.parse_namedtuple_fields_with_types(listexpr.items, call)
         if not types:
             types = [AnyType() for _ in items]
-        return items, types
+        return items, types, ok
 
     def parse_namedtuple_fields_with_types(self, nodes: List[Node],
-                                           context: Context) -> Tuple[List[str], List[Type]]:
+                                           context: Context) -> Tuple[List[str], List[Type], bool]:
         items = []  # type: List[str]
         types = []  # type: List[Type]
         for item in nodes:
@@ -1418,11 +1419,12 @@ def parse_namedtuple_fields_with_types(self, nodes: List[Node],
                 types.append(self.anal_type(type))
             else:
                 return self.fail_namedtuple_arg("Tuple expected as NamedTuple() field", item)
-        return items, types
+        return items, types, True
 
-    def fail_namedtuple_arg(self, message: str, context: Context) -> Tuple[List[str], List[Type]]:
+    def fail_namedtuple_arg(self, message: str,
+                            context: Context) -> Tuple[List[str], List[Type], bool]:
         self.fail(message, context)
-        return [], []
+        return [], [], False
 
     def build_namedtuple_typeinfo(self, name: str, items: List[str],
                                   types: List[Type]) -> TypeInfo:


Test patch:
diff --git a/mypy/test/data/check-namedtuple.test b/mypy/test/data/check-namedtuple.test
--- a/mypy/test/data/check-namedtuple.test
+++ b/mypy/test/data/check-namedtuple.test
@@ -168,3 +168,8 @@ a = (1,)  # E: Incompatible types in assignment (expression has type "Tuple[int]
 import collections
 MyNamedTuple = collections.namedtuple('MyNamedTuple', ['spam', 'eggs'])
 MyNamedTuple.x # E: "MyNamedTuple" has no attribute "x"
+
+[case testNamedTupleEmptyItems]
+from typing import NamedTuple
+A = NamedTuple('A', [])
+[builtins fixtures/list.py]