Repository: python/mypy

Issue:
Behaviour of Type[Any]
Currently a `Type[Any]` is considered to have the same attributes as `Type[object]`. However, actually it probably has many attributes that could be used, especially when the `Any` comes from a module without stubs. Consider the following example:

```python
from typing import Type
from some_module import Candy, Lollipop  # some_module has no stubs/annotations

class CandyFactory:
    manufactured_candy: Type[Candy]

cf = CandyFactory()
cf.manufactured_candy = Lollipop

print(cf.manufactured_candy.sugar_level)
```

Then running:

```
$ mypy --ignore-missing-imports candy.py 
candy.py:11: error: Type[Any] has no attribute "sugar_level"
```
Which seems to go a bit against the idea of gradual typing and intention of `Any`.

One possible solution is to consider that `Type[Any] == Any`, I think this actually would not miss anything that could be catched now, while reducing false positives.




Discussion:
I've wondered myself about how `Type[Any]` should behave, and your reasoning sounds about right. It should probably not be the same as `Any` with respect to subtyping/compatibility, only with respect to attribute access. Passing `Type[Any]` to a function that excepts an `int` should still be rejected (and vice versa).
I'm spelling out explicitly what should change: 

For a value `x` of type `Type[Any]`, mypy should allow arbitrary attribute access like `x.foobar`, and the result type should be `Any`.

Resolving patch:
diff --git a/mypy/checkmember.py b/mypy/checkmember.py
--- a/mypy/checkmember.py
+++ b/mypy/checkmember.py
@@ -176,6 +176,13 @@ def analyze_member_access(name: str,
         item = None
         if isinstance(typ.item, Instance):
             item = typ.item
+        elif isinstance(typ.item, AnyType):
+            fallback = builtin_type('builtins.type')
+            ignore_messages = msg.copy()
+            ignore_messages.disable_errors()
+            return analyze_member_access(name, fallback, node, is_lvalue, is_super,
+                                     is_operator, builtin_type, not_ready_callback,
+                                     ignore_messages, original_type=original_type, chk=chk)
         elif isinstance(typ.item, TypeVarType):
             if isinstance(typ.item.upper_bound, Instance):
                 item = typ.item.upper_bound


Test patch:
diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test
--- a/test-data/unit/check-classes.test
+++ b/test-data/unit/check-classes.test
@@ -2051,6 +2051,25 @@ class X: pass
 foo(X)
 [out]
 
+[case testTypeUsingTypeCTypeAnyMember]
+from typing import Type, Any
+def foo(arg: Type[Any]):
+    x = arg.member_name
+    arg.new_member_name = 42
+    # Member access is ok and types as Any
+    reveal_type(x)  # E: Revealed type is 'Any'
+    # But Type[Any] is distinct from Any
+    y: int = arg  # E: Incompatible types in assignment (expression has type Type[Any], variable has type "int")
+[out]
+
+[case testTypeUsingTypeCTypeAnyMemberFallback]
+from typing import Type, Any
+def foo(arg: Type[Any]):
+    reveal_type(arg.__str__)  # E: Revealed type is 'def () -> builtins.str'
+    reveal_type(arg.mro())  # E: Revealed type is 'builtins.list[builtins.type]'
+[builtins fixtures/type.pyi]
+[out]
+
 [case testTypeUsingTypeCTypeNoArg]
 from typing import Type
 def foo(arg: Type):