-
Notifications
You must be signed in to change notification settings - Fork 1.4k
[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
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
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) |
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
P
must exist on the meta-type ofT
forT
to 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 theIterable
protocol, for example, theIterable
protocol 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
Iterable
assignability and types thatType::try_iterate()
returnedOk()
for is now fixed. Many types that we previously incorrectly considered assignable toIterable
are now no longer considered assignable toIterable
.Test plan
I added mdtests to
protocol.md
explaining 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.interface
togood.txt
for primer runs #19208), I movedzope.interface
to thegood.txt
list 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
T
to 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::stable
I also added some failing tests regarding class-literal types where a class has
Any
orUnknown
in its MRO. I think there's an argument that these should be consideredIterable
actually, 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 withAny
in its MRO might be, because theAny
might 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