diff --git a/mypy/checkmember.py b/mypy/checkmember.py index ea8aff82d209..51de3888e83b 100644 --- a/mypy/checkmember.py +++ b/mypy/checkmember.py @@ -177,26 +177,35 @@ def analyze_member_access(name: str, elif isinstance(typ, TypeType): # Similar to FunctionLike + is_type_obj() above. item = None + fallback = builtin_type('builtins.type') + ignore_messages = msg.copy() + ignore_messages.disable_errors() 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 + elif isinstance(typ.item, FunctionLike) and typ.item.is_type_obj(): + item = typ.item.fallback + elif isinstance(typ.item, TypeType): + # Access member on metaclass object via Type[Type[C]] + if isinstance(typ.item.item, Instance): + item = typ.item.item.type.metaclass_type if item and not is_operator: # See comment above for why operators are skipped result = analyze_class_attribute_access(item, name, node, is_lvalue, builtin_type, not_ready_callback, msg, original_type=original_type) if result: - return result - fallback = builtin_type('builtins.type') + if not (isinstance(result, AnyType) and item.type.fallback_to_any): + return result + else: + # We don't want errors on metaclass lookup for classes with Any fallback + msg = ignore_messages if item is not None: fallback = item.type.metaclass_type or fallback return analyze_member_access(name, fallback, node, is_lvalue, is_super, diff --git a/mypy/types.py b/mypy/types.py index dcc845922419..5fb9430ee6b1 100644 --- a/mypy/types.py +++ b/mypy/types.py @@ -717,8 +717,7 @@ def copy_modified(self, ) def is_type_obj(self) -> bool: - t = self.fallback.type - return t is not None and t.is_metaclass() + return self.fallback.type.is_metaclass() def is_concrete_type_obj(self) -> bool: return self.is_type_obj() and self.is_classmethod_class @@ -1341,10 +1340,7 @@ def __init__(self, item: Union[Instance, AnyType, TypeVarType, TupleType, NoneTy type UnionType must be handled through make_normalized static method. """ super().__init__(line, column) - if isinstance(item, CallableType) and item.is_type_obj(): - self.item = item.fallback - else: - self.item = item + self.item = item @staticmethod def make_normalized(item: Type, *, line: int = -1, column: int = -1) -> Type: diff --git a/test-data/unit/check-classes.test b/test-data/unit/check-classes.test index 7243de98420b..229afb7ce17e 100644 --- a/test-data/unit/check-classes.test +++ b/test-data/unit/check-classes.test @@ -3995,3 +3995,44 @@ class E(metaclass=t.M): pass class F(six.with_metaclass(t.M)): pass @six.add_metaclass(t.M) class G: pass + +[case testMetaclassMemberAccessViaType] +from typing import Type +class M(type): + def m(cls, x: int) -> int: + pass + +class C(metaclass=M): + pass +x = C +y: Type[C] = C + +reveal_type(type(C).m) # E: Revealed type is 'def (cls: __main__.M, x: builtins.int) -> builtins.int' +reveal_type(type(x).m) # E: Revealed type is 'def (cls: __main__.M, x: builtins.int) -> builtins.int' +reveal_type(type(y).m) # E: Revealed type is 'def (cls: __main__.M, x: builtins.int) -> builtins.int' +[out] + +[case testMetaclassMemberAccessViaType2] +from typing import Any, Type +class M(type): + def m(cls, x: int) -> int: + pass +B: Any +class C(B, metaclass=M): + pass + +x: Type[C] +reveal_type(x.m) # E: Revealed type is 'def (x: builtins.int) -> builtins.int' +reveal_type(x.whatever) # E: Revealed type is 'Any' +[out] + +[case testMetaclassMemberAccessViaType3] +from typing import Any, Type, TypeVar +T = TypeVar('T') +class C(Any): + def bar(self: T) -> Type[T]: pass + def foo(self) -> None: + reveal_type(self.bar()) # E: Revealed type is 'Type[__main__.C*]' + reveal_type(self.bar().__name__) # E: Revealed type is 'builtins.str' +[builtins fixtures/type.pyi] +[out] diff --git a/test-data/unit/check-incremental.test b/test-data/unit/check-incremental.test index 1d3ee9a37436..8af04f897c1e 100644 --- a/test-data/unit/check-incremental.test +++ b/test-data/unit/check-incremental.test @@ -3164,3 +3164,23 @@ import foo external_list = [0] [builtins fixtures/dict.pyi] + +[case testIncrementalCrashOnTypeWithFunction] +import a +[file a.py] +import b +[file a.py.2] +from b import x + +[file b.py] +from typing import TypeVar, Type +T = TypeVar('T') + +def tp(arg: T) -> Type[T]: + pass +def func(x: int) -> int: + pass + +x = tp(func) +[out] +[out2] diff --git a/test-data/unit/fixtures/type.pyi b/test-data/unit/fixtures/type.pyi index 4a2dcac1ab4b..35cf0ad3ce73 100644 --- a/test-data/unit/fixtures/type.pyi +++ b/test-data/unit/fixtures/type.pyi @@ -11,6 +11,7 @@ class object: class list(Generic[T]): pass class type: + __name__: str def mro(self) -> List['type']: pass class tuple(Generic[T]): pass @@ -18,4 +19,4 @@ class function: pass class bool: pass class int: pass class str: pass -class unicode: pass \ No newline at end of file +class unicode: pass