Conversation
807a7e6 to
622f477
Compare
| conformance_automated = "Fail" | ||
| conformant = "Partial" | ||
| notes = """ | ||
| Only partially supports `@property` protocol members. |
There was a problem hiding this comment.
I am a ty maintainer. This description was outdated.
I am not making this change to make ty look better on the conformance tests. By disentangling the different aspects of the previous ConcreteHasProperty* tests, we just isolate the deficiency of ty more clearly.
| conformant = "Partial" | ||
| notes = """ | ||
| Only partially supports `@property` protocol members. | ||
| Does not reject incompatible non-generic implementations of generic protocol methods. |
There was a problem hiding this comment.
I think astral-sh/ty#2924 is the better reference here; astral-sh/ty#2323 is specifically discussing unsoundness of Self in protocols, which the spec actually mandates should be allowed. (Though it does get a bit muddied in the comments, since in ty Self is simply a bounded typevar.)
| class HasPropertyProto(Protocol): | ||
| class HasParentProperty(Protocol): | ||
| @property | ||
| def f(self: T) -> T: | ||
| ... | ||
|
|
||
| def m(self, item: T, callback: Callable[[T], str]) -> str: | ||
| def parent(self) -> Self: | ||
| ... |
There was a problem hiding this comment.
The existing tests here were convoluted (the ConcreteHasProperty* assignments failed for multiple reasons). It looks to me like they wanted to tests generic properties and generic methods at the same time and failed to do so. Looking back at the history, I can see that the assertion on the hp2: HasPropertyProto = ConcreteHasProperty2() line has changed from "OK" to "E", further illustrating that it's not really clear what was being tested there.
In the new version, I tried to stay close to the original (I can deviate further if that is desirable for educational reasons, for example), while disentangling the generic-property-related tests from the generic-method-related tests.
| class ConcreteHasProperty1: | ||
| class ConcreteParentProperty: | ||
| @property | ||
| def f(self: T) -> T: |
There was a problem hiding this comment.
If we really want to retain a test with a property that is generic over an unbounded type variable, I can add that again. Otherwise, it seems like using Self is sufficient here (which also makes that property generic)?
| Yes:: | ||
|
|
||
| _T = TypeVar("_T") | ||
| _T = TypeVar("_T", bound="Foo") |
There was a problem hiding this comment.
I can omit this, but it seemed sensible to add.
There was a problem hiding this comment.
Shouldn't this example be using Self?
|
|
||
| class Copyable(Protocol): | ||
| def copy[C: Copyable](self: C) -> C: | ||
| ... |
| ... | ||
|
|
||
| class One: | ||
| def copy(self) -> 'One': |
There was a problem hiding this comment.
This seems like it violates the generic type of Copyable.copy, since there's no "you can use Foo instead of Self" exception for arbitrary type variables in the spec.
622f477 to
c34f258
Compare
carljm
left a comment
There was a problem hiding this comment.
Looks good to me, but I'll wait for another reviewer before merging.
|
|
||
| class One: | ||
| def copy(self) -> 'One': | ||
| def copy(self) -> Self: |
There was a problem hiding this comment.
What's the rationale for this change? I think the original -> 'One' is already allowed, per the spec: the generics spec treats protocol Self as equivalent to a protocol-bounded TypeVar and allows implementations that return their concrete class. The new version with -> Self is also valid, but makes One and Other demonstrate closer-to-equivalent annotations instead of two distinct supported cases.
There was a problem hiding this comment.
See my inline annotation on the negative part of the diff:
This seems like it violates the generic type of Copyable.copy, since there's no "you can use Foo instead of Self" exception for arbitrary type variables in the spec.
You wrote:
the generics spec treats protocol
Selfas equivalent to a protocol-bounded TypeVar and allows implementations that return their concrete class.
The actual protocol in question here,Copyable, does not use Self. It uses a (properly bounded) typevar tough, so if we combine your quote of the spec with "Self is valid within Protocols [it] is treated equivalently to [bounded typevars]", then maybe one could conclude that returning One is also valid here.
It didn't seem clear from the spec to me, which is why I replaced it One with Self, but I can revert that.
There was a problem hiding this comment.
I reverted this change.
There was a problem hiding this comment.
I agree this is really unclear in the spec. It probably deserves a separate PR that is not just a conformance suite clarification, but an actual spec clarification/change with accompanying conformance suite updates.
(Sorry I missed your comment; I've found that much easier to do accidentally now that GitHub collapses them by default.)
| conformant = "Partial" | ||
| notes = """ | ||
| Only partially supports `@property` protocol members. | ||
| Does not reject incompatible non-generic implementations of generic protocol methods. |
There was a problem hiding this comment.
I think astral-sh/ty#2924 is the better reference here; astral-sh/ty#2323 is specifically discussing unsoundness of Self in protocols, which the spec actually mandates should be allowed. (Though it does get a bit muddied in the comments, since in ty Self is simply a bounded typevar.)
| Yes:: | ||
|
|
||
| _T = TypeVar("_T") | ||
| _T = TypeVar("_T", bound="Foo") |
There was a problem hiding this comment.
Shouldn't this example be using Self?
|
|
||
| class InvalidParentProperty: | ||
| @property | ||
| def parent(self) -> int: |
There was a problem hiding this comment.
a more interesting example might be having this return HasParentProperty
The conformance tests currently include a test in
protocols_generic.pywhich wants type checkers to accept assignability ofConcreteHasParentto theHasParentprotocol:However, the
get_parentmethod of the protocol seems to be more general than whatConcreteHasParentimplements. TheTtype variable is unbounded, while the implicitSelfannotation on the unannotatedselfreceived ofConcreteHasParent.get_parentshould be bounded byConcreteHasParent. The protocol should theoretically support calls like the following, throughtype[HasParent], whereasConcreteHasParentwould not:The spec includes a paragraph that states:
On the other hand, no type checker seems to do so (pyright assumes an implicit upper bound, but that's not supported by that statement in the spec). Given that, I think it would be best to just define an upper bound for the type variable here.
I also fixed that problem (using an unbounded type variable for as an annotation on a receiver parameter) elsewhere in the tests and the documentation, often by using
Selfinstead, if it seemed like the test was about something unrelated.