From c32168dc64d50cca87b7b44fbd888c4167fc7f4b Mon Sep 17 00:00:00 2001 From: Jan Petykiewicz Date: Mon, 30 Mar 2026 21:17:33 -0700 Subject: [PATCH] [ILibraryView / Pattern] flatten() should raise PatternError if asked to preserve ports from a repeated ref --- masque/library.py | 5 +++++ masque/pattern.py | 5 +++++ masque/test/test_library.py | 14 ++++++++++++++ masque/test/test_pattern.py | 12 ++++++++++++ 4 files changed, 36 insertions(+) diff --git a/masque/library.py b/masque/library.py index 5b32260..afd25c6 100644 --- a/masque/library.py +++ b/masque/library.py @@ -308,6 +308,11 @@ class ILibraryView(Mapping[str, 'Pattern'], metaclass=ABCMeta): continue for ref in pat.refs[target]: + if flatten_ports and ref.repetition is not None and target_pat.ports: + raise PatternError( + f'Cannot flatten ports from repeated ref to {target!r}; ' + 'flatten with flatten_ports=False or expand/rename the ports manually first.' + ) p = ref.as_pattern(pattern=target_pat) if not flatten_ports: p.ports.clear() diff --git a/masque/pattern.py b/masque/pattern.py index 9014298..0a66aee 100644 --- a/masque/pattern.py +++ b/masque/pattern.py @@ -1082,6 +1082,11 @@ class Pattern(PortList, AnnotatableImpl, Mirrorable): continue for ref in refs: + if flatten_ports and ref.repetition is not None and target_pat.ports: + raise PatternError( + f'Cannot flatten ports from repeated ref to {target!r}; ' + 'flatten with flatten_ports=False or expand/rename the ports manually first.' + ) p = ref.as_pattern(pattern=target_pat) if not flatten_ports: p.ports.clear() diff --git a/masque/test/test_library.py b/masque/test/test_library.py index 5b143fa..9eb705e 100644 --- a/masque/test/test_library.py +++ b/masque/test/test_library.py @@ -77,6 +77,20 @@ def test_library_flatten_preserves_ports_only_child() -> None: assert tuple(flat_parent.ports["P1"].offset) == (11.0, 12.0) +def test_library_flatten_repeated_ref_with_ports_raises() -> None: + lib = Library() + child = Pattern(ports={"P1": Port((1, 2), 0)}) + child.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]]) + lib["child"] = child + + parent = Pattern() + parent.ref("child", repetition=Grid(a_vector=(10, 0), a_count=2)) + lib["parent"] = parent + + with pytest.raises(PatternError, match='Cannot flatten ports from repeated ref'): + lib.flatten("parent", flatten_ports=True) + + def test_lazy_library() -> None: lib = LazyLibrary() called = 0 diff --git a/masque/test/test_pattern.py b/masque/test/test_pattern.py index 338bd39..b459502 100644 --- a/masque/test/test_pattern.py +++ b/masque/test/test_pattern.py @@ -114,6 +114,18 @@ def test_pattern_flatten_preserves_ports_only_child() -> None: assert parent.ports["P1"].rotation == 0 assert tuple(parent.ports["P1"].offset) == (11.0, 12.0) + +def test_pattern_flatten_repeated_ref_with_ports_raises() -> None: + child = Pattern(ports={"P1": Port((1, 2), 0)}) + child.polygon((1, 0), vertices=[[0, 0], [1, 0], [0, 1]]) + + parent = Pattern() + parent.ref("child", repetition=Grid(a_vector=(10, 0), a_count=2)) + + with pytest.raises(PatternError, match='Cannot flatten ports from repeated ref'): + parent.flatten({"child": child}, flatten_ports=True) + + def test_pattern_interface() -> None: source = Pattern() source.ports["A"] = Port((10, 20), 0, ptype="test")