Skip to content

[ty] Detect invalid attribute overrides#24767

Open
charliermarsh wants to merge 8 commits intomainfrom
charlie/attr-override
Open

[ty] Detect invalid attribute overrides#24767
charliermarsh wants to merge 8 commits intomainfrom
charlie/attr-override

Conversation

@charliermarsh
Copy link
Copy Markdown
Member

@charliermarsh charliermarsh commented Apr 21, 2026

Summary

We now detect Liskov violations when a parent-child pair have attributes with a differing ClassVar status.

In general, we interpret an attribute without a ClassVar annotation as an instance attribute, with the exception of cases like the following, where we "allow" the child to "inherit" the annotation to adhere to the conformance suite:

class ProtoB(Protocol):
    z: ClassVar[int]

class ProtoBImpl(ProtoB):
    z = 0

Closes astral-sh/ty#3093.

@astral-sh-bot astral-sh-bot Bot added the ty Multi-file analysis & type inference label Apr 21, 2026
@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented Apr 21, 2026

Typing conformance results improved 🎉

The percentage of diagnostics emitted that were expected errors increased from 87.94% to 87.96%. The percentage of expected errors that received a diagnostic increased from 83.36% to 83.55%. The number of fully passing files improved from 79/133 to 80/133.

Summary

How are test cases classified?

Each test case represents one expected error annotation or a group of annotations sharing a tag. Counts are per test case, not per diagnostic — multiple diagnostics on the same line count as one. Required annotations (E) are true positives when ty flags the expected location and false negatives when it does not. Optional annotations (E?) are true positives when flagged but true negatives (not false negatives) when not. Tagged annotations (E[tag]) require ty to flag exactly one of the tagged lines; tagged multi-annotations (E[tag+]) allow any number up to the tag count. Flagging unexpected locations counts as a false positive.

Metric Old New Diff Outcome
True Positives 882 884 +2 ⏫ (✅)
False Positives 121 121 +0
False Negatives 176 174 -2 ⏬ (✅)
Total Diagnostics 1052 1054 +2
Precision 87.94% 87.96% +0.02% ⏫ (✅)
Recall 83.36% 83.55% +0.19% ⏫ (✅)
Passing Files 79/133 80/133 +1 ⏫ (✅)

Test file breakdown

1 file altered
File True Positives False Positives False Negatives Status
dataclasses_inheritance.py 2 (+2) ✅ 0 0 (-2) ✅ ✅ Newly Passing 🎉
Total (all files) 884 (+2) ✅ 121 174 (-2) ✅ 80/133

True positives added (2)

2 diagnostics
Test case Diff

dataclasses_inheritance.py:62

+error[invalid-attribute-override] Invalid override of attribute `x`: class variable cannot override instance variable `DC6.x`

dataclasses_inheritance.py:66

+error[invalid-attribute-override] Invalid override of attribute `y`: instance variable cannot override class variable `DC6.y`

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented Apr 21, 2026

Memory usage report

Summary

Project Old New Diff Outcome
sphinx 258.60MB 258.63MB +0.01% (28.15kB)
prefect 702.82MB 702.83MB +0.00% (10.27kB)
flake8 47.68MB 47.69MB +0.01% (3.92kB)
trio 116.46MB 116.46MB +0.00% (2.42kB)

Significant changes

Click to expand detailed breakdown

sphinx

Name Old New Diff Outcome
Type<'db>::class_member_with_policy_ 7.62MB 7.63MB +0.23% (18.21kB)
Type<'db>::class_member_with_policy_::interned_arguments 4.02MB 4.03MB +0.10% (4.16kB)
infer_scope_types_impl 15.36MB 15.36MB +0.03% (4.03kB)
Project 11.99kB 13.74kB +14.59% (1.75kB)

prefect

Name Old New Diff Outcome
infer_scope_types_impl 54.66MB 54.66MB +0.01% (4.64kB)
Type<'db>::class_member_with_policy_ 17.66MB 17.66MB +0.02% (2.76kB)
Project 18.99kB 20.74kB +9.21% (1.75kB)
Type<'db>::class_member_with_policy_::interned_arguments 9.82MB 9.82MB +0.01% (1.12kB)

flake8

Name Old New Diff Outcome
Project 5.87kB 7.62kB +29.82% (1.75kB)
Type<'db>::class_member_with_policy_ 572.44kB 574.14kB +0.30% (1.70kB)
Type<'db>::class_member_with_policy_::interned_arguments 311.19kB 311.49kB +0.10% (312.00B)
infer_scope_types_impl 983.70kB 983.86kB +0.02% (168.00B)

trio

Name Old New Diff Outcome
Project 6.74kB 8.49kB +25.95% (1.75kB)
infer_scope_types_impl 4.71MB 4.71MB +0.01% (480.00B)
Type<'db>::class_member_with_policy_::interned_arguments 1.12MB 1.12MB +0.01% (104.00B)
Type<'db>::class_member_with_policy_ 2.03MB 2.03MB +0.00% (100.00B)

@astral-sh-bot
Copy link
Copy Markdown

astral-sh-bot Bot commented Apr 21, 2026

ecosystem-analyzer results

Lint rule Added Removed Changed
invalid-attribute-override 55 0 0
Total 55 0 0
Raw diff (55 changes)
archinstall (https://github.com/archlinux/archinstall)
+ archinstall/tui/ui/components.py:43:2 error[invalid-attribute-override] Invalid override of attribute `BINDINGS`: class variable cannot override instance variable `Screen.BINDINGS`
+ archinstall/tui/ui/components.py:369:2 error[invalid-attribute-override] Invalid override of attribute `BINDINGS`: class variable cannot override instance variable `SelectionList.BINDINGS`

dd-trace-py (https://github.com/DataDog/dd-trace-py)
+ ddtrace/contrib/_events/llm.py:40:5 error[invalid-attribute-override] Invalid override of attribute `span_type`: instance variable cannot override class variable `TracingEvent.span_type`

discord.py (https://github.com/Rapptz/discord.py)
+ discord/enums.py:113:9 error[invalid-attribute-override] Invalid override of attribute `__name__`: class variable cannot override instance variable `type.__name__`

django-stubs (https://github.com/typeddjango/django-stubs)
+ tests/assert_type/db/migrations/test_classvar.py:18:5 error[invalid-attribute-override] Invalid override of attribute `operations`: instance variable cannot override class variable `Migration.operations`
+ tests/assert_type/db/migrations/test_classvar.py:19:5 error[invalid-attribute-override] Invalid override of attribute `initial`: instance variable cannot override class variable `Migration.initial`

pip (https://github.com/pypa/pip)
+ src/pip/_vendor/urllib3/connection.py:107:5 error[invalid-attribute-override] Invalid override of attribute `default_port`: class variable cannot override instance variable `HTTPConnection.default_port`

prefect (https://github.com/PrefectHQ/prefect)
+ src/integrations/prefect-ray/prefect_ray/context.py:21:5 error[invalid-attribute-override] Invalid override of attribute `__var__`: instance variable cannot override class variable `ContextModel.__var__`

scipy-stubs (https://github.com/scipy/scipy-stubs)
+ scipy-stubs/stats/_resampling.pyi:75:5 error[invalid-attribute-override] Invalid override of attribute `__match_args__`: class variable cannot override instance variable `ResamplingMethod.__match_args__`
+ scipy-stubs/stats/_resampling.pyi:121:5 error[invalid-attribute-override] Invalid override of attribute `__match_args__`: class variable cannot override instance variable `ResamplingMethod.__match_args__`

sphinx (https://github.com/sphinx-doc/sphinx)
+ sphinx/directives/__init__.py:55:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/__init__.py:360:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/admonitions.py:30:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `BaseAdmonition.option_spec`
+ sphinx/directives/admonitions.py:31:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `BaseAdmonition.option_spec`
+ sphinx/domains/changeset.py:62:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/index.py:71:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/ifconfig.py:44:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/todo.py:105:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/transforms/post_transforms/__init__.py:65:5 error[invalid-attribute-override] Invalid override of attribute `default_priority`: class variable cannot override instance variable `Transform.default_priority`
+ sphinx/directives/code.py:41:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/code.py:108:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/code.py:423:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/other.py:194:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/other.py:226:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/other.py:242:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/other.py:263:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/other.py:283:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/other.py:318:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/patches.py:92:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/directives/patches.py:142:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/c/__init__.py:421:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/c/__init__.py:451:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/c/__init__.py:480:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/cpp/__init__.py:542:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/cpp/__init__.py:573:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/cpp/__init__.py:603:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/javascript.py:335:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/python/__init__.py:480:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/python/__init__.py:548:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/std/__init__.py:197:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/std/__init__.py:340:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/std/__init__.py:419:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/domains/std/__init__.py:599:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/autosummary/__init__.py:202:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/doctest.py:151:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/doctest.py:157:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/doctest.py:163:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/doctest.py:174:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/doctest.py:184:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/graphviz.py:121:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/graphviz.py:201:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/ext/inheritance_diagram.py:398:5 error[invalid-attribute-override] Invalid override of attribute `option_spec`: class variable cannot override instance variable `Directive.option_spec`
+ sphinx/transforms/__init__.py:368:5 error[invalid-attribute-override] Invalid override of attribute `smartquotes_action`: class variable cannot override instance variable `SmartQuotes.smartquotes_action`
+ sphinx/transforms/post_transforms/code.py:38:5 error[invalid-attribute-override] Invalid override of attribute `default_priority`: class variable cannot override instance variable `Transform.default_priority`

urllib3 (https://github.com/urllib3/urllib3)
+ src/urllib3/connection.py:107:5 error[invalid-attribute-override] Invalid override of attribute `default_port`: class variable cannot override instance variable `HTTPConnection.default_port`

Full report with detailed diff (timing results)

@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented Apr 21, 2026

Merging this PR will not alter performance

✅ 53 untouched benchmarks
⏩ 60 skipped benchmarks1


Comparing charlie/attr-override (1d8c3fe) with main (810cab3)

Open in CodSpeed

Footnotes

  1. 60 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/attr-override branch from 413dc92 to d236787 Compare April 21, 2026 14:40
@charliermarsh
Copy link
Copy Markdown
Member Author

The ecosystem changes are all true positives or cases that already have type ignores for other type-checkers.

@charliermarsh charliermarsh force-pushed the charlie/attr-override branch 2 times, most recently from 096ace5 to 2684664 Compare April 21, 2026 16:32
@charliermarsh charliermarsh marked this pull request as ready for review April 21, 2026 16:39
@charliermarsh charliermarsh force-pushed the charlie/attr-override branch 2 times, most recently from b28ed78 to e6842c3 Compare April 21, 2026 16:47
Copy link
Copy Markdown
Member

@AlexWaygood AlexWaygood left a comment

Choose a reason for hiding this comment

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

We shouldn't use the invalid-method-override rule code for overrides of things that aren't methods. I deliberately gave that rule quite a specific name so that other kinds of overrides could have dedicated error codes with dedicated documentation pages, so it would be easy for users to find more explanation of why certain kinds of overrides are unsound

@charliermarsh
Copy link
Copy Markdown
Member Author

Aye aye boss

@charliermarsh charliermarsh marked this pull request as draft April 21, 2026 17:49
@charliermarsh charliermarsh marked this pull request as ready for review April 21, 2026 18:05
@charliermarsh charliermarsh force-pushed the charlie/attr-override branch from a05fec7 to 1d8c3fe Compare April 21, 2026 21:47
@AlexWaygood AlexWaygood dismissed their stale review April 21, 2026 21:52

Requested changes were made

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.

Detect invalid overrides of instance variables with class variables, and vice versa

3 participants