Skip to content

[ty] Support @classmethod and @staticmethod protocol members#26331

Draft
charliermarsh wants to merge 39 commits into
charlie/protocol-property-check-2from
charlie/protocol-decorated-method-members
Draft

[ty] Support @classmethod and @staticmethod protocol members#26331
charliermarsh wants to merge 39 commits into
charlie/protocol-property-check-2from
charlie/protocol-decorated-method-members

Conversation

@charliermarsh

Copy link
Copy Markdown
Member

Summary

This PR builds on #25332 to add support for protocol members declared with @classmethod or @staticmethod.

We now distinguish regular instance methods from class/static methods when deriving a protocol member's access capabilities. A regular method exposes a bound callable through an instance and an unbound callable through the class; a classmethod or staticmethod exposes the same descriptor-resolved callable through both. This lets us check both access paths without treating decorated methods as Todo.

Classmethods and staticmethods are structurally equivalent when their exposed signatures match, so either can satisfy either protocol form:

class Parser(Protocol):
    @classmethod
    def parse(cls, value: str) -> int: ...

class Implementation:
    @staticmethod
    def parse(value: str) -> int: ...

The same model supports overloaded decorated methods and binds Self to the implementation type. Regular instance methods do not satisfy these members because their class-level access remains unbound. General type[P] and meta-protocol support remains outside the scope of this change.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label Jun 24, 2026
@charliermarsh charliermarsh force-pushed the charlie/protocol-decorated-method-members branch from f94a50c to 2b1d82c Compare June 24, 2026 15:58
@charliermarsh charliermarsh force-pushed the charlie/protocol-property-check-2 branch from fe5b312 to 6a4d154 Compare June 24, 2026 15:58
@astral-sh-bot

astral-sh-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

Typing conformance results

No changes detected ✅

Current numbers
The percentage of diagnostics emitted that were expected errors held steady at 94.44%. The percentage of expected errors that received a diagnostic held steady at 90.21%. The number of fully passing files held steady at 95/134.

@astral-sh-bot

astral-sh-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

Memory usage report

Memory usage unchanged ✅

@astral-sh-bot

astral-sh-bot Bot commented Jun 24, 2026

Copy link
Copy Markdown

ecosystem-analyzer results

Lint rule Added Removed Changed
no-matching-overload 30 0 0
invalid-argument-type 10 0 0
Total 40 0 0

Flaky changes detected. This PR summary excludes flaky changes; see the HTML report for details.

Raw diff (40 changes)
pycryptodome (https://github.com/Legrandin/pycryptodome)
+ lib/Crypto/Cipher/PKCS1_OAEP.py:73:49 error[invalid-argument-type] Argument is incorrect: Expected `Hash | HashModule`, found `Unknown | <module 'Crypto.Hash.SHA1'>`
+ lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:291:40 error[invalid-argument-type] Argument to function `new` is incorrect: Expected `HashLikeClass | None`, found `<module 'Crypto.Hash.SHA1'>`
+ lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:308:40 error[invalid-argument-type] Argument to function `new` is incorrect: Expected `HashLikeClass | None`, found `<module 'Crypto.Hash.SHA1'>`
+ lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:342:49 error[invalid-argument-type] Argument to function `new` is incorrect: Expected `HashLikeClass | None`, found `<module 'Crypto.Hash.MD2'> | <module 'Crypto.Hash.MD5'> | <module 'Crypto.Hash.SHA1'> | <module 'Crypto.Hash.SHA256'> | <module 'Crypto.Hash.RIPEMD160'>`
+ lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:416:48 error[invalid-argument-type] Argument is incorrect: Expected `Hash | HashModule`, found `<module 'Crypto.Hash.SHA1'>`
+ lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:418:48 error[invalid-argument-type] Argument is incorrect: Expected `Hash | HashModule`, found `<module 'Crypto.Hash.SHA224'>`
+ lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:420:48 error[invalid-argument-type] Argument is incorrect: Expected `Hash | HashModule`, found `<module 'Crypto.Hash.SHA256'>`
+ lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:422:48 error[invalid-argument-type] Argument is incorrect: Expected `Hash | HashModule`, found `<module 'Crypto.Hash.SHA384'>`
+ lib/Crypto/SelfTest/Cipher/test_pkcs1_oaep.py:424:48 error[invalid-argument-type] Argument is incorrect: Expected `Hash | HashModule`, found `<module 'Crypto.Hash.SHA512'>`

scrapy (https://github.com/scrapy/scrapy)
+ tests/test_spidermiddleware_start.py:13:14 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_spidermiddleware_start.py:30:14 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ scrapy/core/scheduler.py:450:16 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ scrapy/core/scheduler.py:464:13 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloader_handler_twisted_ftp.py:70:14 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloader_handlers.py:123:28 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloader_handlers.py:164:23 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloader_handlers.py:189:18 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloader_handlers.py:316:9 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloader_handlers.py:322:28 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloadermiddleware_redirect.py:399:19 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloadermiddleware_redirect.py:419:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloadermiddleware_redirect.py:433:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloadermiddleware_redirect.py:456:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_downloadermiddleware_redirect_metarefresh.py:146:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:42:9 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:45:13 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:59:9 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:84:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:101:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:150:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:176:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:206:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:242:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:274:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:296:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_extension_throttle.py:326:10 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_pqueues.py:296:13 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_spidermiddleware_depth.py:69:14 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments
+ tests/test_spidermiddleware_urllength.py:54:9 error[no-matching-overload] No overload of function `build_from_crawler` matches arguments

static-frame (https://github.com/static-frame/static-frame)
+ static_frame/test/unit/test_bus.py:665:53 error[invalid-argument-type] Argument to function `StoreConfigMap.from_frames` is incorrect: Expected `FromFrameProtocol[StoreConfigBase]`, found `<class 'StoreConfigSQLite'>`

Full report with detailed diff (timing results)

@charliermarsh charliermarsh force-pushed the charlie/protocol-property-check-2 branch from 6a4d154 to 3ecc9e2 Compare June 24, 2026 17:21
@charliermarsh charliermarsh force-pushed the charlie/protocol-decorated-method-members branch from 28a6e0f to 2a7943d Compare June 24, 2026 17:21
@charliermarsh charliermarsh force-pushed the charlie/protocol-property-check-2 branch from f14590d to 68f7883 Compare June 24, 2026 17:44
@charliermarsh charliermarsh force-pushed the charlie/protocol-decorated-method-members branch from 2a7943d to d342416 Compare June 24, 2026 17:58
@charliermarsh charliermarsh force-pushed the charlie/protocol-property-check-2 branch from 68f7883 to 03ebd60 Compare June 24, 2026 19:53
@charliermarsh charliermarsh force-pushed the charlie/protocol-decorated-method-members branch from d342416 to 0b9d0b6 Compare June 24, 2026 20:16
@charliermarsh charliermarsh force-pushed the charlie/protocol-property-check-2 branch from 900a097 to 8cc09bf Compare June 24, 2026 22:00
@charliermarsh charliermarsh force-pushed the charlie/protocol-decorated-method-members branch from 0b9d0b6 to d1111c4 Compare June 24, 2026 22:04
@codspeed-hq

codspeed-hq Bot commented Jun 24, 2026

Copy link
Copy Markdown

Merging this PR will degrade performance by 6.68%

❌ 1 regressed benchmark
✅ 74 untouched benchmarks
⏩ 64 skipped benchmarks1

Warning

Please fix the performance issues or acknowledge them on CodSpeed.

Performance Changes

Mode Benchmark BASE HEAD Efficiency
Simulation DateType 201.6 ms 216.1 ms -6.68%

Tip

Investigate this regression with the CodSpeed MCP and your agent.


Comparing charlie/protocol-decorated-method-members (cf12531) with charlie/protocol-property-check-2 (8f86bcf)

Open in CodSpeed

Footnotes

  1. 64 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@charliermarsh charliermarsh force-pushed the charlie/protocol-property-check-2 branch 3 times, most recently from 5c6e1ad to dde5560 Compare June 25, 2026 16:58
@charliermarsh charliermarsh force-pushed the charlie/protocol-decorated-method-members branch from d1111c4 to f8556ec Compare June 25, 2026 17:00
@charliermarsh charliermarsh force-pushed the charlie/protocol-property-check-2 branch from dde5560 to 8f86bcf Compare June 25, 2026 17:03
@charliermarsh charliermarsh force-pushed the charlie/protocol-decorated-method-members branch from f8556ec to cf12531 Compare June 25, 2026 17:03
@charliermarsh charliermarsh force-pushed the charlie/protocol-property-check-2 branch 2 times, most recently from 385851c to 0e47141 Compare June 25, 2026 23:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants