Repository: python/mypy

Issue:
Item "type" of "Union[Type[Foo], Type[Bar]]" has no attribute "x"
```
# mypy 0.530
foo.py:14: warning: Returning Any from function declared to return "int"
foo.py:14: error: Item "type" of "Union[Type[Foo], Type[Bar]]" has no attribute "x"
```

```python
from typing import *

class Foo(NamedTuple):
    a: int
    @classmethod
    def x(cls) -> int: return 1

class Bar(NamedTuple):
    a: int
    @classmethod
    def x(cls) -> int: return 1

def foo(t: Type[Union[Foo, Bar]]) -> int:
    return t.x()
```

If I make them usual classes instead of namedtuples, it works


Discussion:
Good catch. Looks like the code that checks for class attributes doesn't handle unions yet.
This has nothing to do with unions. Accessing x in `Type[FOO]` does not work either, specifically for NamedTuple. Probably something about the combination of TypeType and TupleType. 
You're so right! Relabeled.
This seems similar to https://github.com/python/mypy/issues/3791. Also as discussed there, I think this is quite low priority, since one should prefer dataclasses for such complex cases.
I agree that dataclasses are preferable, but unless I'm mistaken they are not ready for production use. If they are I'll definitely look into them since it would simplify a great deal.
@Daenyth 
> they are not ready for production use.

Yes they aren't yet.  They are planned for Python 3.7.
This also happens for classes that aren't named tuples:

```py
from typing import Tuple, Type

class C(Tuple[int, str]):
    @classmethod
    def f(cls) -> None: pass

t: Type[C]
t.f()  # "Type[C]" has no attribute "f"
```

I'm increasing priority to normal because the above example seems quite reasonable.

Resolving patch:
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -191,6 +191,8 @@ def analyze_member_access(name: str,
         elif isinstance(typ.item, TypeVarType):
             if isinstance(typ.item.upper_bound, Instance):
                 item = typ.item.upper_bound
+        elif isinstance(typ.item, TupleType):
+            item = typ.item.fallback
         elif isinstance(typ.item, FunctionLike) and typ.item.is_type_obj():
             item = typ.item.fallback
         elif isinstance(typ.item, TypeType):


Test patch:
diff --git a/test-data/unit/check-namedtuple.test b/test-data/unit/check-namedtuple.test
--- a/test-data/unit/check-namedtuple.test
+++ b/test-data/unit/check-namedtuple.test
@@ -638,3 +638,14 @@ class A(NamedTuple):
 
 class B:
     pass
+
+[case testTypeNamedTupleClassmethod]
+from typing import Type, NamedTuple
+class D(NamedTuple):
+    @classmethod
+    def f(cls) -> None: pass
+
+d: Type[D]
+d.g()  # E: "Type[D]" has no attribute "g"
+d.f()
+[builtins fixtures/classmethod.pyi]
diff --git a/test-data/unit/check-tuples.test b/test-data/unit/check-tuples.test
--- a/test-data/unit/check-tuples.test
+++ b/test-data/unit/check-tuples.test
@@ -940,3 +940,15 @@ y = ""
 reveal_type(t[x:])  # E: Revealed type is 'Union[builtins.int, builtins.str]'
 t[y:]  # E: Slice index must be an integer or None
 [builtins fixtures/tuple.pyi]
+
+[case testTypeTupleClassmethod]
+from typing import Tuple, Type
+
+class C(Tuple[int, str]):
+    @classmethod
+    def f(cls) -> None: pass
+
+t: Type[C]
+t.g()  # E: "Type[C]" has no attribute "g"
+t.f()
+[builtins fixtures/classmethod.pyi]