[ty] Do not consider a type T to satisfy a method member on a protocol unless the method is available on the meta-type of T#19187
Conversation
281351a to
b1b4faf
Compare
This comment was marked as outdated.
This comment was marked as outdated.
b1b4faf to
e78054d
Compare
|
CodSpeed WallTime Performance ReportMerging #19187 will not alter performanceComparing Summary
|
e78054d to
fbd12a6
Compare
Primer analysis
|
|
| Lint rule | Added | Removed | Changed |
|---|---|---|---|
invalid-argument-type |
0 | 1 | 1 |
invalid-assignment |
2 | 0 | 0 |
unsupported-operator |
2 | 0 | 0 |
| Total | 4 | 1 | 1 |
…col unless the method is available on the meta-type of `T`
5960688 to
25b9d54
Compare
|
(I updated my description in the PR summary of the performance characteristics of this PR, which are now pretty different after rebasing it on top of #19230) |
|
Sorry that I didn't get to this sooner! I never quite got to the bottom of my post-vacation review queue last week, and unfortunately I was tackling the notifications top-down, so this PR suffered from not being noisy enough, recently enough. Thanks for the reminder! Neither mypy nor pyright enforce this rule; they seem to instead avoid the After reading through the description here, and the ecosystem analysis, I admit I am not convinced that we should enforce this rule, either. The ecosystem false positives on psycopg and itsdangerous seem really problematic to me, and I don't find the arguments presented in favor of enforcing this rule convincing. Specifically, I think that both So my take would be that we should not allow an instance-only attribute to satisfy a dunder-method member of a Protocol, but we should otherwise allow instance-only attributes to satisfy Protocol method members. If I'm reading the issue and the ecosystem analysis correctly, I think that approach would fix all the false negatives fixed by this PR (and would fix the Iterable-assignability problem), without triggering any of the new false positives. |
|
(requested ping!) |
|
Haha I just went and looked it up right away so you wouldn't have to do that, but thank you 😆 Will review it shortly, after I record some of the other conclusions from our discussion so I don't forget them. |
carljm
left a comment
There was a problem hiding this comment.
We discussed this in person, and I'm open to try this approach. If we execute the descriptor protocol on accessing a method from a Protocol type, then it makes sense to require that it exist on the meta-type.
…col unless the method is available on the meta-type of `T` (#19187)
Summary
A callable instance attribute is not sufficient for a type to satisfy a protocol with a method member: a method member specified by a protocol
Pmust exist on the meta-type ofTforTto be a subtype ofP:There are several reasons why we must enforce this rule:
Some methods, such as dunder methods, are always looked up on the class directly. If a class with an
__iter__instance attribute satisfied theIterableprotocol, for example, theIterableprotocol would not accurately describe the requirements Python has for a class to be iterable at runtime. We could apply different rules for dunder method members as opposed to non-dunder method members, but I worry that this would appear inconsistent and would confuse users.Allowing callable instance attributes to satisfy method members of protocols would make
issubclass()narrowing of runtime-checkable protocols unsound, as theissubclass()mechanism at runtime for protocols only checks whether a method is accessible on the class object, not the instance. (Protocols with non-method members cannot be passed toissubclass()at all at runtime.)If we allowed an instance-only attribute to satisfy a method member on a protocol, it would make
type[]types unsound for protocols. For example, this currently type-checks fine onmain, but it crashes at runtime; under this PR, it no longer type-checks:Enforcing this rule fixes astral-sh/ty#764, because the inconsistency between our understanding of
Iterableassignability and types thatType::try_iterate()returnedOk()for is now fixed. Many types that we previously incorrectly considered assignable toIterableare now no longer considered assignable toIterable.Test plan
I added mdtests to
protocol.mdexplaining and enforcing this ruleI added a corpus test for [panic] ty crashes with UnboundIterAndGetitemError on Iterable types during type checking ty#764 that ensures that the crash is fixed
In a PR based on top of this one ([ty] Move
zope.interfacetogood.txtfor primer runs #19208), I movedzope.interfaceto thegood.txtlist in mypy_primer, and confirmed that this PR means we no longer crash when analyzing that codebase. (Previously, we crashed due to [panic] ty crashes with UnboundIterAndGetitemError on Iterable types during type checking ty#764.)I analyzed the mypy_primer report in [ty] Do not consider a type
Tto satisfy a method member on a protocol unless the method is available on the meta-type ofT#19187 (comment). Overall it LGTM.The fixes in this PR allow us to stabilise the property test added in [ty] Add a new property test: all types assignable to
Iterable[object]should be considered iterable #19186. I checked that it is stable locally by runningQUICKCHECK_TESTS=1000000 cargo test --release -p ty_python_semantic -- --ignored types::property_tests::stableI also added some failing tests regarding class-literal types where a class has
AnyorUnknownin its MRO. I think there's an argument that these should be consideredIterableactually, but not because they might have an__iter__instance attribute. Rather, the metaclass of such a class might define an__iter__method, which would make all classes with that metaclass iterable. We can't come to any firm conclusion about what the metaclass of a class withAnyin its MRO might be, because theAnymight materialize to a type with a custom metaclass.For now, I defer the question of fixing this, but I have a draft PR open to explore fixing it: [ty] Intersect with a dynamic type when calculating the metaclass of a class if that class has a dynamic type in its MRO #19157. It has some... surprising primer results. I need to do some more digging there to figure out what's going on. I suspect that there is yet another underlying bug being uncovered there. 😆
Performance
There's a performance regression on this PR, but not a huge one. I think that's sort-of unavoidable; we're again just doing slightly more work than we did before. #19230 will more than reclaim the performance hit here, though, if that PR is accepted.After rebasing on #19230, this PR now shows speedups of 3% on some benchmarks, and slowdowns of 2% on others. Overall the picture looks pretty positive to me, but it's hard to make out much signal here: https://codspeed.io/astral-sh/ruff/branches/alex%2Fmethod-metatype?runnerMode=Instrumentation