Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions nominal_refactor_advisor/class_overlap.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""Classes that restate another class's body (#68, shapes 5 and 6).

`ClassBodyIndex` says which declarations two classes share. This module says
Expand Down Expand Up @@ -843,8 +843,12 @@
*self._family_receipts(first, second),
*self._construction_receipts(first, second, move),
*self._identity_receipts(owner, restating, move),
*self._positional_construction_receipts(
move, (first, second)
*(
self._positional_construction_receipts(
move, (first, second)
)
if move is not ExtractMixin
else ()
),
),
)
Expand Down
49 changes: 49 additions & 0 deletions tests/test_missing_mixin_unblock.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,24 @@
" extra: str = ''\n"
)

POSITIONAL_CONSTRUCTION = (
"from dataclasses import dataclass\n\n\n"
"@dataclass(frozen=True)\n"
"class PositionallyBuilt:\n"
" alpha: str\n"
" beta: int\n"
" gamma: float\n"
" own_pos: str = 'x'\n\n\n"
"@dataclass(frozen=True)\n"
"class KeywordBuilt:\n"
" alpha: str\n"
" beta: int\n"
" gamma: float\n"
" own_kw: str = 'y'\n\n\n"
"def make_positional() -> PositionallyBuilt:\n"
" return PositionallyBuilt('a', 1, 2.0)\n"
)

COPIER = (
"from dataclasses import dataclass\n\n\n"
"@dataclass(frozen=True)\n"
Expand Down Expand Up @@ -221,6 +239,37 @@ def test_keyword_only_construction_diff_does_not_block_extract_mixin(
)


def test_positional_construction_does_not_block_extract_mixin_verdict(
tmp_path: Path,
) -> None:
"""Positional construction is handled by a keywording prerequisite at the
owner level. It should not block the verdict for ExtractMixin moves."""

from nominal_refactor_advisor.class_overlap import ExtractMixin
from nominal_refactor_advisor.overlap_receipts import OverlapReceiptKind

package = tmp_path / "pkg"
package.mkdir()
(package / "__init__.py").write_text("")
(package / "classes.py").write_text(POSITIONAL_CONSTRUCTION)
snapshot = CodemodSourceSnapshot.from_modules(parse_python_modules(tmp_path))

verdicts = snapshot.class_overlap_verdicts
extract_mixins = [
c for c in verdicts.candidates if c.move is ExtractMixin
]
assert extract_mixins, "fixture must produce an ExtractMixin verdict"
for c in extract_mixins:
positional_refusals = [
r for r in c.refusals
if r.kind is OverlapReceiptKind.POSITIONAL_CONSTRUCTION
]
assert not positional_refusals, (
f"ExtractMixin verdict should not have POSITIONAL_CONSTRUCTION guards, "
f"got {[r.detail for r in positional_refusals]}"
)


def test_descendant_without_projected_fields_does_not_block_carrier(
tmp_path: Path,
) -> None:
Expand Down
Loading