Enforce slots for closed dataclasses (#11) - #13
Conversation
Add binding-aware recognition and conservative safety analysis for standard-library dataclasses whose instance layout can be closed safely. Cover open-state evidence, class identity hazards, and inherited layouts with example, property, plug-in, and PyPy shim tests. Reserve `R9111` for the issue's new rule and move the existing `prefer-type-statement` refactor message to `R9112`.
Explain the literal slots requirement, evidence-based safety exemptions, and explicit compatibility suppression path. Record the binding, decorator-order, and cached layout analysis used by the checker. Update the public inventory to thirteen messages and identify the renumbered type-statement rule as `R9112`.
Treat fields from the full inherited lineage as declared state, so safe grandparent-field assignments remain eligible for generated slots. Classify manual slots from Astroid's resolved layout rather than from the presence of an assignment, keeping multiple empty-slot marker bases layout-neutral.
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
Summary
WalkthroughAdd the ChangesDataclass slots checker
Sequence Diagram(s)sequenceDiagram
participant Pylint
participant DataclassSlotsChecker
participant DataclassDecoratorResolver
participant LayoutAnalyzer
Pylint->>DataclassSlotsChecker: visit class
DataclassSlotsChecker->>DataclassDecoratorResolver: resolve decorator
DataclassDecoratorResolver-->>DataclassSlotsChecker: dataclass and slots value
DataclassSlotsChecker->>LayoutAnalyzer: evaluate layout safety
LayoutAnalyzer-->>DataclassSlotsChecker: eligibility result
DataclassSlotsChecker-->>Pylint: emit R9111 when eligible
Suggested labels: Poem
Important Pre-merge checks failedPlease resolve all errors before merging. Addressing warnings is optional. ❌ Failed checks (1 warning, 3 inconclusive)
✅ Passed checks (16 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
Extract the open-state, class-hazard, and reverse-inheritance decisions into focused helpers. This preserves the conservative checker behaviour while keeping the new analysis within the repository's complexity thresholds.
Restrict declared instance state to real dataclass fields and explicit slots, and require manual slot declarations to have a runtime value. Preserve inherited dictionaries through generated-slot bases and limit reverse multiple-inheritance suppression to genuinely conflicting slot lineages.
|
@coderabbitai resume |
✅ Action performedReviews resumed. |
|
@coderabbitai review |
✅ Action performedReview finished.
|
Preserve conservative layout and import-resolution behaviour while fixing the review findings around declared state, generic extension bases, inheritance, recursive analysis, and manual slots. Strengthen diagnostic tests, document the 0.2.0 message migration, and bind Pylint to the supported major-version range.
|
@coderabbitai resume |
✅ Action performedReviews resumed. |
Inspect nested executable scopes for replacement-class closures and treat `typing_extensions` ClassVar and Protocol imports like their standard-library counterparts. Document the extracted inference helper and strengthen message-ID coverage.
Memoize local inherited-layout classifications per module and clear the provisional cache before final eligibility analysis. Add a structural scaling regression for deep single-inheritance chains. Record the conservative analysis and Pylint compatibility decisions in ADR 001 and link them from the developer documentation.
Use the project Oxford spelling required by the documentation gate.
Stop class-cell scans at nested class boundaries and consume at most two inference candidates when deciding ambiguity. Add focused regressions for nested helper classes and unbounded inference streams.
|
@coderabbitai Have the following now been resolved? df12_python_lints/_dataclass_analysis.py (1)
🤖 Detailed instructionsVerify each finding against current code. Fix only still-valid issues, skip the In Source: MCP tools |
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- target file outline ---'
ast-grep outline df12_python_lints/_dataclass_analysis.py --items all --match 'uses_class_cell|class_cell' || true
echo '--- class-cell helper implementation ---'
rg -n -A35 -B8 'def _uses_class_cell\b|nodes_of_class\(' df12_python_lints/_dataclass_analysis.py
echo '--- relevant test locations ---'
rg -n -A18 -B6 'nested.*class|helper.*class|zero-argument.*super|super\(\)' tests/test_dataclass_slots_safety.py tests/test_dataclass_slots.py tests/dataclass_slots_support.py || true
echo '--- changed files and target diff ---'
git diff --name-only HEAD~1..HEAD || true
git diff HEAD~1..HEAD -- df12_python_lints/_dataclass_analysis.py tests/test_dataclass_slots_safety.py tests/test_dataclass_slots.py || trueLength of output: 12379 🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- safety tests around class-cell handling ---'
sed -n '120,185p' tests/test_dataclass_slots_safety.py
echo '--- assertion helpers ---'
sed -n '1,120p' tests/dataclass_slots_support.py
echo '--- all nested ClassDef declarations in dataclass-slots tests ---'
rg -n -U -P '^\s+class\s+\w+:[\s\S]{0,500}?\bsuper\s*\(\s*\)' \
tests/test_dataclass_slots.py tests/test_dataclass_slots_safety.py || true
echo '--- helper-class names and report assertions ---'
rg -n -i -C4 'class\s+(helper|local|inner)|assert_reports\(' \
tests/test_dataclass_slots.py tests/test_dataclass_slots_safety.pyLength of output: 15315
The required regression test is absent. Add a test that uses I could not run validation in this sandbox. You are interacting with an AI system. |
|
@coderabbitai Have the following failed checks now been resolved? If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures. Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat documentation and validation coverage as in scope). ❌ Failed checks (1 warning)
|
This comment was marked as resolved.
This comment was marked as resolved.
Require manual `__slots__` declarations to resolve unambiguously to a complete valid slot value before suppressing R9111. Use the validated names for inherited layout and declared-state analysis, and document and test the rejected annotation-only, invalid, unresolved, and ambiguous forms.
Split inferred value classification from element validation so each helper has one decision level while preserving the conservative R9111 behaviour.
Add terminal bare returns to the checker lifecycle methods and narrowly suppress the conflicting redundant-return rules where necessary.
|
@coderabbitai resume |
✅ Action performedReviews resumed. |
A manual `__slots__` entry naming `__dict__` keeps the instance dictionary
alive, so the layout is not closed. Classify such a layout as `Layout.UNSAFE`
before it can be read as `SLOTTED`, which stops R9111 firing on a dataclass
child of a local base that declares `__slots__ = ("__dict__",)`.
Mark `DataclassSlotsChecker.__init__` with `typing.override`, keeping the
existing `disable-next` comment immediately before the `def` so it still
applies. The name is bound through a `TYPE_CHECKING` branch: type checkers see
`typing.override` precisely, while the runtime fallback keeps the plugin
importable under the PyPy 3.11 pylint shim, where `typing.override` is absent.
Rewrite the ADR decision statement impersonally, and correct the developer
guide's account of the replacement-class scan: it enters nested executable
scopes but stops at nested `ClassDef` bodies, since a nested helper class owns
its own class cell.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Accept the `LayoutAnalyzer` identifier and literal external API terms so the generated spelling configuration validates the documentation.
Reuse each class's declared state while analysing local inheritance so deep dataclass chains avoid repeated ancestor walks.
|
@coderabbitai Have the following failed checks now been resolved? If further work is required, please provide an AI agent prompt for the remaining work to be done to address these failures. Do not treat warnings as optional or aspirational. Where a change is out of scope for this PR, propose a GitHub issue unless one exists already. (Treat o11y, code safety, documentation and validation coverage as in scope). ❌ Failed checks (1 warning)
|
|
Tip For best results, initiate chat on the files or code changes. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
set -euo pipefail
echo '--- relevant file discovery ---'
fd -t f . df12_python_lints tests docs | rg '(_dataclass_(state|analysis)\.py$|dataclass_slots\.py$|test_dataclass_slots_(performance|safety|layout)\.py$|dataclass_slots_support\.py$|developers-guide\.md$|adr-001)'
echo '--- module outlines ---'
ast-grep outline df12_python_lints/_dataclass_state.py --items all --view expanded
ast-grep outline df12_python_lints/_dataclass_analysis.py --items all --match 'LayoutAnalyzer|state|layout' --view expanded
ast-grep outline df12_python_lints/dataclass_slots.py --items all --view expanded
ast-grep outline tests/test_dataclass_slots_performance.py --items all --view expanded
echo '--- state aggregation definitions and call sites ---'
rg -n -A45 -B10 \
'def (declared_instance_state|dataclass_field_names|local_slot_names|has_declared_instance_fields)\b|\bdeclared_instance_state\s*\(' \
df12_python_lints tests
echo '--- layout cache and eligibility path ---'
rg -n -A80 -B12 \
'_local_layout|_base_layout|_layout_cache|is_eligible|has_declared_instance_fields|declared_instance_state' \
df12_python_lints/_dataclass_analysis.py df12_python_lints/dataclass_slots.py
echo '--- performance regression coverage ---'
cat -n tests/test_dataclass_slots_performance.py
echo '--- current PR diff for relevant paths ---'
git diff "$(git merge-base HEAD origin/main 2>/dev/null || git merge-base HEAD main 2>/dev/null || echo HEAD~1)" HEAD -- \
df12_python_lints/_dataclass_state.py \
df12_python_lints/_dataclass_analysis.py \
df12_python_lints/dataclass_slots.py \
tests/test_dataclass_slots_performance.py \
docs/developers-guide.md \
docs/adr-001-conservative-dataclass-layout-analysis.md || trueLength of output: 84469
No remaining work exists. Do not create a follow-up issue. No AI agent prompt is required. Repository test execution was unavailable in this sandbox. You are interacting with an AI system. |
Summary
This branch adds the
prefer-slots-for-dataclass(R9111) Pylint rule soclosed standard-library dataclasses declare an explicit instance layout, while
holding its tongue when source evidence shows that generated slots would be
unsafe, ineffective or identity-sensitive.
It recognizes real
dataclasses.dataclassimports and aliases, attaches thediagnostic to the decorator expression, and covers open state, extension
boundaries, decorator order, class-cell hazards and transitive inherited
layouts. Review follow-up distinguishes real dataclass fields from class-only
names, requires a runtime value for manual slots, preserves inherited
dictionaries and limits reverse multiple-inheritance suppression to genuinely
conflicting slot lineages. A manual
__slots__entry naming__dict__nowclassifies the layout as unsafe rather than slotted, so a dataclass inheriting
that layout is neither treated as closed nor reported.
The existing
prefer-type-statementmessage moves from the newly collidingR9111toR9112;C9112remains unchanged because Pylint message categoriesare part of the identifier.
Closes #11.
Review walkthrough
df12_python_lints/dataclass_slots.pyfor the checker, message definition and decorator-attached diagnostic.
df12_python_lints/_dataclass_decorators.py,df12_python_lints/_dataclass_state.pyand
df12_python_lints/_dataclass_analysis.pyfor binding-aware recognition, instance-state classification, cached layout
analysis and precise reverse multiple-inheritance handling.
tests/test_dataclass_slots.py,tests/test_dataclass_slots_safety.pyand
tests/test_properties.pyfor the recognition, state, safety, inheritance and keyword-order contracts.
docs/users-guide.mdand
docs/developers-guide.mdfor the house policy and implementation strategy.
Validation
make check-fmt,make lint,make typecheck,make test: passed afterthe latest review round; Ruff, 100% docstring coverage, Pylint 10/10 under
the PyPy shim,
tyclean, and 294 tests passed (one existing test skipped).make all: passed; Ruff, 100% docstring coverage, Pylint 10/10, typechecking, 271 tests and spelling succeeded (one existing test skipped).
make markdownlint: passed, including the en-GB Oxford spelling gate.make nixie: passed; all Mermaid diagrams validated.make crosshair: passed; the opt-in model check completed successfully.make audit: passed; no known dependency vulnerabilities found.mbake validate Makefile: passed.coderabbit review --agent: the final committed full-branch review completedwith zero findings.
act-validation,lint-test, CodeScene and Sourcery checks passed.Notes
The issue was written while pull request #6 allocated only
R9101throughR9110. Its merged form also usedR9111forprefer-type-statement.Preserving this issue's explicit
R9111acceptance contract therefore requiresrenumbering that existing refactor message to
R9112.DataclassSlotsChecker.__init__carriestyping.override. The name is boundthrough a
TYPE_CHECKINGbranch so type checkers resolve it precisely, whilethe runtime fallback keeps the plugin importable under the PyPy 3.11 pylint
shim that the project's own lint gate uses, where
typing.overrideis absent.References