[BuildLibrary] allow prefixing/renaming/postprocessing in BuildCellsView
This commit is contained in:
parent
b62879b410
commit
890e7e5c0f
3 changed files with 611 additions and 40 deletions
|
|
@ -21,6 +21,7 @@ from ..library import (
|
|||
)
|
||||
from ..pattern import Pattern
|
||||
from ..ports import Port
|
||||
from ..utils.ports2data import ports_to_data
|
||||
|
||||
|
||||
def _owned_by(report: BuildReport, owner: str) -> set[str]:
|
||||
|
|
@ -832,3 +833,257 @@ def test_build_session_subtree_preserves_type_and_builds_deferred_dependencies()
|
|||
|
||||
assert child_calls == 1
|
||||
assert set(built) == {"top", "child", "leaf", "unrelated"}
|
||||
|
||||
|
||||
def test_build_cells_prefixed_view_generates_facades_for_attributes_and_items() -> None:
|
||||
builder = LibraryBuilder()
|
||||
cells = builder.cells.prefixed(
|
||||
"vendor_",
|
||||
facade=lambda proxy: ports_to_data(proxy, (10, 2)),
|
||||
)
|
||||
|
||||
cells.fixed = cell(Pattern)(ports={"p": Port((1, 2), 0)})
|
||||
dynamic_name = "dynamic"
|
||||
cells[dynamic_name] = cell(Pattern)(ports={"q": Port((3, 4), 0)})
|
||||
built, report = builder.build(output="library")
|
||||
|
||||
assert set(built) == {"vendor_fixed", "fixed", "vendor_dynamic", "dynamic"}
|
||||
assert set(built["fixed"].refs) == {"vendor_fixed"}
|
||||
assert set(built["dynamic"].refs) == {"vendor_dynamic"}
|
||||
assert set(built["fixed"].ports) == {"p"}
|
||||
assert len(built["fixed"].labels[(10, 2)]) == 1
|
||||
assert not built["vendor_fixed"].labels
|
||||
assert report.dependency_graph["fixed"] == frozenset({"vendor_fixed"})
|
||||
assert report.dependency_graph["dynamic"] == frozenset({"vendor_dynamic"})
|
||||
|
||||
|
||||
def test_build_cells_prefixed_view_postprocesses_only_the_implementation() -> None:
|
||||
source = Pattern(ports={"p": Port((0, 0), 0)})
|
||||
builder = LibraryBuilder()
|
||||
physical = builder.cells.prefixed(
|
||||
"vendor_",
|
||||
postprocess=lambda pattern: ports_to_data(pattern, (20, 1)),
|
||||
)
|
||||
physical["cell"] = source
|
||||
unprefixed = builder.cells.prefixed(
|
||||
None,
|
||||
postprocess=lambda pattern: ports_to_data(pattern, (21, 1)),
|
||||
)
|
||||
unprefixed["local"] = Pattern(ports={"q": Port((1, 1), 0)})
|
||||
built, _report = builder.build(output="library")
|
||||
|
||||
assert set(built) == {"vendor_cell", "local"}
|
||||
assert len(built["vendor_cell"].labels[(20, 1)]) == 1
|
||||
assert len(built["local"].labels[(21, 1)]) == 1
|
||||
assert not source.labels
|
||||
|
||||
|
||||
def test_build_cells_postprocess_precedes_facade_port_copy() -> None:
|
||||
builder = LibraryBuilder()
|
||||
|
||||
def add_port(pattern: Pattern) -> Pattern:
|
||||
pattern.ports["added"] = Port((5, 6), 0)
|
||||
return pattern
|
||||
|
||||
cells = builder.cells.prefixed(
|
||||
"impl_",
|
||||
postprocess=add_port,
|
||||
facade=lambda pattern: ports_to_data(pattern, (30, 0)),
|
||||
)
|
||||
cells.device = Pattern()
|
||||
built, _report = builder.build(output="library")
|
||||
|
||||
assert set(built["impl_device"].ports) == {"added"}
|
||||
assert set(built["device"].ports) == {"added"}
|
||||
assert built["device"].labels[(30, 0)][0].string.startswith("added:")
|
||||
|
||||
|
||||
def test_build_cells_facade_is_ignored_when_name_does_not_change() -> None:
|
||||
builder = LibraryBuilder()
|
||||
|
||||
def fail_if_called(_pattern: Pattern) -> Pattern:
|
||||
raise AssertionError("facade should not be called")
|
||||
|
||||
cells = builder.cells.prefixed("vendor_", facade=fail_if_called)
|
||||
cells["vendor_cell"] = Pattern()
|
||||
built, _report = builder.build(output="library")
|
||||
|
||||
assert set(built) == {"vendor_cell"}
|
||||
|
||||
|
||||
def test_build_cells_renamed_views_are_independent_and_accept_dynamic_names() -> None:
|
||||
builder = LibraryBuilder()
|
||||
upper = builder.cells.renamed(str.upper)
|
||||
suffixed = builder.cells.renamed(lambda name: name + "_v2")
|
||||
|
||||
builder.cells.plain = Pattern()
|
||||
upper["upper"] = Pattern()
|
||||
suffixed["other"] = Pattern()
|
||||
built, _report = builder.build(output="library")
|
||||
|
||||
assert set(built) == {"plain", "UPPER", "other_v2"}
|
||||
|
||||
|
||||
def test_build_cells_policy_registration_is_atomic() -> None:
|
||||
builder = LibraryBuilder()
|
||||
builder.cells.public = Pattern()
|
||||
prefixed = builder.cells.prefixed("impl_", facade=lambda pattern: pattern)
|
||||
|
||||
with pytest.raises(LibraryError, match="already exist"):
|
||||
prefixed.public = Pattern()
|
||||
|
||||
assert set(builder) == {"public"}
|
||||
assert "impl_public" not in builder
|
||||
|
||||
|
||||
def test_build_cells_policy_views_validate_names_and_transforms() -> None:
|
||||
builder = LibraryBuilder()
|
||||
invalid_name = builder.cells.renamed(lambda _name: None) # type: ignore[arg-type,return-value]
|
||||
|
||||
with pytest.raises(TypeError, match="expected str"):
|
||||
invalid_name["cell"] = Pattern()
|
||||
with pytest.raises(TypeError, match="must be callable"):
|
||||
builder.cells.prefixed("x_", facade=object()) # type: ignore[arg-type]
|
||||
|
||||
assert not builder
|
||||
|
||||
|
||||
def test_build_cells_view_deletion_requires_concrete_names_after_rename() -> None:
|
||||
builder = LibraryBuilder()
|
||||
prefixed = builder.cells.prefixed("impl_", facade=lambda pattern: pattern)
|
||||
prefixed["cell"] = Pattern()
|
||||
|
||||
with pytest.raises(BuildError, match="Delete concrete names"):
|
||||
del prefixed["cell"]
|
||||
|
||||
del builder.cells["cell"]
|
||||
del builder.cells["impl_cell"]
|
||||
builder.cells["plain"] = Pattern()
|
||||
del builder.cells["plain"]
|
||||
assert not builder
|
||||
|
||||
|
||||
def test_build_cells_item_reads_remain_write_only() -> None:
|
||||
builder = LibraryBuilder()
|
||||
builder.cells["cell"] = Pattern()
|
||||
|
||||
with pytest.raises(BuildError, match="write-only"):
|
||||
_ = builder.cells["cell"]
|
||||
|
||||
|
||||
def test_build_cells_prefixed_view_normalizes_recipe_tree_and_generates_facade() -> None:
|
||||
builder = LibraryBuilder()
|
||||
builder.cells["_helper"] = Pattern()
|
||||
cells = builder.cells.prefixed(
|
||||
"impl_",
|
||||
postprocess=lambda pattern: Pattern(ports={"out": Port((7, 8), 0)}, refs=pattern.refs),
|
||||
facade=lambda pattern: ports_to_data(pattern, (40, 0)),
|
||||
)
|
||||
|
||||
def make_tree() -> Library:
|
||||
tree = Library({"_helper": Pattern(), "old_top": Pattern()})
|
||||
tree["old_top"].ref("_helper")
|
||||
return tree
|
||||
|
||||
cells.tree = cell(make_tree)()
|
||||
built, report = builder.build(output="library")
|
||||
|
||||
assert {"_helper", "impl_tree", "tree"} <= set(built)
|
||||
helper_targets = set(built["impl_tree"].refs)
|
||||
assert "_helper" not in helper_targets
|
||||
assert len(helper_targets) == 1
|
||||
renamed_helper = helper_targets.pop()
|
||||
assert renamed_helper in built
|
||||
assert report.provenance[renamed_helper].kind == "helper"
|
||||
assert report.provenance[renamed_helper].requested_name == "_helper"
|
||||
assert report.provenance[renamed_helper].owner_declared_name == "impl_tree"
|
||||
assert set(built["tree"].ports) == {"out"}
|
||||
assert set(built["tree"].refs) == {"impl_tree"}
|
||||
|
||||
|
||||
def test_build_cells_view_accepts_direct_tree_and_rejects_multiple_tops() -> None:
|
||||
builder = LibraryBuilder()
|
||||
builder.cells["tree"] = Library({"old_top": Pattern()})
|
||||
built, _report = builder.build(output="library")
|
||||
assert set(built) == {"tree"}
|
||||
|
||||
invalid = LibraryBuilder()
|
||||
invalid.cells["tree"] = cell(lambda: Library({"one": Pattern(), "two": Pattern()}))()
|
||||
with pytest.raises(BuildError, match="exactly one topcell"):
|
||||
invalid.validate()
|
||||
|
||||
|
||||
def test_build_cells_tree_declared_name_wins_over_single_use_helper() -> None:
|
||||
builder = LibraryBuilder()
|
||||
|
||||
def make_tree() -> Library:
|
||||
top = Pattern()
|
||||
top.ref("_device")
|
||||
return Library({"_device": Pattern(), "old_top": top})
|
||||
|
||||
builder.cells["_device"] = cell(make_tree)()
|
||||
built, report = builder.build(output="library")
|
||||
|
||||
assert "_device" in built
|
||||
assert set(built["_device"].refs) != {"_device"}
|
||||
assert len(built["_device"].refs) == 1
|
||||
helper_name = next(iter(built["_device"].refs))
|
||||
assert helper_name in built
|
||||
assert report.provenance["_device"].kind == "declared"
|
||||
assert report.provenance["_device"].requested_name == "_device"
|
||||
assert report.provenance[helper_name].kind == "helper"
|
||||
assert report.provenance[helper_name].requested_name == "_device"
|
||||
assert report.provenance[helper_name].owner_declared_name == "_device"
|
||||
|
||||
|
||||
def test_build_cells_tree_declared_name_rejects_public_helper_conflict() -> None:
|
||||
builder = LibraryBuilder()
|
||||
|
||||
def make_tree() -> Library:
|
||||
top = Pattern()
|
||||
top.ref("device")
|
||||
return Library({"device": Pattern(), "old_top": top})
|
||||
|
||||
builder.cells["device"] = cell(make_tree)()
|
||||
with pytest.raises(BuildError, match="Unresolved duplicate key"):
|
||||
builder.validate()
|
||||
|
||||
del builder.cells["device"]
|
||||
builder.cells["device"] = Pattern()
|
||||
builder.validate()
|
||||
|
||||
|
||||
def test_build_cells_replacement_postprocess_updates_self_published_recipe_and_facade() -> None:
|
||||
builder = LibraryBuilder()
|
||||
cells = builder.cells.prefixed(
|
||||
"impl_",
|
||||
postprocess=lambda _pattern: Pattern(ports={"new": Port((5, 6), 0)}),
|
||||
facade=lambda pattern: ports_to_data(pattern, (50, 0)),
|
||||
)
|
||||
|
||||
def make_device(lib: ILibrary) -> Pattern:
|
||||
original = Pattern(ports={"old": Port((1, 2), 0)})
|
||||
lib["impl_device"] = original
|
||||
return original
|
||||
|
||||
cells.device = cell(make_device)(builder.library)
|
||||
built, report = builder.build(output="library")
|
||||
|
||||
assert set(built) == {"impl_device", "device"}
|
||||
assert set(built["impl_device"].ports) == {"new"}
|
||||
assert set(built["device"].ports) == {"new"}
|
||||
assert built["device"].labels[(50, 0)][0].string.startswith("new:")
|
||||
assert report.provenance["impl_device"].kind == "declared"
|
||||
assert report.dependency_graph["device"] == frozenset({"impl_device"})
|
||||
|
||||
|
||||
def test_build_cells_rejects_unrelated_self_published_pattern() -> None:
|
||||
builder = LibraryBuilder()
|
||||
|
||||
def make_device(lib: ILibrary) -> Pattern:
|
||||
lib["device"] = Pattern()
|
||||
return Pattern()
|
||||
|
||||
builder.cells.device = cell(make_device)(builder.library)
|
||||
with pytest.raises(BuildError, match="wrote a different pattern"):
|
||||
builder.validate()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue