Skip to content

Conversation

@AlexWaygood
Copy link
Member

@AlexWaygood AlexWaygood commented Nov 13, 2025

Summary

A basic implementation of Liskov assignability.

Currently we only check methods on subclasses: not attributes or properties. These should also obviously be checked as well, but they're deferred for now. Similarly, even for methods, there is much that is currently deferred: no checks are done for staticmethods or classmethods for now, and we only check definitions inferred as Type::FunctionLiterals.

The main thing missing from the checks implemented here is that the diagnostics don't do a good job of explaining why a particular override violates the Liskov Substitution Principle. Other type checkers do a great job at this: they'll tell you that the return annotation is invalid, or that you've changed a parameter name, etc. All of that is very difficult for us to implement until something like #19580 is implemented to propagate up information about why an assignability or subtyping check failed. I think we'll definitely need something like that eventually, but I'm loath to do that until the new constraint solver has landed, since it'll touch the same area of code and cause a lot of merge conflicts!

Another thing to note (which looks like it comes up a lot in the ecosystem) is that other type checkers are less strict than us in some respects. For example, pyright ignores Liskov issues stemming from a parameter on a method override having the wrong name if the method is a dunder method. We could consider pushing "parameter renaming issues" into a separate (disabled-by-default) error code, but once again this is very difficult to do without something like #19580.

Test Plan

Mdtests and snapshots

@AlexWaygood AlexWaygood added the ty Multi-file analysis & type inference label Nov 13, 2025
@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 13, 2025

Diagnostic diff on typing conformance tests

Changes were detected when running ty on typing conformance tests
--- old-output.txt	2025-11-18 19:14:13.775177666 +0000
+++ new-output.txt	2025-11-18 19:14:17.566209433 +0000
@@ -1,4 +1,4 @@
-fatal[panic] Panicked at /home/runner/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a885bb4/src/function/execute.rs:465:17 when checking `/home/runner/work/ruff/ruff/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(19ea3)): execute: too many cycle iterations`
+fatal[panic] Panicked at /home/runner/.cargo/git/checkouts/salsa-e6f3bb7c2a062968/a885bb4/src/function/execute.rs:465:17 when checking `/home/runner/work/ruff/ruff/typing/conformance/tests/aliases_typealiastype.py`: `infer_definition_types(Id(1a6a3)): execute: too many cycle iterations`
 _directives_deprecated_library.py:15:31: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `int`
 _directives_deprecated_library.py:30:26: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `str`
 _directives_deprecated_library.py:36:41: error[invalid-return-type] Function always implicitly returns `None`, which is not assignable to return type `Self@__add__`

@AlexWaygood AlexWaygood force-pushed the alex/basic-liskov branch 2 times, most recently from ec973f2 to cc3b9a8 Compare November 13, 2025 20:10
@codspeed-hq
Copy link

codspeed-hq bot commented Nov 13, 2025

CodSpeed Performance Report

Merging #21436 will degrade performances by 10.11%

Comparing alex/basic-liskov (c4b5edb) with main (192c37d)

Summary

❌ 4 regressions
✅ 18 untouched
⏩ 30 skipped1

⚠️ Please fix the performance issues or acknowledge them on CodSpeed.

Benchmarks breakdown

Mode Benchmark BASE HEAD Change
Simulation anyio 1.1 s 1.1 s -5.15%
WallTime medium[static-frame] 15.3 s 16.5 s -7.46%
WallTime multithreaded[altair] 559.5 ms 622.4 ms -10.11%
WallTime small[tanjun] 2.3 s 2.5 s -4.83%

Footnotes

  1. 30 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.

@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 13, 2025

mypy_primer results

Changes were detected when running on open source projects
more-itertools (https://github.com/more-itertools/more-itertools)
+ more_itertools/more.py:2278:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__contains__`: Definition is incompatible with `Sequence.__contains__`
+ more_itertools/more.py:2303:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `Sequence.__getitem__`
+ more_itertools/more.py:2383:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `index`: Definition is incompatible with `Sequence.index`
- more_itertools/more.pyi:502:45: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ more_itertools/more.pyi:487:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__contains__`: Definition is incompatible with `Sequence.__contains__`
+ more_itertools/more.pyi:492:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `Sequence.__getitem__`
- Found 29 diagnostics
+ Found 33 diagnostics

com2ann (https://github.com/ilevkivskyi/com2ann)
+ src/com2ann.py:155:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_Assign`: Definition is incompatible with `NodeVisitor.visit_Assign`
+ src/com2ann.py:191:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_For`: Definition is incompatible with `NodeVisitor.visit_For`
+ src/com2ann.py:194:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_AsyncFor`: Definition is incompatible with `NodeVisitor.visit_AsyncFor`
+ src/com2ann.py:197:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_With`: Definition is incompatible with `NodeVisitor.visit_With`
+ src/com2ann.py:200:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_AsyncWith`: Definition is incompatible with `NodeVisitor.visit_AsyncWith`
+ src/com2ann.py:208:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_FunctionDef`: Definition is incompatible with `NodeVisitor.visit_FunctionDef`
+ src/com2ann.py:211:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_AsyncFunctionDef`: Definition is incompatible with `NodeVisitor.visit_AsyncFunctionDef`
- Found 3 diagnostics
+ Found 10 diagnostics

aioredis (https://github.com/aio-libs/aioredis)
+ aioredis/client.py:146:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `update`: Definition is incompatible with `MutableMapping.update`
- Found 25 diagnostics
+ Found 26 diagnostics

parso (https://github.com/davidhalter/parso)
+ parso/python/errors.py:557:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:569:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:582:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:604:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_node`: Definition is incompatible with `Rule.get_node`
+ parso/python/errors.py:607:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:617:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:631:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:664:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:680:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:692:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_node`: Definition is incompatible with `Rule.get_node`
+ parso/python/errors.py:695:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:995:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:1038:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:1232:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:1238:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:1247:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:1254:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/errors.py:1265:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
+ parso/python/parser.py:101:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `convert_leaf`: Definition is incompatible with `BaseParser.convert_leaf`
+ parso/python/pep8.py:766:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `is_issue`: Definition is incompatible with `Rule.is_issue`
- Found 183 diagnostics
+ Found 203 diagnostics

nionutils (https://github.com/nion-software/nionutils)
+ nion/utils/Converter.py:112:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `convert_back`: Definition is incompatible with `ConverterLike.convert_back`
+ nion/utils/Converter.py:161:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `convert_back`: Definition is incompatible with `ConverterLike.convert_back`
+ nion/utils/Converter.py:172:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `convert_back`: Definition is incompatible with `ConverterLike.convert_back`
+ nion/utils/Converter.py:181:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `convert_back`: Definition is incompatible with `ConverterLike.convert_back`
+ nion/utils/Converter.py:192:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `convert_back`: Definition is incompatible with `ConverterLike.convert_back`
+ nion/utils/Converter.py:220:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `convert_back`: Definition is incompatible with `ConverterLike.convert_back`
+ nion/utils/Converter.py:254:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `convert_back`: Definition is incompatible with `ConverterLike.convert_back`
+ nion/utils/StructuredModel.py:256:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `MutableSequence.__getitem__`
+ nion/utils/StructuredModel.py:259:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__setitem__`: Definition is incompatible with `MutableSequence.__setitem__`
+ nion/utils/StructuredModel.py:262:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__delitem__`: Definition is incompatible with `MutableSequence.__delitem__`
+ nion/utils/StructuredModel.py:265:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__contains__`: Definition is incompatible with `Sequence.__contains__`
- Found 12 diagnostics
+ Found 23 diagnostics

kornia (https://github.com/kornia/kornia)
- kornia/constants.py:38:59: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- kornia/geometry/boxes.py:761:92: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- kornia/geometry/keypoints.py:214:37: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ kornia/geometry/vector.py:42:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `TensorWrapper.__getitem__`
+ kornia/geometry/vector.py:120:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `TensorWrapper.__getitem__`
- kornia/models/detection/base.py:178:19: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- kornia/models/edge_detection/base.py:102:19: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- kornia/models/super_resolution/base.py:117:19: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- Found 766 diagnostics
+ Found 762 diagnostics

bandersnatch (https://github.com/pypa/bandersnatch)
+ src/bandersnatch_storage_plugins/s3.py:417:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `symlink`: Definition is incompatible with `Storage.symlink`
- src/bandersnatch_storage_plugins/swift.py:344:31: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/bandersnatch_storage_plugins/swift.py:422:23: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/bandersnatch_storage_plugins/swift.py:947:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `symlink`: Definition is incompatible with `Storage.symlink`

asynq (https://github.com/quora/asynq)
+ asynq/debug.py:147:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `formatException`: Definition is incompatible with `Formatter.formatException`
+ asynq/debug.pyi:30:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `formatException`: Definition is incompatible with `Formatter.formatException`
- asynq/decorators.pyi:103:133: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ asynq/tests/test_contexts.py:38:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__exit__`: Definition is incompatible with `AsyncContext.__exit__`
+ asynq/tools.py:352:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `asyncio`: Definition is incompatible with `PureAsyncDecorator.asyncio`
- Found 170 diagnostics
+ Found 173 diagnostics

beartype (https://github.com/beartype/beartype)
- beartype/_check/metadata/hint/hintsmeta.py:311:58: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- beartype/_util/cache/pool/utilcachepoollistfixed.py:147:45: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ beartype/_util/cache/pool/utilcachepoollistfixed.py:251:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `append`: Definition is incompatible with `MutableSequence.append`
+ beartype/_util/cache/pool/utilcachepoollistfixed.py:261:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `extend`: Definition is incompatible with `MutableSequence.extend`
- beartype/claw/_importlib/_clawimpload.py:395:26: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ beartype/typing/_typingpep544.py:253:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__instancecheck__`: Definition is incompatible with `ABCMeta.__instancecheck__`

itsdangerous (https://github.com/pallets/itsdangerous)
- src/itsdangerous/timed.py:185:17: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/itsdangerous/timed.py:222:24: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- Found 4 diagnostics
+ Found 2 diagnostics

spack (https://github.com/spack/spack)
+ lib/spack/spack/audit.py:125:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `Sequence.__getitem__`
+ lib/spack/spack/cmd/common/arguments.py:104:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__call__`: Definition is incompatible with `Action.__call__`
+ lib/spack/spack/cmd/common/arguments.py:123:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__call__`: Definition is incompatible with `Action.__call__`
+ lib/spack/spack/cmd/common/arguments.py:158:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__call__`: Definition is incompatible with `Action.__call__`
+ lib/spack/spack/cmd/common/arguments.py:598:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__call__`: Definition is incompatible with `Action.__call__`
+ lib/spack/spack/cmd/info.py:160:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `format_name`: Definition is incompatible with `Formatter.format_name`
+ lib/spack/spack/cmd/info.py:163:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `format_values`: Definition is incompatible with `Formatter.format_values`
+ lib/spack/spack/cmd/info.py:539:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `format_name`: Definition is incompatible with `Formatter.format_name`
+ lib/spack/spack/cmd/info.py:544:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `format_values`: Definition is incompatible with `Formatter.format_values`
+ lib/spack/spack/cmd/info.py:556:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `format_description`: Definition is incompatible with `Formatter.format_description`
+ lib/spack/spack/llnl/util/filesystem.py:1961:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `Sequence.__getitem__`
+ lib/spack/spack/llnl/util/lang.py:967:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `MutableSequence.__getitem__`
+ lib/spack/spack/llnl/util/lang.py:970:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__setitem__`: Definition is incompatible with `MutableSequence.__setitem__`
+ lib/spack/spack/llnl/util/lang.py:973:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__delitem__`: Definition is incompatible with `MutableSequence.__delitem__`
+ lib/spack/spack/llnl/util/lang.py:979:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `insert`: Definition is incompatible with `MutableSequence.insert`
+ lib/spack/spack/repo.py:524:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `needs_update`: Definition is incompatible with `Indexer.needs_update`
+ lib/spack/spack/report.py:167:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `succeed`: Definition is incompatible with `SpecRecord.succeed`
+ lib/spack/spack/reporters/cdash.py:273:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `build_report`: Definition is incompatible with `Reporter.build_report`
+ lib/spack/spack/reporters/cdash.py:362:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `test_report`: Definition is incompatible with `Reporter.test_report`
+ lib/spack/spack/reporters/cdash.py:396:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `concretization_report`: Definition is incompatible with `Reporter.concretization_report`
- lib/spack/spack/util/prefix.py:52:47: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ lib/spack/spack/variant.py:581:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `Sequence.__getitem__`
+ lib/spack/spack/vendor/altgraph/GraphAlgo.py:165:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `setdefault`: Definition is incompatible with `MutableMapping.setdefault`
+ lib/spack/spack/vendor/jinja2/runtime.py:1033:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getattr__`: Definition is incompatible with `Undefined.__getattr__`
+ lib/spack/spack/vendor/markupsafe/__init__.py:83:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__add__`: Definition is incompatible with `str.__add__`
+ lib/spack/spack/vendor/markupsafe/__init__.py:95:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__mul__`: Definition is incompatible with `str.__mul__`
- lib/spack/spack/vendor/markupsafe/__init__.py:119:17: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- lib/spack/spack/vendor/markupsafe/__init__.py:126:18: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- lib/spack/spack/vendor/markupsafe/__init__.py:133:72: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ lib/spack/spack/vendor/markupsafe/__init__.py:101:5: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__rmul__`: Definition is incompatible with `str.__rmul__`
+ lib/spack/spack/vendor/markupsafe/__init__.py:103:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__mod__`: Definition is incompatible with `str.__mod__`
+ lib/spack/spack/vendor/markupsafe/__init__.py:114:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `join`: Definition is incompatible with `str.join`
+ lib/spack/spack/vendor/markupsafe/__init__.py:193:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `partition`: Definition is incompatible with `str.partition`
+ lib/spack/spack/vendor/markupsafe/__init__.py:198:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `rpartition`: Definition is incompatible with `str.rpartition`
+ lib/spack/spack/vendor/markupsafe/__init__.py:203:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `format`: Definition is incompatible with `str.format`
+ lib/spack/spack/vendor/pyrsistent/typing.pyi:100:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__contains__`: Definition is incompatible with `AbstractSet.__contains__`
+ lib/spack/spack/vendor/ruamel/yaml/comments.py:554:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `insert`: Definition is incompatible with `MutableSequence.insert`
+ lib/spack/spack/vendor/ruamel/yaml/comments.py:563:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `extend`: Definition is incompatible with `MutableSequence.extend`
- lib/spack/spack/vendor/ruamel/yaml/constructor.py:1396:61: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ lib/spack/spack/vendor/ruamel/yaml/representer.py:967:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `represent_set`: Definition is incompatible with `SafeRepresenter.represent_set`
- Found 7913 diagnostics
+ Found 7943 diagnostics

isort (https://github.com/pycqa/isort)
- isort/io.py:69:58: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- Found 37 diagnostics
+ Found 36 diagnostics

websockets (https://github.com/aaugustin/websockets)
+ src/websockets/datastructures.py:132:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `update`: Definition is incompatible with `MutableMapping.update`
- Found 41 diagnostics
+ Found 42 diagnostics

pip (https://github.com/pypa/pip)
+ src/pip/_internal/network/auth.py:440:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__call__`: Definition is incompatible with `AuthBase.__call__`
- src/pip/_vendor/cachecontrol/adapter.py:81:26: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/pip/_vendor/cachecontrol/heuristics.py:124:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `update_headers`: Definition is incompatible with `BaseHeuristic.update_headers`
+ src/pip/_vendor/cachecontrol/heuristics.py:156:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `warning`: Definition is incompatible with `BaseHeuristic.warning`
+ src/pip/_vendor/distlib/compat.py:970:13: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `MutableSequence.__getitem__`
+ src/pip/_vendor/distlib/compat.py:982:13: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `pop`: Definition is incompatible with `MutableSequence.pop`
+ src/pip/_vendor/distlib/compat.py:994:13: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `Sequence.__getitem__`
+ src/pip/_vendor/idna/codec.py:11:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `encode`: Definition is incompatible with `codecs.Codec.encode`
+ src/pip/_vendor/idna/codec.py:20:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `decode`: Definition is incompatible with `codecs.Codec.decode`
+ src/pip/_vendor/idna/codec.py:31:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `_buffer_encode`: Definition is incompatible with `BufferedIncrementalEncoder._buffer_encode`
+ src/pip/_vendor/idna/codec.py:65:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `_buffer_decode`: Definition is incompatible with `BufferedIncrementalDecoder._buffer_decode`
+ src/pip/_vendor/pkg_resources/__init__.py:2128:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `_has`: Definition is incompatible with `NullProvider._has`
+ src/pip/_vendor/pkg_resources/__init__.py:2132:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `_isdir`: Definition is incompatible with `NullProvider._isdir`
+ src/pip/_vendor/pkg_resources/__init__.py:2135:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `_listdir`: Definition is incompatible with `NullProvider._listdir`
+ src/pip/_vendor/pygments/lexer.py:784:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_tokens_unprocessed`: Definition is incompatible with `RegexLexer.get_tokens_unprocessed`
+ src/pip/_vendor/pygments/lexers/python.py:1189:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_tokens_unprocessed`: Definition is incompatible with `RegexLexer.get_tokens_unprocessed`
+ src/pip/_vendor/pygments/token.py:28:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__contains__`: Definition is incompatible with `Sequence.__contains__`
- src/pip/_vendor/resolvelib/resolvers/resolution.py:568:19: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/pip/_vendor/rich/progress.py:255:51: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/pip/_vendor/urllib3/contrib/appengine.py:131:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `urlopen`: Definition is incompatible with `RequestMethods.urlopen`
+ src/pip/_vendor/urllib3/contrib/ntlmpool.py:115:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `urlopen`: Definition is incompatible with `HTTPConnectionPool.urlopen`
+ src/pip/_vendor/urllib3/poolmanager.py:353:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `urlopen`: Definition is incompatible with `RequestMethods.urlopen`
+ src/pip/_vendor/urllib3/poolmanager.py:526:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `urlopen`: Definition is incompatible with `RequestMethods.urlopen`
- Found 579 diagnostics
+ Found 596 diagnostics

werkzeug (https://github.com/pallets/werkzeug)
- src/werkzeug/datastructures/mixins.py:302:18: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/datastructures/mixins.py:314:19: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/datastructures/structures.py:75:15: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/werkzeug/datastructures/structures.py:314:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `setdefault`: Definition is incompatible with `MutableMapping.setdefault`
- src/werkzeug/datastructures/structures.py:351:74: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/datastructures/structures.py:375:44: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/datastructures/structures.py:418:18: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/datastructures/structures.py:456:19: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/werkzeug/datastructures/structures.py:656:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__setstate__`: Definition is incompatible with `MultiDict.__setstate__`
- src/werkzeug/datastructures/structures.py:674:42: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/datastructures/structures.py:680:44: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/datastructures/structures.py:683:74: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/datastructures/structures.py:744:18: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/werkzeug/datastructures/structures.py:1081:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `add`: Definition is incompatible with `MutableSet.add`
+ src/werkzeug/datastructures/structures.py:1085:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `remove`: Definition is incompatible with `MutableSet.remove`
+ src/werkzeug/datastructures/structures.py:1121:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `discard`: Definition is incompatible with `MutableSet.discard`
- src/werkzeug/datastructures/structures.py:1195:51: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/debug/console.py:159:64: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/werkzeug/utils.py:153:60: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/werkzeug/utils.py:146:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `lookup`: Definition is incompatible with `_DictAccessorProperty.lookup`
+ tests/test_routing.py:770:13: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `to_python`: Definition is incompatible with `BaseConverter.to_python`
+ tests/test_routing.py:774:13: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `to_url`: Definition is incompatible with `BaseConverter.to_url`
- Found 409 diagnostics
+ Found 403 diagnostics

pytest (https://github.com/pytest-dev/pytest)
+ src/_pytest/_code/code.py:417:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `MutableSequence.__getitem__`
+ src/_pytest/assertion/rewrite.py:854:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_Assert`: Definition is incompatible with `NodeVisitor.visit_Assert`
+ src/_pytest/assertion/rewrite.py:975:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_NamedExpr`: Definition is incompatible with `NodeVisitor.visit_NamedExpr`
+ src/_pytest/assertion/rewrite.py:987:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_Name`: Definition is incompatible with `NodeVisitor.visit_Name`
+ src/_pytest/assertion/rewrite.py:997:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_BoolOp`: Definition is incompatible with `NodeVisitor.visit_BoolOp`
+ src/_pytest/assertion/rewrite.py:1043:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_UnaryOp`: Definition is incompatible with `NodeVisitor.visit_UnaryOp`
+ src/_pytest/assertion/rewrite.py:1049:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_BinOp`: Definition is incompatible with `NodeVisitor.visit_BinOp`
+ src/_pytest/assertion/rewrite.py:1059:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_Call`: Definition is incompatible with `NodeVisitor.visit_Call`
+ src/_pytest/assertion/rewrite.py:1092:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_Starred`: Definition is incompatible with `NodeVisitor.visit_Starred`
+ src/_pytest/assertion/rewrite.py:1098:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_Attribute`: Definition is incompatible with `NodeVisitor.visit_Attribute`
+ src/_pytest/assertion/rewrite.py:1110:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_Compare`: Definition is incompatible with `NodeVisitor.visit_Compare`
- src/_pytest/doctest.py:317:24: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/_pytest/mark/structures.py:493:13: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__call__`: Definition is incompatible with `MarkDecorator.__call__`
- src/_pytest/mark/structures.py:496:24: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/_pytest/mark/structures.py:508:13: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__call__`: Definition is incompatible with `MarkDecorator.__call__`
+ src/_pytest/mark/structures.py:540:13: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__call__`: Definition is incompatible with `MarkDecorator.__call__`
- src/_pytest/mark/structures.py:553:63: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/_pytest/mark/structures.py:557:62: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/_pytest/mark/structures.py:668:18: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/_pytest/nodes.py:515:24: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/_pytest/python.py:1761:24: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/_pytest/recwarn.py:231:35: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ src/_pytest/reports.py:512:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `toterminal`: Definition is incompatible with `TerminalRepr.toterminal`
+ testing/test_assertion.py:854:17: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__getitem__`: Definition is incompatible with `MutableSequence.__getitem__`
+ testing/test_assertion.py:860:17: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__setitem__`: Definition is incompatible with `MutableSequence.__setitem__`
+ testing/test_assertion.py:863:17: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__delitem__`: Definition is incompatible with `MutableSequence.__delitem__`
- Found 445 diagnostics
+ Found 455 diagnostics

twine (https://github.com/pypa/twine)
+ twine/auth.py:73:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__call__`: Definition is incompatible with `AuthBase.__call__`
- Found 11 diagnostics
+ Found 12 diagnostics

python-htmlgen (https://github.com/srittau/python-htmlgen)
+ test_htmlgen/document.py:33:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `add_stylesheet`: Definition is incompatible with `Head.add_stylesheet`
- Found 22 diagnostics
+ Found 23 diagnostics

starlette (https://github.com/encode/starlette)
- starlette/datastructures.py:530:35: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- starlette/datastructures.py:533:37: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- starlette/datastructures.py:536:48: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ tests/test_authentication.py:27:15: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `authenticate`: Definition is incompatible with `AuthenticationBackend.authenticate`
- Found 225 diagnostics
+ Found 223 diagnostics

graphql-core (https://github.com/graphql-python/graphql-core)
+ src/graphql/pyutils/ref_set.py:38:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__contains__`: Definition is incompatible with `AbstractSet.__contains__`
- src/graphql/pyutils/ref_map.py:63:37: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/graphql/pyutils/ref_map.py:67:39: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/graphql/pyutils/ref_map.py:71:48: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- src/graphql/pyutils/ref_map.py:75:76: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- Found 489 diagnostics
+ Found 486 diagnostics

alerta (https://github.com/alerta/alerta)
+ alerta/database/backends/mongodb/base.py:1513:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_customer_notes`: Definition is incompatible with `Database.get_customer_notes`
+ alerta/database/backends/postgres/base.py:554:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_topn_count`: Definition is incompatible with `Database.get_topn_count`
+ alerta/database/backends/postgres/base.py:580:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_topn_flapping`: Definition is incompatible with `Database.get_topn_flapping`
+ alerta/database/backends/postgres/base.py:606:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_topn_standing`: Definition is incompatible with `Database.get_topn_standing`
+ alerta/database/backends/postgres/base.py:1448:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_customer_notes`: Definition is incompatible with `Database.get_customer_notes`
+ tests/test_plugins.py:472:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `post_receive`: Definition is incompatible with `PluginBase.post_receive`
+ tests/test_plugins.py:476:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `status_change`: Definition is incompatible with `PluginBase.status_change`
- Found 545 diagnostics
+ Found 552 diagnostics

pylint (https://github.com/pycqa/pylint)
+ pylint/checkers/exceptions.py:274:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `visit_default`: Definition is incompatible with `BaseVisitor.visit_default`
- Found 210 diagnostics
+ Found 211 diagnostics

rich (https://github.com/Textualize/rich)
- rich/progress.py:255:51: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- Found 346 diagnostics
+ Found 345 diagnostics

scrapy (https://github.com/scrapy/scrapy)
- scrapy/exporters.py:373:66: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- scrapy/http/headers.py:34:18: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ scrapy/http/headers.py:76:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get`: Definition is incompatible with `Mapping.get`
- scrapy/http/headers.py:103:62: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- scrapy/http/headers.py:106:46: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ scrapy/pipelines/__init__.py:40:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `_add_middleware`: Definition is incompatible with `MiddlewareManager._add_middleware`
- scrapy/settings/__init__.py:506:89: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ scrapy/utils/datatypes.py:82:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get`: Definition is incompatible with `Mapping.get`
- scrapy/utils/datatypes.py:89:90: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
+ scrapy/utils/datatypes.py:115:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `__setitem__`: Definition is incompatible with `UserDict.__setitem__`
+ tests/test_downloader_handler_twisted_ftp.py:188:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `_get_factory`: Definition is incompatible with `TestFTPBase._get_factory`
+ tests/test_extension_statsmailer.py:21:13: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_stats`: Definition is incompatible with `StatsCollector.get_stats`
+ tests/test_feedexport.py:846:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `export_item`: Definition is incompatible with `JsonItemExporter.export_item`
+ tests/test_spidermiddleware_referer.py:690:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `referrer`: Definition is incompatible with `ReferrerPolicy.referrer`
- tests/test_spidermiddleware_referer.py:1025:16: warning[�]8;;https://ty.dev/rules#unused-ignore-comment�\unused-ignore-comment�]8;;�\] Unused blanket `type: ignore` directive
- Found 1706 diagnostics
+ Found 1707 diagnostics

paasta (https://github.com/yelp/paasta)
+ paasta_tools/cassandracluster_tools.py:127:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `validate`: Definition is incompatible with `LongRunningServiceConfig.validate`
+ paasta_tools/flink_tools.py:92:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `validate`: Definition is incompatible with `LongRunningServiceConfig.validate`
+ paasta_tools/flink_tools.py:115:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_pool`: Definition is incompatible with `InstanceConfig.get_pool`
+ paasta_tools/kafkacluster_tools.py:60:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `validate`: Definition is incompatible with `LongRunningServiceConfig.validate`
+ paasta_tools/kubernetes_tools.py:1971:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `get_autoscaled_instances`: Definition is incompatible with `LongRunningServiceConfig.get_autoscaled_instances`
+ paasta_tools/monkrelaycluster_tools.py:60:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `validate`: Definition is incompatible with `LongRunningServiceConfig.validate`
+ paasta_tools/nrtsearchservice_tools.py:60:9: error[�]8;;https://ty.dev/rules#invalid-method-override�\invalid-method-override�]8;;�\] Invalid override of method `validate`: Definition is incompatible with `LongRunningServiceConfig.validate`
+ paasta_tools/tron_tools.py:189:9: er

... (truncated 3654 lines) ...
Memory usage changes were detected when running on open source projects
trio (https://github.com/python-trio/trio)
-     memo metadata = ~31MB
+     memo metadata = ~33MB

sphinx (https://github.com/sphinx-doc/sphinx)
- TOTAL MEMORY USAGE: ~273MB
+ TOTAL MEMORY USAGE: ~287MB
-     struct metadata = ~18MB
+     struct metadata = ~20MB
-     struct fields = ~18MB
+     struct fields = ~20MB
-     memo metadata = ~66MB
+     memo metadata = ~73MB

prefect (https://github.com/PrefectHQ/prefect)
-     struct metadata = ~38MB
+     struct metadata = ~40MB
-     memo metadata = ~131MB
+     memo metadata = ~138MB

@astral-sh-bot
Copy link

astral-sh-bot bot commented Nov 13, 2025

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-method-override 3,149 0 0
unused-ignore-comment 0 439 0
Total 3,149 439 0

Full report with detailed diff (timing results)

Comment on lines +551 to +554
if self.db().should_check_file(self.file()) {
self.check_class_definitions();
self.check_overloaded_functions(node);
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an attempt to reduce the performance hit from this PR. A few regressions went down a few percentage points as a result of this change, but I wouldn't say this made a huge impact on perf -- it seemed to improve things fairly significantly in terms of memory usage, however.

Comment on lines +3797 to +3836
pub(super) fn components_excluding_self(&self) -> Vec<String> {
let body_scope = self.class.body_scope(self.db);
let file = body_scope.file(self.db);
let module_ast = parsed_module(self.db, file).load(self.db);
let index = semantic_index(self.db, file);
let file_scope_id = body_scope.file_scope_id(self.db);

let mut name_parts = vec![];

// Skips itself
for (_, ancestor_scope) in index.ancestor_scopes(file_scope_id).skip(1) {
let node = ancestor_scope.node();

match ancestor_scope.kind() {
ScopeKind::Class => {
if let Some(class_def) = node.as_class() {
name_parts.push(class_def.node(&module_ast).name.as_str().to_string());
}
}
ScopeKind::Function => {
if let Some(function_def) = node.as_function() {
name_parts.push(format!(
"<locals of function '{}'>",
function_def.node(&module_ast).name.as_str()
));
}
}
_ => {}
}
}

if let Some(module) = file_to_module(self.db, file) {
let module_name = module.name(self.db);
name_parts.push(module_name.as_str().to_string());
}

name_parts.reverse();
name_parts
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(this method used to be called qualified_name_components, it used to be a method on ClassLiteral, and it used to be in display.rs)

Comment on lines -279 to -285
/// Returns the components of the qualified name of this class, excluding this class itself.
///
/// For example, calling this method on a class `C` in the module `a.b` would return
/// `["a", "b"]`. Calling this method on a class `D` inside the namespace of a method
/// `m` inside the namespace of a class `C` in the module `a.b` would return
/// `["a", "b", "C", "<locals of function 'm'>"]`.
fn qualified_name_components(self, db: &'db dyn Db) -> Vec<String> {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(moved to class.rs for better reusability)

@AlexWaygood AlexWaygood marked this pull request as ready for review November 17, 2025 19:05
@carljm
Copy link
Contributor

carljm commented Nov 18, 2025

Not sure what's up with the ConstraintSet mdtests failing only on MacOS?

@AlexWaygood
Copy link
Member Author

Not sure what's up with the ConstraintSet mdtests failing only on MacOS?

I have no idea. I haven't touched any code to do with constraint sets in this PR, nor have I added any MacOS-specific code! The test also passes locally for me on my MacBook, but seems to be failing very persistently in CI on this PR.

@dcreager, any idea why this PR might be causing the test to start failing here...? The only significant way I think that this PR could impact other areas of ty is that we have to fetch the type for all attributes defined in the body of every class we check (so we're doing a lot more types in every file we check).

@MichaReiser
Copy link
Member

MichaReiser commented Nov 18, 2025

Do we iterate over hash maps or rely on salsa ids for ordering?

Copy link
Contributor

@carljm carljm left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!

from typing import Literal

class Date:
# error: [invalid-method-override]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not necessary to decide in this PR, but I wonder if we should special-case ignoring object.__setattr__ for Liskov purposes (and separately implement validation of a correct __setattr__ signature). TBH I'm not even sure why typeshed has object.__setattr__, and I don't think there is any soundness need for subclasses of object to respect Liskov compatibility with the object.__setattr__ definition in typeshed.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I think you're right.

I was also wondering about special-case ignoring object.__hash__. It's obviously common to define unhashable classes in Python, and it's often the correct thing to do to add __hash__ = None if your class is mutable. Having a type checker yell at you every time you have to do that has never felt helpful to me.

I can propose them both (either together or separately) as followups. I'd rather keep them out of this PR, since either special case (while, IMO, well-motivated) would be a deviation from the behaviour of other type checkers.

Comment on lines +139 to +143
If a child class's method definition is Liskov-compatible with the method definition on its parent
class, Liskov compatibility must also nonetheless be checked with respect to the method definition
on its grandparent class. This is because type checkers will treat the child class as a subtype of
the grandparent class just as much as they treat it as a subtype of the parent class, so
substitutability with respect to the grandparent class is just as important:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not that it needs to be specifically mentioned here, but I think the strongest case for this is gradual types, because it means you can have cases where each individual parent-child relationship passes Liskov checking, but the grandparent-child relationship does not:

class Grandparent:
    x: int

class Parent(Grandparent):
    x: Any

class Child(Parent):
    x: str

return;
}

let class_specialized = class.identity_specialization(db);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be good to add some tests for method overrides involving generics. The behavior should fall out from callable subtyping, but it wouldn't hurt to have some such tests for overrides specifically.

class::CodeGeneratorKind,
context::InferContext,
diagnostic::report_invalid_method_override,
ide_support::{MemberWithDefinition, all_declarations_and_bindings},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this PR should idealy move this stuff out of ide_support? It's clearly no longer just for IDE support.


for supercls in class.iter_mro(db).skip(1).filter_map(ClassBase::into_class) {
let Place::Defined(type_on_supercls, _, _) =
Type::instance(db, supercls).member(db, &member.name).place
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an own_instance_member method we could use here instead, to short-circuit this "find and then skip if not in our own scope" dance?

It feels odd (and potentially inefficient) that if we have a long inheritance chain of length N, and a method defined at the top of it (say on object) and then overridden at the leaf, we will do O(N^2) mro traversal, as we (wastefully) traverse the full MRO of every intervening type in the inheritance chain, only to find the same super-class method on object every time, and then skip it because it's not defined in our own scope.

Or alternatively, if we can't use an own_instance_member, what about checking the place table before doing the member lookup?

The break below gives a false sense of efficiency. If the method doesn't exist anywhere in the MRO, we will only traverse the MRO once, which is good -- but the same would be true if we did the "outer" loop once and did own_instance_member for each type in the MRO. When the method does exist somewhere up the MRO, this way does a lot more traversal.

Copy link
Member Author

@AlexWaygood AlexWaygood Nov 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there an own_instance_member method we could use here instead, to short-circuit this "find and then skip if not in our own scope" dance?

There is a method by that name, yes. But it emulates looking up an attribute directly in self.__dict__ (it does not include class attributes defined directly in the class body). We'd have to call own_class_member as well as own_instance_member to do this correctly.

But looking at this again, this would have the advantage that it would include synthesized members, since own_class_member calls own_synthesized_member. I think we're currently incorrectly skipping over synthesized members (from dataclasses/namedtuples/etc.) on superclasses, because they don't appear in the symbol table for those superclasses.

So your suggestion probably brings correctness benefits as well as performance benefits. Thanks!

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type::member is Salsa-cached, but I think that doesn't help here as much as we'd hope? The internal own-member stuff that is done for each class in the MRO is not cached, so we are still doing the N^2 walk -- but we cache the final lookup result for each class in the MRO. Which is useful if that attribute is later looked up on that superclass, but still wasted if not.

@dcreager
Copy link
Member

@dcreager, any idea why this PR might be causing the test to start failing here...? The only significant way I think that this PR could impact other areas of ty is that we have to fetch the type for all attributes defined in the body of every class we check (so we're doing a lot more types in every file we check).

It might cause the typevar ordering to be different, which would cause the internal BDD structure to change. I'm seeing this on another PR, too, so I will try to push up a separate fix for it.

@dcreager
Copy link
Member

It might cause the typevar ordering to be different, which would cause the internal BDD structure to change. I'm seeing this on another PR, too, so I will try to push up a separate fix for it.

#21524 fixes the macos CI failures I was getting on #21414. I was getting a slightly different test failure than you, but my hope is that they had the same underlying cause. You can cherry-pick that commit if you want to test soon, or wait till that lands on main.

dcreager added a commit that referenced this pull request Nov 19, 2025
We're seeing flaky test failures on macos, which seems to be caused by
different Salsa ID orderings on the different platforms. Constraint set
BDDs order their internal nodes based on the Salsa IDs of the interned
typevar structs, and we had some code that depended on variable ordering
in an unexpected way.

This patch definitely fixes the macos test failure on #21414, and
hopefully fixes it on #21436, too.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ecosystem-analyzer ty Multi-file analysis & type inference

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants