diff --git a/conformance/results/mypy/protocols_generic.toml b/conformance/results/mypy/protocols_generic.toml index 407eefd55..655434e55 100644 --- a/conformance/results/mypy/protocols_generic.toml +++ b/conformance/results/mypy/protocols_generic.toml @@ -16,25 +16,21 @@ protocols_generic.py:56: error: Incompatible types in assignment (expression has protocols_generic.py:66: error: Incompatible types in assignment (expression has type "Sender[int]", variable has type "Sender[float]") [assignment] protocols_generic.py:74: error: Incompatible types in assignment (expression has type "AttrProto[int]", variable has type "AttrProto[float]") [assignment] protocols_generic.py:75: error: Incompatible types in assignment (expression has type "AttrProto[float]", variable has type "AttrProto[int]") [assignment] -protocols_generic.py:145: error: Incompatible types in assignment (expression has type "ConcreteHasProperty2", variable has type "HasPropertyProto") [assignment] -protocols_generic.py:145: note: Following member(s) of "ConcreteHasProperty2" have conflicts: -protocols_generic.py:145: note: Expected: -protocols_generic.py:145: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str -protocols_generic.py:145: note: Got: -protocols_generic.py:145: note: def m(self, item: int, callback: Callable[[int], str]) -> str -protocols_generic.py:146: error: Incompatible types in assignment (expression has type "ConcreteHasProperty3", variable has type "HasPropertyProto") [assignment] -protocols_generic.py:146: note: Following member(s) of "ConcreteHasProperty3" have conflicts: -protocols_generic.py:146: note: f: expected "ConcreteHasProperty3", got "int" -protocols_generic.py:146: note: Expected: -protocols_generic.py:146: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str -protocols_generic.py:146: note: Got: -protocols_generic.py:146: note: def m(self, item: int, callback: Callable[[int], str]) -> str -protocols_generic.py:147: error: Incompatible types in assignment (expression has type "ConcreteHasProperty4", variable has type "HasPropertyProto") [assignment] -protocols_generic.py:147: note: Following member(s) of "ConcreteHasProperty4" have conflicts: -protocols_generic.py:147: note: Expected: -protocols_generic.py:147: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str -protocols_generic.py:147: note: Got: -protocols_generic.py:147: note: def m(self, item: str, callback: Callable[[int], str]) -> str +protocols_generic.py:133: error: Incompatible types in assignment (expression has type "InvalidParentProperty", variable has type "HasParentProperty") [assignment] +protocols_generic.py:133: note: Following member(s) of "InvalidParentProperty" have conflicts: +protocols_generic.py:133: note: parent: expected "InvalidParentProperty", got "HasParentProperty" +protocols_generic.py:153: error: Incompatible types in assignment (expression has type "InvalidHasMethod", variable has type "HasMethod") [assignment] +protocols_generic.py:153: note: Following member(s) of "InvalidHasMethod" have conflicts: +protocols_generic.py:153: note: Expected: +protocols_generic.py:153: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str +protocols_generic.py:153: note: Got: +protocols_generic.py:153: note: def m(self, item: int, callback: Callable[[int], str]) -> str +protocols_generic.py:185: error: Incompatible types in assignment (expression has type "InvalidGreaterThan", variable has type "HasGreaterThan") [assignment] +protocols_generic.py:185: note: Following member(s) of "InvalidGreaterThan" have conflicts: +protocols_generic.py:185: note: Expected: +protocols_generic.py:185: note: def __gt__(self, InvalidGreaterThan, /) -> bool +protocols_generic.py:185: note: Got: +protocols_generic.py:185: note: def __gt__(self, int, /) -> bool """ conformance_automated = "Pass" errors_diff = """ diff --git a/conformance/results/pycroscope/protocols_generic.toml b/conformance/results/pycroscope/protocols_generic.toml index be06306c6..5986ddd50 100644 --- a/conformance/results/pycroscope/protocols_generic.toml +++ b/conformance/results/pycroscope/protocols_generic.toml @@ -4,7 +4,7 @@ Treats global object as a literal. """ conformance_automated = "Fail" errors_diff = """ -Line 96: Unexpected errors ['./protocols_generic.py:96:12: Literal[] is not equivalent to protocols_generic.ConcreteHasParent'] +Line 107: Unexpected errors ['./protocols_generic.py:107:12: Literal[] is not equivalent to protocols_generic.ConcreteHasParent'] """ output = """ ./protocols_generic.py:40:0: Incompatible assignment: expected protocols_generic.Proto1[int, str], got protocols_generic.Concrete1 [incompatible_assignment] @@ -13,8 +13,8 @@ output = """ ./protocols_generic.py:66:4: Incompatible assignment: expected protocols_generic.Sender[float | int], got protocols_generic.Sender[int] [incompatible_assignment] ./protocols_generic.py:74:4: Incompatible assignment: expected protocols_generic.AttrProto[float | int], got protocols_generic.AttrProto[int] [incompatible_assignment] ./protocols_generic.py:75:4: Incompatible assignment: expected protocols_generic.AttrProto[int], got protocols_generic.AttrProto[float | int] [incompatible_assignment] -./protocols_generic.py:96:12: Literal[] is not equivalent to protocols_generic.ConcreteHasParent -./protocols_generic.py:145:0: Incompatible assignment: expected protocols_generic.HasPropertyProto, got protocols_generic.ConcreteHasProperty2 [incompatible_assignment] -./protocols_generic.py:146:0: Incompatible assignment: expected protocols_generic.HasPropertyProto, got protocols_generic.ConcreteHasProperty3 [incompatible_assignment] -./protocols_generic.py:147:0: Incompatible assignment: expected protocols_generic.HasPropertyProto, got protocols_generic.ConcreteHasProperty4 [incompatible_assignment] +./protocols_generic.py:107:12: Literal[] is not equivalent to protocols_generic.ConcreteHasParent +./protocols_generic.py:133:0: Incompatible assignment: expected protocols_generic.HasParentProperty, got protocols_generic.InvalidParentProperty [incompatible_assignment] +./protocols_generic.py:153:0: Incompatible assignment: expected protocols_generic.HasMethod, got protocols_generic.InvalidHasMethod [incompatible_assignment] +./protocols_generic.py:185:0: Incompatible assignment: expected protocols_generic.HasGreaterThan, got protocols_generic.InvalidGreaterThan [incompatible_assignment] """ diff --git a/conformance/results/pyrefly/protocols_generic.toml b/conformance/results/pyrefly/protocols_generic.toml index 634166922..f4344e4ff 100644 --- a/conformance/results/pyrefly/protocols_generic.toml +++ b/conformance/results/pyrefly/protocols_generic.toml @@ -9,7 +9,7 @@ ERROR protocols_generic.py:56:20-29: `Box[float]` is not assignable to `Box[int] ERROR protocols_generic.py:66:25-35: `Sender[int]` is not assignable to `Sender[float]` [bad-assignment] ERROR protocols_generic.py:74:28-36: `AttrProto[int]` is not assignable to `AttrProto[float]` [bad-assignment] ERROR protocols_generic.py:75:26-36: `AttrProto[float]` is not assignable to `AttrProto[int]` [bad-assignment] -ERROR protocols_generic.py:145:25-47: `ConcreteHasProperty2` is not assignable to `HasPropertyProto` [bad-assignment] -ERROR protocols_generic.py:146:25-47: `ConcreteHasProperty3` is not assignable to `HasPropertyProto` [bad-assignment] -ERROR protocols_generic.py:147:25-47: `ConcreteHasProperty4` is not assignable to `HasPropertyProto` [bad-assignment] +ERROR protocols_generic.py:133:26-49: `InvalidParentProperty` is not assignable to `HasParentProperty` [bad-assignment] +ERROR protocols_generic.py:153:18-36: `InvalidHasMethod` is not assignable to `HasMethod` [bad-assignment] +ERROR protocols_generic.py:185:23-43: `InvalidGreaterThan` is not assignable to `HasGreaterThan` [bad-assignment] """ diff --git a/conformance/results/pyright/protocols_generic.toml b/conformance/results/pyright/protocols_generic.toml index f19f84dae..5045dd70a 100644 --- a/conformance/results/pyright/protocols_generic.toml +++ b/conformance/results/pyright/protocols_generic.toml @@ -25,32 +25,27 @@ protocols_generic.py:74:28 - error: Type "AttrProto[int]" is not assignable to d protocols_generic.py:75:26 - error: Type "AttrProto[float]" is not assignable to declared type "AttrProto[int]"   "AttrProto[float]" is not assignable to "AttrProto[int]"     Type parameter "T@AttrProto" is invariant, but "float" is not the same as "int" (reportAssignmentType) -protocols_generic.py:145:25 - error: Type "ConcreteHasProperty2" is not assignable to declared type "HasPropertyProto" -  "ConcreteHasProperty2" is incompatible with protocol "HasPropertyProto" +protocols_generic.py:133:26 - error: Type "InvalidParentProperty" is not assignable to declared type "HasParentProperty" +  "InvalidParentProperty" is incompatible with protocol "HasParentProperty" +    "parent" is an incompatible type +      Type "() -> HasParentProperty" is not assignable to type "() -> InvalidParentProperty" +        "HasParentProperty" is not assignable to "InvalidParentProperty" +        Function return type "HasParentProperty" is incompatible with type "InvalidParentProperty" +          "HasParentProperty" is not assignable to "InvalidParentProperty" (reportAssignmentType) +protocols_generic.py:153:18 - error: Type "InvalidHasMethod" is not assignable to declared type "HasMethod" +  "InvalidHasMethod" is incompatible with protocol "HasMethod"     "m" is an incompatible type       Type "(item: int, callback: (int) -> str) -> str" is not assignable to type "(item: T@m, callback: (T@m) -> str) -> str"         Parameter 1: type "T@m" is incompatible with type "int"           "object*" is not assignable to "int"         Parameter 2: type "(T@m) -> str" is incompatible with type "(int) -> str"           Type "(T@m) -> str" is not assignable to type "(int) -> str" (reportAssignmentType) -protocols_generic.py:146:25 - error: Type "ConcreteHasProperty3" is not assignable to declared type "HasPropertyProto" -  "ConcreteHasProperty3" is incompatible with protocol "HasPropertyProto" -    "f" is an incompatible type -      Type "() -> int" is not assignable to type "() -> HasPropertyProto" -        Function return type "int" is incompatible with type "HasPropertyProto" -          "int" is incompatible with protocol "HasPropertyProto" -    "m" is an incompatible type -      Type "(item: int, callback: (int) -> str) -> str" is not assignable to type "(item: T@m, callback: (T@m) -> str) -> str" -        Parameter 1: type "T@m" is incompatible with type "int" - ... (reportAssignmentType) -protocols_generic.py:147:25 - error: Type "ConcreteHasProperty4" is not assignable to declared type "HasPropertyProto" -  "ConcreteHasProperty4" is incompatible with protocol "HasPropertyProto" -    "m" is an incompatible type -      Type "(item: str, callback: (int) -> str) -> str" is not assignable to type "(item: T@m, callback: (T@m) -> str) -> str" -        Parameter 1: type "T@m" is incompatible with type "str" -          "object*" is not assignable to "str" -        Parameter 2: type "(T@m) -> str" is incompatible with type "(int) -> str" -          Type "(T@m) -> str" is not assignable to type "(int) -> str" (reportAssignmentType) +protocols_generic.py:185:23 - error: Type "InvalidGreaterThan" is not assignable to declared type "HasGreaterThan" +  "InvalidGreaterThan" is incompatible with protocol "HasGreaterThan" +    "__gt__" is an incompatible type +      Type "(other: int) -> bool" is not assignable to type "(other: InvalidGreaterThan) -> bool" +        Parameter 1: type "InvalidGreaterThan" is incompatible with type "int" +          "InvalidGreaterThan" is not assignable to "int" (reportAssignmentType) """ conformance_automated = "Pass" errors_diff = """ diff --git a/conformance/results/results.html b/conformance/results/results.html index ebb94bf64..4083006ba 100644 --- a/conformance/results/results.html +++ b/conformance/results/results.html @@ -1365,7 +1365,7 @@

Python Type System Conformance Test Results

Partial
    -
  • Only partially supports @property protocol members.
  • +
  • Does not reject incompatible non-generic implementations of generic protocol methods.
Pass diff --git a/conformance/results/ty/protocols_generic.toml b/conformance/results/ty/protocols_generic.toml index 0d955245e..81b16bf33 100644 --- a/conformance/results/ty/protocols_generic.toml +++ b/conformance/results/ty/protocols_generic.toml @@ -1,10 +1,10 @@ conformance_automated = "Fail" conformant = "Partial" notes = """ -Only partially supports `@property` protocol members. +Does not reject incompatible non-generic implementations of generic protocol methods. """ errors_diff = """ -Line 144: Unexpected errors ['protocols_generic.py:144:25: error[invalid-assignment] Object of type `ConcreteHasProperty1` is not assignable to `HasPropertyProto`'] +Line 153: Expected 1 errors """ output = """ protocols_generic.py:40:24: error[invalid-assignment] Object of type `Concrete1` is not assignable to `Proto1[int, str]` @@ -13,8 +13,6 @@ protocols_generic.py:56:20: error[invalid-assignment] Object of type `Box[float] protocols_generic.py:66:25: error[invalid-assignment] Object of type `Sender[int]` is not assignable to `Sender[float]` protocols_generic.py:74:28: error[invalid-assignment] Object of type `AttrProto[int]` is not assignable to `AttrProto[float]` protocols_generic.py:75:26: error[invalid-assignment] Object of type `AttrProto[float]` is not assignable to `AttrProto[int]` -protocols_generic.py:144:25: error[invalid-assignment] Object of type `ConcreteHasProperty1` is not assignable to `HasPropertyProto` -protocols_generic.py:145:25: error[invalid-assignment] Object of type `ConcreteHasProperty2` is not assignable to `HasPropertyProto` -protocols_generic.py:146:25: error[invalid-assignment] Object of type `ConcreteHasProperty3` is not assignable to `HasPropertyProto` -protocols_generic.py:147:25: error[invalid-assignment] Object of type `ConcreteHasProperty4` is not assignable to `HasPropertyProto` +protocols_generic.py:133:26: error[invalid-assignment] Object of type `InvalidParentProperty` is not assignable to `HasParentProperty` +protocols_generic.py:185:23: error[invalid-assignment] Object of type `InvalidGreaterThan` is not assignable to `HasGreaterThan` """ diff --git a/conformance/results/zuban/protocols_generic.toml b/conformance/results/zuban/protocols_generic.toml index 102540dd1..570482769 100644 --- a/conformance/results/zuban/protocols_generic.toml +++ b/conformance/results/zuban/protocols_generic.toml @@ -17,23 +17,19 @@ protocols_generic.py:56: error: Incompatible types in assignment (expression has protocols_generic.py:66: error: Incompatible types in assignment (expression has type "Sender[int]", variable has type "Sender[float]") [assignment] protocols_generic.py:74: error: Incompatible types in assignment (expression has type "AttrProto[int]", variable has type "AttrProto[float]") [assignment] protocols_generic.py:75: error: Incompatible types in assignment (expression has type "AttrProto[float]", variable has type "AttrProto[int]") [assignment] -protocols_generic.py:145: error: Incompatible types in assignment (expression has type "ConcreteHasProperty2", variable has type "HasPropertyProto") [assignment] -protocols_generic.py:145: note: Following member(s) of "ConcreteHasProperty2" have conflicts: -protocols_generic.py:145: note: Expected: -protocols_generic.py:145: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str -protocols_generic.py:145: note: Got: -protocols_generic.py:145: note: def m(self, item: int, callback: Callable[[int], str]) -> str -protocols_generic.py:146: error: Incompatible types in assignment (expression has type "ConcreteHasProperty3", variable has type "HasPropertyProto") [assignment] -protocols_generic.py:146: note: Following member(s) of "ConcreteHasProperty3" have conflicts: -protocols_generic.py:146: note: f: expected "ConcreteHasProperty3", got "int" -protocols_generic.py:146: note: Expected: -protocols_generic.py:146: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str -protocols_generic.py:146: note: Got: -protocols_generic.py:146: note: def m(self, item: int, callback: Callable[[int], str]) -> str -protocols_generic.py:147: error: Incompatible types in assignment (expression has type "ConcreteHasProperty4", variable has type "HasPropertyProto") [assignment] -protocols_generic.py:147: note: Following member(s) of "ConcreteHasProperty4" have conflicts: -protocols_generic.py:147: note: Expected: -protocols_generic.py:147: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str -protocols_generic.py:147: note: Got: -protocols_generic.py:147: note: def m(self, item: str, callback: Callable[[int], str]) -> str +protocols_generic.py:133: error: Incompatible types in assignment (expression has type "InvalidParentProperty", variable has type "HasParentProperty") [assignment] +protocols_generic.py:133: note: Following member(s) of "InvalidParentProperty" have conflicts: +protocols_generic.py:133: note: parent: expected "InvalidParentProperty", got "HasParentProperty" +protocols_generic.py:153: error: Incompatible types in assignment (expression has type "InvalidHasMethod", variable has type "HasMethod") [assignment] +protocols_generic.py:153: note: Following member(s) of "InvalidHasMethod" have conflicts: +protocols_generic.py:153: note: Expected: +protocols_generic.py:153: note: def [T] m(self, item: T, callback: Callable[[T], str]) -> str +protocols_generic.py:153: note: Got: +protocols_generic.py:153: note: def m(self, item: int, callback: Callable[[int], str]) -> str +protocols_generic.py:185: error: Incompatible types in assignment (expression has type "InvalidGreaterThan", variable has type "HasGreaterThan") [assignment] +protocols_generic.py:185: note: Following member(s) of "InvalidGreaterThan" have conflicts: +protocols_generic.py:185: note: Expected: +protocols_generic.py:185: note: def [Self: InvalidGreaterThan] __gt__(self, other: Self) -> bool +protocols_generic.py:185: note: Got: +protocols_generic.py:185: note: def __gt__(self, other: int) -> bool """ diff --git a/conformance/tests/protocols_generic.py b/conformance/tests/protocols_generic.py index b417e00f6..be7525e56 100644 --- a/conformance/tests/protocols_generic.py +++ b/conformance/tests/protocols_generic.py @@ -75,8 +75,19 @@ def func3(attr_int: AttrProto[int], attr_float: AttrProto[float]): v2: AttrProto[int] = attr_float # E +# Specification: https://typing.readthedocs.io/en/latest/spec/protocol.html#self-types-in-protocols +# > The self-types in protocols follow the rules for other methods. +# Specification: https://typing.readthedocs.io/en/latest/spec/annotations.html#annotating-instance-and-class-methods +# > In addition, the first argument in an instance method can be annotated +# > with a type variable. In this case the return type may use the same +# > type variable, thus making that method a generic function. + + +T_bounded = TypeVar("T_bounded", bound="HasParent") + + class HasParent(Protocol): - def get_parent(self: T) -> T: + def get_parent(self: T_bounded) -> T_bounded: ... @@ -96,52 +107,79 @@ def get_parent(self) -> Self: assert_type(parent, ConcreteHasParent) -class HasPropertyProto(Protocol): - @property - def f(self: T) -> T: - ... +# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#use-in-protocols +# > `Self` is valid within Protocols, similar to its use in classes: - def m(self, item: T, callback: Callable[[T], str]) -> str: + +class HasParentProperty(Protocol): + @property + def parent(self) -> Self: ... -class ConcreteHasProperty1: +class ConcreteParentProperty: @property - def f(self: T) -> T: + def parent(self) -> Self: return self + +class InvalidParentProperty: + @property + def parent(self) -> HasParentProperty: + return ConcreteParentProperty() + + +hp1: HasParentProperty = ConcreteParentProperty() # OK +hp2: HasParentProperty = InvalidParentProperty() # E + + + +class HasMethod(Protocol): def m(self, item: T, callback: Callable[[T], str]) -> str: - return "" + ... -class ConcreteHasProperty2: - @property - def f(self) -> Self: - return self +class ConcreteHasMethod: + def m(self, item: T, callback: Callable[[T], str]) -> str: + return "" + +class InvalidHasMethod: def m(self, item: int, callback: Callable[[int], str]) -> str: return "" -class ConcreteHasProperty3: - @property - def f(self) -> int: - return 0 +hm1: HasMethod = ConcreteHasMethod() # OK +hm2: HasMethod = InvalidHasMethod() # E - def m(self, item: int, callback: Callable[[int], str]) -> str: - return "" +# Specification: https://typing.readthedocs.io/en/latest/spec/generics.html#use-in-protocols +# > Checking a class for assignability to a protocol: If a protocol uses `Self` +# > in methods or attribute annotations, then a class `Foo` is assignable +# > to the protocol if its corresponding methods and attribute annotations use +# > either `Self` or `Foo` or any of `Foo`'s subclasses. -class ConcreteHasProperty4: - @property - def f(self) -> Self: - return self - def m(self, item: str, callback: Callable[[int], str]) -> str: - return "" +class HasGreaterThan(Protocol): + def __gt__(self, other: Self) -> bool: + ... + + +class ConcreteGreaterThan1: + def __gt__(self, other: Self) -> bool: + return False + + +class ConcreteGreaterThan2: + def __gt__(self, other: "ConcreteGreaterThan2") -> bool: + return False + + +class InvalidGreaterThan: + def __gt__(self, other: int) -> bool: + return False -hp1: HasPropertyProto = ConcreteHasProperty1() # OK -hp2: HasPropertyProto = ConcreteHasProperty2() # E -hp3: HasPropertyProto = ConcreteHasProperty3() # E -hp4: HasPropertyProto = ConcreteHasProperty4() # E +hg1: HasGreaterThan = ConcreteGreaterThan1() # OK +hg2: HasGreaterThan = ConcreteGreaterThan2() # OK +hg3: HasGreaterThan = InvalidGreaterThan() # E diff --git a/docs/guides/writing_stubs.rst b/docs/guides/writing_stubs.rst index 3039e8160..c9c642aba 100644 --- a/docs/guides/writing_stubs.rst +++ b/docs/guides/writing_stubs.rst @@ -647,24 +647,23 @@ No:: def foo(x: X = X()) -> None: ... def bar(y: str = None) -> None: ... -Do not annotate ``self`` and ``cls`` in method definitions, except when -referencing a type variable. +Do not annotate ``self`` and ``cls`` in method definitions unless an explicit +annotation is needed to constrain or relax the receiver type. Yes:: - _T = TypeVar("_T") - class Foo: def bar(self) -> None: ... @classmethod - def create(cls: type[_T]) -> _T: ... + def create(cls) -> Self: ... No:: class Foo: - def bar(self: Foo) -> None: ... + def bar1(self: Self) -> None: ... + def bar2(self: Foo) -> None: ... @classmethod - def baz(cls: type[Foo]) -> int: ... + def create(cls: type[Self]) -> Self: ... The bodies of functions and methods should consist of only the ellipsis literal ``...`` on the same line as the closing parenthesis and colon. diff --git a/docs/spec/protocol.rst b/docs/spec/protocol.rst index 2803da0a6..bfa352aa2 100644 --- a/docs/spec/protocol.rst +++ b/docs/spec/protocol.rst @@ -365,6 +365,7 @@ The self-types in protocols follow the class Copyable(Protocol): def copy[C: Copyable](self: C) -> C: + ... class One: def copy(self) -> 'One':