diff --git a/mypy/messages.py b/mypy/messages.py index 99ff6d5a3703..29fd1503e595 100644 --- a/mypy/messages.py +++ b/mypy/messages.py @@ -743,7 +743,9 @@ def incompatible_argument_note( context: Context, code: ErrorCode | None, ) -> None: - if isinstance(original_caller_type, (Instance, TupleType, TypedDictType)): + if isinstance( + original_caller_type, (Instance, TupleType, TypedDictType, TypeType, CallableType) + ): if isinstance(callee_type, Instance) and callee_type.type.is_protocol: self.report_protocol_problems( original_caller_type, callee_type, context, code=code @@ -1791,7 +1793,7 @@ def impossible_intersection( def report_protocol_problems( self, - subtype: Instance | TupleType | TypedDictType, + subtype: Instance | TupleType | TypedDictType | TypeType | CallableType, supertype: Instance, context: Context, *, @@ -1811,15 +1813,15 @@ def report_protocol_problems( exclusions: dict[type, list[str]] = { TypedDictType: ["typing.Mapping"], TupleType: ["typing.Iterable", "typing.Sequence"], - Instance: [], } - if supertype.type.fullname in exclusions[type(subtype)]: + if supertype.type.fullname in exclusions.get(type(subtype), []): return if any(isinstance(tp, UninhabitedType) for tp in get_proper_types(supertype.args)): # We don't want to add notes for failed inference (e.g. Iterable[]). # This will be only confusing a user even more. return + class_obj = False if isinstance(subtype, TupleType): if not isinstance(subtype.partial_fallback, Instance): return @@ -1828,6 +1830,21 @@ def report_protocol_problems( if not isinstance(subtype.fallback, Instance): return subtype = subtype.fallback + elif isinstance(subtype, TypeType): + if not isinstance(subtype.item, Instance): + return + class_obj = True + subtype = subtype.item + elif isinstance(subtype, CallableType): + if not subtype.is_type_obj(): + return + ret_type = get_proper_type(subtype.ret_type) + if isinstance(ret_type, TupleType): + ret_type = ret_type.partial_fallback + if not isinstance(ret_type, Instance): + return + class_obj = True + subtype = ret_type # Report missing members missing = get_missing_protocol_members(subtype, supertype) @@ -1836,20 +1853,29 @@ def report_protocol_problems( and len(missing) < len(supertype.type.protocol_members) and len(missing) <= MAX_ITEMS ): - self.note( - '"{}" is missing following "{}" protocol member{}:'.format( - subtype.type.name, supertype.type.name, plural_s(missing) - ), - context, - code=code, - ) - self.note(", ".join(missing), context, offset=OFFSET, code=code) + if missing == ["__call__"] and class_obj: + self.note( + '"{}" has constructor incompatible with "__call__" of "{}"'.format( + subtype.type.name, supertype.type.name + ), + context, + code=code, + ) + else: + self.note( + '"{}" is missing following "{}" protocol member{}:'.format( + subtype.type.name, supertype.type.name, plural_s(missing) + ), + context, + code=code, + ) + self.note(", ".join(missing), context, offset=OFFSET, code=code) elif len(missing) > MAX_ITEMS or len(missing) == len(supertype.type.protocol_members): # This is an obviously wrong type: too many missing members return # Report member type conflicts - conflict_types = get_conflict_protocol_types(subtype, supertype) + conflict_types = get_conflict_protocol_types(subtype, supertype, class_obj=class_obj) if conflict_types and ( not is_subtype(subtype, erase_type(supertype)) or not subtype.type.defn.type_vars @@ -1875,29 +1901,43 @@ def report_protocol_problems( else: self.note("Expected:", context, offset=OFFSET, code=code) if isinstance(exp, CallableType): - self.note(pretty_callable(exp), context, offset=2 * OFFSET, code=code) + self.note( + pretty_callable(exp, skip_self=class_obj), + context, + offset=2 * OFFSET, + code=code, + ) else: assert isinstance(exp, Overloaded) - self.pretty_overload(exp, context, 2 * OFFSET, code=code) + self.pretty_overload( + exp, context, 2 * OFFSET, code=code, skip_self=class_obj + ) self.note("Got:", context, offset=OFFSET, code=code) if isinstance(got, CallableType): - self.note(pretty_callable(got), context, offset=2 * OFFSET, code=code) + self.note( + pretty_callable(got, skip_self=class_obj), + context, + offset=2 * OFFSET, + code=code, + ) else: assert isinstance(got, Overloaded) - self.pretty_overload(got, context, 2 * OFFSET, code=code) + self.pretty_overload( + got, context, 2 * OFFSET, code=code, skip_self=class_obj + ) self.print_more(conflict_types, context, OFFSET, MAX_ITEMS, code=code) # Report flag conflicts (i.e. settable vs read-only etc.) - conflict_flags = get_bad_protocol_flags(subtype, supertype) + conflict_flags = get_bad_protocol_flags(subtype, supertype, class_obj=class_obj) for name, subflags, superflags in conflict_flags[:MAX_ITEMS]: - if IS_CLASSVAR in subflags and IS_CLASSVAR not in superflags: + if not class_obj and IS_CLASSVAR in subflags and IS_CLASSVAR not in superflags: self.note( "Protocol member {}.{} expected instance variable," " got class variable".format(supertype.type.name, name), context, code=code, ) - if IS_CLASSVAR in superflags and IS_CLASSVAR not in subflags: + if not class_obj and IS_CLASSVAR in superflags and IS_CLASSVAR not in subflags: self.note( "Protocol member {}.{} expected class variable," " got instance variable".format(supertype.type.name, name), @@ -1919,6 +1959,13 @@ def report_protocol_problems( context, code=code, ) + if class_obj and IS_SETTABLE in superflags and IS_CLASSVAR not in subflags: + self.note( + "Only class variables allowed for class object access on protocols," + ' {} is an instance variable of "{}"'.format(name, subtype.type.name), + context, + code=code, + ) self.print_more(conflict_flags, context, OFFSET, MAX_ITEMS, code=code) def pretty_overload( @@ -1930,6 +1977,7 @@ def pretty_overload( add_class_or_static_decorator: bool = False, allow_dups: bool = False, code: ErrorCode | None = None, + skip_self: bool = False, ) -> None: for item in tp.items: self.note("@overload", context, offset=offset, allow_dups=allow_dups, code=code) @@ -1940,7 +1988,11 @@ def pretty_overload( self.note(decorator, context, offset=offset, allow_dups=allow_dups, code=code) self.note( - pretty_callable(item), context, offset=offset, allow_dups=allow_dups, code=code + pretty_callable(item, skip_self=skip_self), + context, + offset=offset, + allow_dups=allow_dups, + code=code, ) def print_more( @@ -2373,10 +2425,14 @@ def pretty_class_or_static_decorator(tp: CallableType) -> str | None: return None -def pretty_callable(tp: CallableType) -> str: +def pretty_callable(tp: CallableType, skip_self: bool = False) -> str: """Return a nice easily-readable representation of a callable type. For example: def [T <: int] f(self, x: int, y: T) -> None + + If skip_self is True, print an actual callable type, as it would appear + when bound on an instance/class, rather than how it would appear in the + defining statement. """ s = "" asterisk = False @@ -2420,7 +2476,11 @@ def [T <: int] f(self, x: int, y: T) -> None and hasattr(tp.definition, "arguments") ): definition_arg_names = [arg.variable.name for arg in tp.definition.arguments] - if len(definition_arg_names) > len(tp.arg_names) and definition_arg_names[0]: + if ( + len(definition_arg_names) > len(tp.arg_names) + and definition_arg_names[0] + and not skip_self + ): if s: s = ", " + s s = definition_arg_names[0] + s @@ -2487,7 +2547,9 @@ def get_missing_protocol_members(left: Instance, right: Instance) -> list[str]: return missing -def get_conflict_protocol_types(left: Instance, right: Instance) -> list[tuple[str, Type, Type]]: +def get_conflict_protocol_types( + left: Instance, right: Instance, class_obj: bool = False +) -> list[tuple[str, Type, Type]]: """Find members that are defined in 'left' but have incompatible types. Return them as a list of ('member', 'got', 'expected'). """ @@ -2498,7 +2560,7 @@ def get_conflict_protocol_types(left: Instance, right: Instance) -> list[tuple[s continue supertype = find_member(member, right, left) assert supertype is not None - subtype = find_member(member, left, left) + subtype = find_member(member, left, left, class_obj=class_obj) if not subtype: continue is_compat = is_subtype(subtype, supertype, ignore_pos_arg_names=True) @@ -2510,7 +2572,7 @@ def get_conflict_protocol_types(left: Instance, right: Instance) -> list[tuple[s def get_bad_protocol_flags( - left: Instance, right: Instance + left: Instance, right: Instance, class_obj: bool = False ) -> list[tuple[str, set[int], set[int]]]: """Return all incompatible attribute flags for members that are present in both 'left' and 'right'. @@ -2536,6 +2598,9 @@ def get_bad_protocol_flags( and IS_SETTABLE not in subflags or IS_CLASS_OR_STATIC in superflags and IS_CLASS_OR_STATIC not in subflags + or class_obj + and IS_SETTABLE in superflags + and IS_CLASSVAR not in subflags ): bad_flags.append((name, subflags, superflags)) return bad_flags diff --git a/mypy/subtypes.py b/mypy/subtypes.py index 6c0fd6510e7f..3aefa315db9e 100644 --- a/mypy/subtypes.py +++ b/mypy/subtypes.py @@ -668,6 +668,14 @@ def visit_callable_type(self, left: CallableType) -> bool: assert call is not None if self._is_subtype(left, call): return True + if right.type.is_protocol and left.is_type_obj(): + ret_type = get_proper_type(left.ret_type) + if isinstance(ret_type, TupleType): + ret_type = mypy.typeops.tuple_fallback(ret_type) + if isinstance(ret_type, Instance) and is_protocol_implementation( + ret_type, right, proper_subtype=self.proper_subtype, class_obj=True + ): + return True return self._is_subtype(left.fallback, right) elif isinstance(right, TypeType): # This is unsound, we don't check the __init__ signature. @@ -897,6 +905,10 @@ def visit_type_type(self, left: TypeType) -> bool: if isinstance(item, TypeVarType): item = get_proper_type(item.upper_bound) if isinstance(item, Instance): + if right.type.is_protocol and is_protocol_implementation( + item, right, proper_subtype=self.proper_subtype, class_obj=True + ): + return True metaclass = item.type.metaclass_type return metaclass is not None and self._is_subtype(metaclass, right) return False @@ -916,7 +928,7 @@ def pop_on_exit(stack: list[tuple[T, T]], left: T, right: T) -> Iterator[None]: def is_protocol_implementation( - left: Instance, right: Instance, proper_subtype: bool = False + left: Instance, right: Instance, proper_subtype: bool = False, class_obj: bool = False ) -> bool: """Check whether 'left' implements the protocol 'right'. @@ -959,7 +971,19 @@ def f(self) -> A: ... # We always bind self to the subtype. (Similarly to nominal types). supertype = get_proper_type(find_member(member, right, left)) assert supertype is not None - subtype = get_proper_type(find_member(member, left, left)) + if member == "__call__" and class_obj: + # Special case: class objects always have __call__ that is just the constructor. + # TODO: move this helper function to typeops.py? + import mypy.checkmember + + def named_type(fullname: str) -> Instance: + return Instance(left.type.mro[-1], []) + + subtype: ProperType | None = mypy.checkmember.type_object_type( + left.type, named_type + ) + else: + subtype = get_proper_type(find_member(member, left, left, class_obj=class_obj)) # Useful for debugging: # print(member, 'of', left, 'has type', subtype) # print(member, 'of', right, 'has type', supertype) @@ -986,14 +1010,19 @@ def f(self) -> A: ... if isinstance(subtype, NoneType) and isinstance(supertype, CallableType): # We want __hash__ = None idiom to work even without --strict-optional return False - subflags = get_member_flags(member, left.type) + subflags = get_member_flags(member, left.type, class_obj=class_obj) superflags = get_member_flags(member, right.type) if IS_SETTABLE in superflags: # Check opposite direction for settable attributes. if not is_subtype(supertype, subtype): return False - if (IS_CLASSVAR in subflags) != (IS_CLASSVAR in superflags): - return False + if not class_obj: + if (IS_CLASSVAR in subflags) != (IS_CLASSVAR in superflags): + return False + else: + if IS_SETTABLE in superflags and IS_CLASSVAR not in subflags: + # Only class variables are allowed for class object access. + return False if IS_SETTABLE in superflags and IS_SETTABLE not in subflags: return False # This rule is copied from nominal check in checker.py @@ -1014,7 +1043,7 @@ def f(self) -> A: ... def find_member( - name: str, itype: Instance, subtype: Type, is_operator: bool = False + name: str, itype: Instance, subtype: Type, is_operator: bool = False, class_obj: bool = False ) -> Type | None: """Find the type of member by 'name' in 'itype's TypeInfo. @@ -1027,23 +1056,24 @@ def find_member( method = info.get_method(name) if method: if isinstance(method, Decorator): - return find_node_type(method.var, itype, subtype) + return find_node_type(method.var, itype, subtype, class_obj=class_obj) if method.is_property: assert isinstance(method, OverloadedFuncDef) dec = method.items[0] assert isinstance(dec, Decorator) - return find_node_type(dec.var, itype, subtype) - return find_node_type(method, itype, subtype) + return find_node_type(dec.var, itype, subtype, class_obj=class_obj) + return find_node_type(method, itype, subtype, class_obj=class_obj) else: # don't have such method, maybe variable or decorator? node = info.get(name) v = node.node if node else None if isinstance(v, Var): - return find_node_type(v, itype, subtype) + return find_node_type(v, itype, subtype, class_obj=class_obj) if ( not v and name not in ["__getattr__", "__setattr__", "__getattribute__"] and not is_operator + and not class_obj ): for method_name in ("__getattribute__", "__getattr__"): # Normally, mypy assumes that instances that define __getattr__ have all @@ -1068,7 +1098,7 @@ def find_member( return None -def get_member_flags(name: str, info: TypeInfo) -> set[int]: +def get_member_flags(name: str, info: TypeInfo, class_obj: bool = False) -> set[int]: """Detect whether a member 'name' is settable, whether it is an instance or class variable, and whether it is class or static method. @@ -1103,11 +1133,15 @@ def get_member_flags(name: str, info: TypeInfo) -> set[int]: flags = {IS_SETTABLE} if v.is_classvar: flags.add(IS_CLASSVAR) + if class_obj and v.is_inferred: + flags.add(IS_CLASSVAR) return flags return set() -def find_node_type(node: Var | FuncBase, itype: Instance, subtype: Type) -> Type: +def find_node_type( + node: Var | FuncBase, itype: Instance, subtype: Type, class_obj: bool = False +) -> Type: """Find type of a variable or method 'node' (maybe also a decorated method). Apply type arguments from 'itype', and bind 'self' to 'subtype'. """ @@ -1129,10 +1163,16 @@ def find_node_type(node: Var | FuncBase, itype: Instance, subtype: Type) -> Type and not node.is_staticmethod ): assert isinstance(p_typ, FunctionLike) - signature = bind_self( - p_typ, subtype, is_classmethod=isinstance(node, Var) and node.is_classmethod - ) - if node.is_property: + if class_obj and not ( + node.is_class if isinstance(node, FuncBase) else node.is_classmethod + ): + # Don't bind instance methods on class objects. + signature = p_typ + else: + signature = bind_self( + p_typ, subtype, is_classmethod=isinstance(node, Var) and node.is_classmethod + ) + if node.is_property and not class_obj: assert isinstance(signature, CallableType) typ = signature.ret_type else: diff --git a/test-data/unit/check-protocols.test b/test-data/unit/check-protocols.test index 36e4959852f7..90276ebae972 100644 --- a/test-data/unit/check-protocols.test +++ b/test-data/unit/check-protocols.test @@ -3138,3 +3138,382 @@ foo(B()) # E: Argument 1 to "foo" has incompatible type "B"; expected "Template # N: Following member(s) of "B" have conflicts: \ # N: Meta: expected "Type[__main__.Template.Meta]", got "Type[__main__.B.Meta]" foo(C()) # OK + +[case testProtocolClassObjectAttribute] +from typing import ClassVar, Protocol + +class P(Protocol): + foo: int + +class A: + foo = 42 +class B: + foo: ClassVar[int] +class C: + foo: ClassVar[str] +class D: + foo: int + +def test(arg: P) -> None: ... +test(A) # OK +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: foo: expected "int", got "str" +test(D) # E: Argument 1 to "test" has incompatible type "Type[D]"; expected "P" \ + # N: Only class variables allowed for class object access on protocols, foo is an instance variable of "D" + +[case testProtocolClassObjectPropertyRejected] +from typing import Protocol + +class P(Protocol): + @property + def foo(self) -> int: ... + +class B: + @property + def foo(self) -> int: ... + +def test(arg: P) -> None: ... +# TODO: give better diagnostics in this case. +test(B) # E: Argument 1 to "test" has incompatible type "Type[B]"; expected "P" \ + # N: Following member(s) of "B" have conflicts: \ + # N: foo: expected "int", got "Callable[[B], int]" +[builtins fixtures/property.pyi] + +[case testProtocolClassObjectInstanceMethod] +from typing import Any, Protocol + +class P(Protocol): + def foo(self, obj: Any) -> int: ... + +class B: + def foo(self) -> int: ... +class C: + def foo(self) -> str: ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def foo(obj: Any) -> int \ + # N: Got: \ + # N: def foo(self: C) -> str + +[case testProtocolClassObjectInstanceMethodArg] +from typing import Any, Protocol + +class P(Protocol): + def foo(self, obj: B) -> int: ... + +class B: + def foo(self) -> int: ... +class C: + def foo(self) -> int: ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def foo(obj: B) -> int \ + # N: Got: \ + # N: def foo(self: C) -> int + +[case testProtocolClassObjectInstanceMethodOverloaded] +from typing import Any, Protocol, overload + +class P(Protocol): + @overload + def foo(self, obj: Any, arg: int) -> int: ... + @overload + def foo(self, obj: Any, arg: str) -> str: ... + +class B: + @overload + def foo(self, arg: int) -> int: ... + @overload + def foo(self, arg: str) -> str: ... + def foo(self, arg: Any) -> Any: + ... + +class C: + @overload + def foo(self, arg: int) -> int: ... + @overload + def foo(self, arg: str) -> int: ... + def foo(self, arg: Any) -> Any: + ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: @overload \ + # N: def foo(obj: Any, arg: int) -> int \ + # N: @overload \ + # N: def foo(obj: Any, arg: str) -> str \ + # N: Got: \ + # N: @overload \ + # N: def foo(self: C, arg: int) -> int \ + # N: @overload \ + # N: def foo(self: C, arg: str) -> int + +[case testProtocolClassObjectClassMethod] +from typing import Protocol + +class P(Protocol): + def foo(self) -> int: ... + +class B: + @classmethod + def foo(cls) -> int: ... +class C: + @classmethod + def foo(cls) -> str: ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def foo() -> int \ + # N: Got: \ + # N: def foo() -> str +[builtins fixtures/classmethod.pyi] + +[case testProtocolClassObjectStaticMethod] +from typing import Protocol + +class P(Protocol): + def foo(self) -> int: ... + +class B: + @staticmethod + def foo() -> int: ... +class C: + @staticmethod + def foo() -> str: ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def foo() -> int \ + # N: Got: \ + # N: def foo() -> str +[builtins fixtures/staticmethod.pyi] + +[case testProtocolClassObjectGenericInstanceMethod] +from typing import Any, Protocol, Generic, List, TypeVar + +class P(Protocol): + def foo(self, obj: Any) -> List[int]: ... + +T = TypeVar("T") +class A(Generic[T]): + def foo(self) -> T: ... +class AA(A[List[T]]): ... + +class B(AA[int]): ... +class C(AA[str]): ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def foo(obj: Any) -> List[int] \ + # N: Got: \ + # N: def foo(self: A[List[str]]) -> List[str] +[builtins fixtures/list.pyi] + +[case testProtocolClassObjectGenericClassMethod] +from typing import Any, Protocol, Generic, List, TypeVar + +class P(Protocol): + def foo(self) -> List[int]: ... + +T = TypeVar("T") +class A(Generic[T]): + @classmethod + def foo(self) -> T: ... +class AA(A[List[T]]): ... + +class B(AA[int]): ... +class C(AA[str]): ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def foo() -> List[int] \ + # N: Got: \ + # N: def foo() -> List[str] +[builtins fixtures/isinstancelist.pyi] + +[case testProtocolClassObjectSelfTypeInstanceMethod] +from typing import Protocol, TypeVar, Union + +T = TypeVar("T") +class P(Protocol): + def foo(self, arg: T) -> T: ... + +class B: + def foo(self: T) -> T: ... +class C: + def foo(self: T) -> Union[T, int]: ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def [T] foo(arg: T) -> T \ + # N: Got: \ + # N: def [T] foo(self: T) -> Union[T, int] + +[case testProtocolClassObjectSelfTypeClassMethod] +from typing import Protocol, Type, TypeVar + +T = TypeVar("T") +class P(Protocol): + def foo(self) -> B: ... + +class B: + @classmethod + def foo(cls: Type[T]) -> T: ... +class C: + @classmethod + def foo(cls: Type[T]) -> T: ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def foo() -> B \ + # N: Got: \ + # N: def foo() -> C +[builtins fixtures/classmethod.pyi] + +[case testProtocolClassObjectAttributeAndCall] +from typing import Any, ClassVar, Protocol + +class P(Protocol): + foo: int + def __call__(self, x: int, y: int) -> Any: ... + +class B: + foo: ClassVar[int] + def __init__(self, x: int, y: int) -> None: ... +class C: + foo: ClassVar[int] + def __init__(self, x: int, y: str) -> None: ... + +def test(arg: P) -> None: ... +test(B) # OK +test(C) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: "C" has constructor incompatible with "__call__" of "P" + +[case testProtocolTypeTypeAttribute] +from typing import ClassVar, Protocol, Type + +class P(Protocol): + foo: int + +class A: + foo = 42 +class B: + foo: ClassVar[int] +class C: + foo: ClassVar[str] +class D: + foo: int + +def test(arg: P) -> None: ... +a: Type[A] +b: Type[B] +c: Type[C] +d: Type[D] +test(a) # OK +test(b) # OK +test(c) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: foo: expected "int", got "str" +test(d) # E: Argument 1 to "test" has incompatible type "Type[D]"; expected "P" \ + # N: Only class variables allowed for class object access on protocols, foo is an instance variable of "D" + +[case testProtocolTypeTypeInstanceMethod] +from typing import Any, Protocol, Type + +class P(Protocol): + def foo(self, cls: Any) -> int: ... + +class B: + def foo(self) -> int: ... +class C: + def foo(self) -> str: ... + +def test(arg: P) -> None: ... +b: Type[B] +c: Type[C] +test(b) # OK +test(c) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def foo(cls: Any) -> int \ + # N: Got: \ + # N: def foo(self: C) -> str + +[case testProtocolTypeTypeClassMethod] +from typing import Protocol, Type + +class P(Protocol): + def foo(self) -> int: ... + +class B: + @classmethod + def foo(cls) -> int: ... +class C: + @classmethod + def foo(cls) -> str: ... + +def test(arg: P) -> None: ... +b: Type[B] +c: Type[C] +test(b) # OK +test(c) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def foo() -> int \ + # N: Got: \ + # N: def foo() -> str +[builtins fixtures/classmethod.pyi] + +[case testProtocolTypeTypeSelfTypeInstanceMethod] +from typing import Protocol, Type, TypeVar, Union + +T = TypeVar("T") +class P(Protocol): + def foo(self, arg: T) -> T: ... + +class B: + def foo(self: T) -> T: ... +class C: + def foo(self: T) -> Union[T, int]: ... + +def test(arg: P) -> None: ... +b: Type[B] +c: Type[C] +test(b) # OK +test(c) # E: Argument 1 to "test" has incompatible type "Type[C]"; expected "P" \ + # N: Following member(s) of "C" have conflicts: \ + # N: Expected: \ + # N: def [T] foo(arg: T) -> T \ + # N: Got: \ + # N: def [T] foo(self: T) -> Union[T, int]