From cf88598bcb2e658630e5e246bc2a7436a6ed2555 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 25 Oct 2026 14:28:58 -0700 Subject: [PATCH 01/52] remove custom init --- edg/core/Blocks.py | 9 +-------- edg/core/HierarchyBlock.py | 2 -- edg/core/Link.py | 1 - 3 files changed, 1 insertion(+), 11 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index d1a7420ca..653b1fc62 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -427,13 +427,6 @@ def _populate_def_proto_port_init(self, pb: edgir.BlockLikeTypes, ref_map: Refab ref_map, ) - def _populate_def_proto_param_init(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) -> None: - for name, param in self._parameters.items(): - if param.initializer is not None: - AssignBinding.populate_assign_proto( - edgir.add_pair(pb.constraints, f"(init){name}"), param, param.initializer, ref_map - ) - def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) -> None: """Populates the contents of a block proto: constraints""" assert ( @@ -577,7 +570,7 @@ def Parameter(self, tpe: ConstraintType, *, doc: Optional[str] = None) -> Constr elt = tpe._bind(ParamBinding(self)) self._parameters.register(elt) if elt.initializer is not None: - self._check_constraint(elt.initializer) + self.assign(elt, elt.initializer, name="(init)") if doc is not None: self._param_docs[elt] = doc diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 8de595f02..0a3d09a05 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -510,8 +510,6 @@ def _def_to_proto(self) -> edgir.HierarchyBlock: self._populate_def_proto_block_base(pb) self._populate_def_proto_port_init(pb, ref_map) - self._populate_def_proto_param_init(pb, ref_map) - self._populate_def_proto_hierarchy(pb, ref_map) self._populate_def_proto_block_contents(pb, ref_map) self._populate_def_proto_description(pb, ref_map) diff --git a/edg/core/Link.py b/edg/core/Link.py index 270e8f692..03c369439 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -46,7 +46,6 @@ def _def_to_proto(self) -> edgir.Link: pb = edgir.Link() self._populate_def_proto_block_base(pb) - self._populate_def_proto_param_init(pb, ref_map) self._populate_def_proto_block_contents(pb, ref_map) self._populate_def_proto_description(pb, ref_map) # specifically ignore the port initializers From 3e780ff7e8a00ca6a7ac41b9637e60e6186467cd Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 25 Oct 2026 15:15:52 -0700 Subject: [PATCH 02/52] Update Blocks.py --- edg/core/Blocks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 653b1fc62..c9efe205f 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -570,7 +570,7 @@ def Parameter(self, tpe: ConstraintType, *, doc: Optional[str] = None) -> Constr elt = tpe._bind(ParamBinding(self)) self._parameters.register(elt) if elt.initializer is not None: - self.assign(elt, elt.initializer, name="(init)") + self.assign(elt, elt.initializer) if doc is not None: self._param_docs[elt] = doc From d138e3bbbc11997a476b828bebf300e4ae03fdbc Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sun, 25 Oct 2026 16:12:09 -0700 Subject: [PATCH 03/52] make manager internal --- edg/core/Blocks.py | 12 ++++++------ edg/core/Core.py | 12 ++++++------ edg/core/HierarchyBlock.py | 8 ++++---- edg/core/MultipackBlock.py | 2 +- edg/core/Ports.py | 8 ++++---- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index c9efe205f..2ee84aac9 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -285,19 +285,19 @@ def __init__(self) -> None: None # additional string field to be displayed as part of the tooltip for blocks ) - self._parameters: SubElementDict[ConstraintExpr] = self.manager.new_dict(ConstraintExpr) + self._parameters: SubElementDict[ConstraintExpr] = self._manager.new_dict(ConstraintExpr) self._param_docs = IdentityDict[ConstraintExpr, str]() - self._ports: SubElementDict[BasePort] = self.manager.new_dict(BasePort) + self._ports: SubElementDict[BasePort] = self._manager.new_dict(BasePort) self._required_ports = IdentityDict[BasePort, Optional[BoolExpr]]() # port -> precondition for required self._port_docs = IdentityDict[BasePort, str]() - self._connects = self.manager.new_dict(Connection, anon_prefix="anon_link") + self._connects = self._manager.new_dict(Connection, anon_prefix="anon_link") self._connects_by_port = IdentityDict[BasePort, Connection]() # port -> connection self._connect_delegateds = IdentityDict[ Connection, List[Connection] ]() # for net joins, joined connect -> prior connects - self._constraints: SubElementDict[ConstraintExpr] = self.manager.new_dict( + self._constraints: SubElementDict[ConstraintExpr] = self._manager.new_dict( ConstraintExpr, anon_prefix="anon_constr" ) @@ -496,7 +496,7 @@ def require(self, constraint: BoolLike, name: Optional[str] = None, *, _unchecke self._constraints.register(constraint_typed) if name: # TODO unify naming API with everything else? - self.manager.add_element(name, constraint_typed) + self._manager.add_element(name, constraint_typed) return constraint_typed @@ -521,7 +521,7 @@ def assign( self._constraints.register(constraint) if name: # TODO unify naming API with everything else? - self.manager.add_element(name, constraint) + self._manager.add_element(name, constraint) return constraint diff --git a/edg/core/Core.py b/edg/core/Core.py index 388bdf11f..27069a9fb 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -207,17 +207,17 @@ def __repr__(self) -> str: def __init__(self) -> None: self._parent: Optional[LibraryElement] = None # set by binding, None means not bound - self.manager = SubElementManager() - self.manager_ignored: Set[str] = set(["_parent"]) + self._manager = SubElementManager() + self._manager_ignored: Set[str] = set(["_parent"]) @override def __setattr__(self, name: str, value: Any) -> None: - if hasattr(self, "manager_ignored") and name not in self.manager_ignored: - self.manager.add_element(name, value) + if hasattr(self, "_manager_ignored") and name not in self._manager_ignored: + self._manager.add_element(name, value) super().__setattr__(name, value) def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str: - self_name = self.manager.name_of(subelt) + self_name = self._manager.name_of(subelt) if self_name is not None: return self_name else: @@ -261,7 +261,7 @@ class HasMetadata(LibraryElement): def __init__(self) -> None: super().__init__() - self._metadata: SubElementDict[Any] = self.manager.new_dict(Any) + self._metadata: SubElementDict[Any] = self._manager.new_dict(Any) MetadataType = TypeVar( "MetadataType", bound=Union[str, Mapping[str, Any], SubElementDict[Any], IdentityDict[Any, Any]] diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 0a3d09a05..140c8bcc8 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -336,13 +336,13 @@ def __init__(self) -> None: self._init_params: Dict[str, ConstraintExpr] for param_name, param in self._init_params.items(): self._parameters.register(param) - self.manager.add_element(param_name, param) + self._manager.add_element(param_name, param) delattr(self, "_init_params") self._mixins: List["BlockInterfaceMixin"] = [] - self._blocks = self.manager.new_dict(Block) - self._chains = self.manager.new_dict(ChainConnect, anon_prefix="anon_chain") + self._blocks = self._manager.new_dict(Block) + self._chains = self._manager.new_dict(ChainConnect, anon_prefix="anon_chain") self._port_tags = IdentityDict[BasePort, Set[PortTag]]() def _get_ports_by_tag(self, tags: Set[PortTag]) -> List[BasePort]: @@ -542,7 +542,7 @@ def with_mixin(self, tpe: MixinType) -> MixinType: assert self._parent is not None elt = tpe._bind(self._parent) - self._parent.manager.add_alias(elt, self) + self._parent._manager.add_alias(elt, self) self._mixins.append(elt) return elt diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index 0b615504b..145cade0f 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -132,7 +132,7 @@ class MultipackBlock(Block): def __init__(self) -> None: super().__init__() - self._packed_blocks: SubElementDict[PackedBlockTypes] = self.manager.new_dict((Block, PackedBlockArray)) + self._packed_blocks: SubElementDict[PackedBlockTypes] = self._manager.new_dict((Block, PackedBlockArray)) # TODO should these be defined in terms of Refs? # packed block -> (exterior port -> packed block port) self._packed_connects_by_packed_block = IdentityDict[ diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 0fba17ecd..bd5772767 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -149,10 +149,10 @@ def __init__(self) -> None: self._bridge_instance: Optional[PortBridge] = None # internal only self._adapter_count: int = 0 - self._parameters: SubElementDict[ConstraintExpr] = self.manager.new_dict(ConstraintExpr) - self._ports: SubElementDict[Port] = self.manager.new_dict(Port) + self._parameters: SubElementDict[ConstraintExpr] = self._manager.new_dict(ConstraintExpr) + self._ports: SubElementDict[Port] = self._manager.new_dict(Port) - self.manager_ignored.update(["_is_connected", "_name"]) + self._manager_ignored.update(["_is_connected", "_name"]) self._is_connected = BoolExpr()._bind(IsConnectedBinding(self)) self._name = StringExpr()._bind(NameBinding(self)) @@ -214,7 +214,7 @@ def _convert(self, adapter: PortAdapter[ConvertTargetType]) -> ConvertTargetType adapter_inst = enclosing_block.Block(adapter) adapter_name_suffix = f"_{self._adapter_count}" if self._adapter_count > 0 else "" - enclosing_block.manager.add_element( + enclosing_block._manager.add_element( f"(adapter){self._name_from(enclosing_block)}{adapter_name_suffix}", adapter_inst ) enclosing_block.connect(self, adapter_inst.src) # we don't name it to avoid explicit name conflicts From 377abcb5911e98ea298ca4e53dd85f2954763b54 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 11:29:39 -0700 Subject: [PATCH 04/52] patchy refactoring of param naming system --- edg/core/Blocks.py | 19 +++++++------- edg/core/Core.py | 51 +++++++++++++++++++++++++++++++++++--- edg/core/HierarchyBlock.py | 13 +++++----- edg/core/MultipackBlock.py | 2 +- edg/core/Util.py | 2 +- 5 files changed, 66 insertions(+), 21 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 2ee84aac9..ef5cd7748 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -285,7 +285,7 @@ def __init__(self) -> None: None # additional string field to be displayed as part of the tooltip for blocks ) - self._parameters: SubElementDict[ConstraintExpr] = self._manager.new_dict(ConstraintExpr) + self._parameters: List[ConstraintExpr] = [] self._param_docs = IdentityDict[ConstraintExpr, str]() self._ports: SubElementDict[BasePort] = self._manager.new_dict(BasePort) @@ -350,9 +350,9 @@ def _add_doc_metadata(self) -> None: metadata_dict: Dict[str, str] = {} if self.__doc__: metadata_dict[""] = inspect.cleandoc(self.__doc__) - for name, param in self._parameters.items(): + for param in self._parameters: if param in self._param_docs: - metadata_dict[name] = self._param_docs[param] + metadata_dict[self._manager.name(param)] = self._param_docs[param] for name, port in self._ports.items(): if port in self._port_docs: metadata_dict[name] = self._port_docs[port] @@ -365,7 +365,7 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: or self._elaboration_state == BlockElaborationState.post_generate ) - self._parameters.finalize() + self._manager.validate_names() self._ports.finalize() if (self.__class__, AbstractBlockProperty) in self._elt_properties: @@ -387,9 +387,9 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: for cls in indirect_bases: pb.super_superclasses.add().target.name = cls._static_def_name() - for name, param in self._parameters.items(): + for param in self._parameters: assert isinstance(param.binding, ParamBinding) - param._populate_decl_proto(edgir.add_pair(pb.params, name)) + param._populate_decl_proto(edgir.add_pair(pb.params, self._manager.name(param))) for name, port in self._ports.items(): port._populate_portlike_proto(edgir.add_pair(pb.ports, name)) @@ -447,8 +447,8 @@ def _populate_def_proto_description(self, pb: edgir.BlockLikeTypes, ref_map: Ref def _build_ref_map(self, ref_map: Refable.RefMapType, prefix: edgir.LocalPath) -> None: super()._build_ref_map(ref_map, prefix) ref_map[self.name()] = edgir.localpath_concat(prefix, edgir.NAME) - for name, param in self._parameters.items(): - param._build_ref_map(ref_map, edgir.localpath_concat(prefix, name)) + for param in self._parameters: + param._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._manager.name(param))) for name, port in self._ports.items(): port._build_ref_map(ref_map, edgir.localpath_concat(prefix, name)) @@ -568,7 +568,8 @@ def Parameter(self, tpe: ConstraintType, *, doc: Optional[str] = None) -> Constr raise EdgTypeError(f"param to Parameter(...)", tpe, ConstraintExpr) elt = tpe._bind(ParamBinding(self)) - self._parameters.register(elt) + self._parameters.append(elt) + self._manager.add_expected(elt) if elt.initializer is not None: self.assign(elt, elt.initializer) diff --git a/edg/core/Core.py b/edg/core/Core.py index 27069a9fb..e05937c1b 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -81,11 +81,31 @@ def name_of(self, elt: ElementType) -> Optional[str]: return None -class SubElementManager: +class NamespaceManager: def __init__(self) -> None: self.dicts: List[Tuple[Union[Type[Any], Tuple[Type[Any], ...]], SubElementDict[Any]]] = [] self.aliases = IdentityDict[Any, Any]() + self._base_names = IdentityDict[Any, Optional[str]]() + + def validate_names(self) -> bool: + # asserts that all names are given and valid + seen_names: Set[str] = set() + missing_names: List[Any] = [] + duplicate_names: List[str] = [] + for obj, name in self._base_names.items(): + if name is None: + missing_names.append(obj) + if name in seen_names: + duplicate_names.append(name) + seen_names.add(name) + + # TODO replace with EDG error type + if missing_names: + raise ValueError(f"no name for objects {missing_names}") + if duplicate_names: + raise ValueError(f"duplicate names {duplicate_names}") + def new_dict( self, filter_type: Union[Type[ElementType], Tuple[Type[ElementType], ...]], anon_prefix: Optional[str] = None ) -> SubElementDict[ElementType]: @@ -109,10 +129,24 @@ def add_element(self, name: str, item: Any) -> None: assert name not in dict.container or dict.names.get(item, None) == name, f"duplicate name {name}" assert len(assigned) <= 1, f"assigned {item} to multiple SubElementDict {assigned}" + def add_expected(self, item: Any) -> None: + self._base_names[item] = None + + def add_name(self, item: Any, name: str) -> None: + existing = self._base_names.get(item, 0) # uses 0 as guard + if existing == 0: + return # not expected, ignore + elif existing is not None: + raise ValueError(f"attempted to reassign name {name} to item with prior name {existing}") + elif existing is None: + self._base_names.update(item, name) + def name_of(self, item: Any) -> Optional[str]: if item in self.aliases: item = self.aliases[item] name_candidates = [sub_dict.name_of(item) for sub_dict_type, sub_dict in self.dicts] + if item in self._base_names: + name_candidates.append(self._base_names[item]) name_candidates_filtered = [name_candidate for name_candidate in name_candidates if name_candidate is not None] assert len(name_candidates_filtered) <= 1, f"more than 1 name candidates {name_candidates} for {item}" if not name_candidates_filtered: @@ -120,6 +154,13 @@ def name_of(self, item: Any) -> Optional[str]: else: return name_candidates_filtered[0] + def name(self, item: Any) -> str: + name = self.name_of(item) + if name is None: + raise ValueError(f"no name for {item}") + else: + return name + class ElementDict(Generic[ElementType]): """Dict that contains sub-elements, basically a dict with a hook that notifies its parent when items are added.""" @@ -127,9 +168,9 @@ class ElementDict(Generic[ElementType]): # TODO also have a KeyType? Perhaps enforce type bounds, eg must be str-able? def __init__(self) -> None: self.container: Dict[Union[str, int], ElementType] = {} - self._parent: Optional[Tuple[str, SubElementManager]] = None # name prefix, pointer to top level + self._parent: Optional[Tuple[str, NamespaceManager]] = None # name prefix, pointer to top level - def _set_parent(self, parent: Tuple[str, SubElementManager]) -> None: + def _set_parent(self, parent: Tuple[str, NamespaceManager]) -> None: self._parent = parent def __setitem__(self, key: Union[str, int], value: ElementType) -> None: @@ -207,13 +248,15 @@ def __repr__(self) -> str: def __init__(self) -> None: self._parent: Optional[LibraryElement] = None # set by binding, None means not bound - self._manager = SubElementManager() + self._manager = NamespaceManager() self._manager_ignored: Set[str] = set(["_parent"]) @override def __setattr__(self, name: str, value: Any) -> None: if hasattr(self, "_manager_ignored") and name not in self._manager_ignored: self._manager.add_element(name, value) + if hasattr(self, "_manager"): # for before _manager is defined + self._manager.add_name(name, value) super().__setattr__(name, value) def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str: diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 140c8bcc8..9d93caeef 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -335,8 +335,9 @@ def __init__(self) -> None: if hasattr(self, "_init_params"): # used to propagate params generated in the metaclass __init__ hook self._init_params: Dict[str, ConstraintExpr] for param_name, param in self._init_params.items(): - self._parameters.register(param) - self._manager.add_element(param_name, param) + self._parameters.append(param) + self._manager.add_expected(param) + self._manager.add_name(param, param_name) delattr(self, "_init_params") self._mixins: List["BlockInterfaceMixin"] = [] @@ -372,11 +373,11 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: assert isinstance(pb, edgir.HierarchyBlock) # generate param defaults - for param_name, param in self._parameters.items(): + for param in self._parameters: if isinstance(param.binding, InitParamBinding) and param.binding.value is not None: # default values can't depend on anything so the ref_map is empty param_typed_value = param._to_expr_type(param.binding.value) - param_typed_value._populate_expr_proto(pb.param_defaults[param_name], IdentityDict()) + param_typed_value._populate_expr_proto(pb.param_defaults[self._manager.name(param)], IdentityDict()) def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refable.RefMapType) -> None: self._blocks.finalize() @@ -485,9 +486,9 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab # generate block initializers for block_name, block in self._blocks.items(): - all_block_params = dict(block._parameters.items()) + all_block_params = {block._manager.name(param): param for param in block._parameters} for mixin in block._mixins: - all_block_params.update(mixin._parameters.items()) + all_block_params.update({mixin._manager.name(param): param for param in mixin._parameters}) for block_param_name, block_param in all_block_params.items(): if isinstance(block_param.binding, InitParamBinding) and block_param.binding.value is not None: param_typed_value = block_param._to_expr_type(block_param.binding.value) diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index 145cade0f..516d5b348 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -252,7 +252,7 @@ def PackedParameter(self, packed_param: PackedParamTypes) -> ConstraintExpr: else: raise TypeError() self.packed_assign(new_param, packed_param) - self._parameters.register(new_param) + self._parameters.append(new_param) return new_param def unpacked_assign(self, packed_param: UnpackedParamTypes, self_param: ConstraintExpr) -> None: diff --git a/edg/core/Util.py b/edg/core/Util.py index 9cff2f148..f76a33162 100644 --- a/edg/core/Util.py +++ b/edg/core/Util.py @@ -21,7 +21,7 @@ def edg_to_dict(obj: Union[BasePort, BaseBlock]) -> Dict[str, Any]: elif isinstance(obj, Block) or isinstance(obj, Link): return { "_": edg_obj_name(obj), - "params": obj._parameters, + "params": {obj._manager.name(param): param for param in obj._parameters}, "ports": {k: edg_to_dict(v) for k, v in obj._ports.items()}, } else: From 800658e4e2b1842078bec9a2405d95ea1104584f Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 11:32:57 -0700 Subject: [PATCH 05/52] fixes --- edg/core/Core.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index e05937c1b..c0a7c01d6 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -136,6 +136,8 @@ def add_name(self, item: Any, name: str) -> None: existing = self._base_names.get(item, 0) # uses 0 as guard if existing == 0: return # not expected, ignore + elif existing == name: + return # already assigned, ignore elif existing is not None: raise ValueError(f"attempted to reassign name {name} to item with prior name {existing}") elif existing is None: @@ -256,7 +258,7 @@ def __setattr__(self, name: str, value: Any) -> None: if hasattr(self, "_manager_ignored") and name not in self._manager_ignored: self._manager.add_element(name, value) if hasattr(self, "_manager"): # for before _manager is defined - self._manager.add_name(name, value) + self._manager.add_name(value, name) super().__setattr__(name, value) def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str: From f603a0404def2ba2c2cf61fc9bc389aa389fe54a Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 11:49:41 -0700 Subject: [PATCH 06/52] quickndirty source locator infrastructure --- edg/core/Blocks.py | 8 ++++-- edg/core/Core.py | 57 ++++++++++++++++++++++++++------------ edg/core/HierarchyBlock.py | 4 +-- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index ef5cd7748..228970a69 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -12,7 +12,7 @@ from .Array import BaseVector, Vector from .Binding import AssignBinding, NameBinding from .ConstraintExpr import ConstraintExpr, BoolExpr, ParamBinding, AssignExpr, StringExpr, BoolLike -from .Core import Refable, HasMetadata, builder, SubElementDict, non_library, EltPropertiesBase +from .Core import Refable, HasMetadata, builder, SubElementDict, non_library, EltPropertiesBase, SourceLocator from .HdlUserExceptions import * from .IdentityDict import IdentityDict from .IdentitySet import IdentitySet @@ -569,7 +569,11 @@ def Parameter(self, tpe: ConstraintType, *, doc: Optional[str] = None) -> Constr elt = tpe._bind(ParamBinding(self)) self._parameters.append(elt) - self._manager.add_expected(elt) + calling_frame = inspect.currentframe() + assert calling_frame is not None + calling_frame = calling_frame.f_back + assert calling_frame is not None + self._manager.add_expected(elt, SourceLocator.from_frame(calling_frame)) if elt.initializer is not None: self.assign(elt, elt.initializer) diff --git a/edg/core/Core.py b/edg/core/Core.py index c0a7c01d6..1eb81b696 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -81,24 +81,43 @@ def name_of(self, elt: ElementType) -> Optional[str]: return None +class SourceLocator: + """Object that can be used to locate the source of a library element, for error reporting.""" + + def __init__(self, filename: str, lineno: int) -> None: + self.filename = filename + self.lineno = lineno + + @classmethod + def from_frame(cls, frame: Any) -> SourceLocator: + return cls(frame.f_code.co_filename, frame.f_lineno) + + @override + def __repr__(self) -> str: + return f"{self.filename}:{self.lineno}" + + class NamespaceManager: def __init__(self) -> None: self.dicts: List[Tuple[Union[Type[Any], Tuple[Type[Any], ...]], SubElementDict[Any]]] = [] self.aliases = IdentityDict[Any, Any]() - self._base_names = IdentityDict[Any, Optional[str]]() + self._base_names = IdentityDict[Any, Union[str, SourceLocator]]() - def validate_names(self) -> bool: + def validate_names(self) -> None: # asserts that all names are given and valid seen_names: Set[str] = set() - missing_names: List[Any] = [] + missing_names: List[Tuple[Any, SourceLocator]] = [] duplicate_names: List[str] = [] for obj, name in self._base_names.items(): - if name is None: - missing_names.append(obj) - if name in seen_names: - duplicate_names.append(name) - seen_names.add(name) + if isinstance(name, str): + if name in seen_names: + duplicate_names.append(name) + seen_names.add(name) + elif isinstance(name, SourceLocator): + missing_names.append((obj, name)) + else: + raise TypeError("unexpected name entry type") # TODO replace with EDG error type if missing_names: @@ -129,26 +148,29 @@ def add_element(self, name: str, item: Any) -> None: assert name not in dict.container or dict.names.get(item, None) == name, f"duplicate name {name}" assert len(assigned) <= 1, f"assigned {item} to multiple SubElementDict {assigned}" - def add_expected(self, item: Any) -> None: - self._base_names[item] = None + def add_expected(self, item: Any, source_locator: SourceLocator) -> None: + self._base_names[item] = source_locator def add_name(self, item: Any, name: str) -> None: - existing = self._base_names.get(item, 0) # uses 0 as guard - if existing == 0: + existing = self._base_names.get(item, None) + if existing is None: return # not expected, ignore elif existing == name: return # already assigned, ignore - elif existing is not None: + elif isinstance(existing, str): raise ValueError(f"attempted to reassign name {name} to item with prior name {existing}") - elif existing is None: + elif isinstance(existing, SourceLocator): self._base_names.update(item, name) + else: + raise ValueError("unexpected name entry type") def name_of(self, item: Any) -> Optional[str]: if item in self.aliases: item = self.aliases[item] name_candidates = [sub_dict.name_of(item) for sub_dict_type, sub_dict in self.dicts] - if item in self._base_names: - name_candidates.append(self._base_names[item]) + base_name_candidate = self._base_names.get(item, None) + if isinstance(base_name_candidate, str): + name_candidates.append(base_name_candidate) name_candidates_filtered = [name_candidate for name_candidate in name_candidates if name_candidate is not None] assert len(name_candidates_filtered) <= 1, f"more than 1 name candidates {name_candidates} for {item}" if not name_candidates_filtered: @@ -159,7 +181,8 @@ def name_of(self, item: Any) -> Optional[str]: def name(self, item: Any) -> str: name = self.name_of(item) if name is None: - raise ValueError(f"no name for {item}") + base_name_of = self._base_names.get(item, None) + raise ValueError(f"no name for {item} at {base_name_of}") else: return name diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 9d93caeef..e3a18070a 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -27,7 +27,7 @@ from .Blocks import BaseBlock, Connection, BlockElaborationState, AbstractBlockProperty, BaseBlockMeta from .ConstraintExpr import BoolLike, FloatLike, IntLike, RangeLike, StringLike from .ConstraintExpr import ConstraintExpr, BoolExpr, FloatExpr, IntExpr, RangeExpr, StringExpr -from .Core import Refable, non_library +from .Core import Refable, non_library, SourceLocator from .HdlUserExceptions import * from .IdentityDict import IdentityDict from .IdentitySet import IdentitySet @@ -336,7 +336,7 @@ def __init__(self) -> None: self._init_params: Dict[str, ConstraintExpr] for param_name, param in self._init_params.items(): self._parameters.append(param) - self._manager.add_expected(param) + self._manager.add_expected(param, SourceLocator.from_frame(inspect.currentframe())) self._manager.add_name(param, param_name) delattr(self, "_init_params") From a8cbfd77070dd8c96e1f9c5ba3207b523c575c99 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 11:54:09 -0700 Subject: [PATCH 07/52] fix missing register in multipack case --- edg/core/Blocks.py | 6 +----- edg/core/Core.py | 11 +++++++++-- edg/core/HierarchyBlock.py | 2 +- edg/core/MultipackBlock.py | 3 ++- 4 files changed, 13 insertions(+), 9 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 228970a69..cf43bc184 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -569,11 +569,7 @@ def Parameter(self, tpe: ConstraintType, *, doc: Optional[str] = None) -> Constr elt = tpe._bind(ParamBinding(self)) self._parameters.append(elt) - calling_frame = inspect.currentframe() - assert calling_frame is not None - calling_frame = calling_frame.f_back - assert calling_frame is not None - self._manager.add_expected(elt, SourceLocator.from_frame(calling_frame)) + self._manager.add_expected(elt, SourceLocator.from_current_frame(back=2)) if elt.initializer is not None: self.assign(elt, elt.initializer) diff --git a/edg/core/Core.py b/edg/core/Core.py index 1eb81b696..e876bb60c 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -1,5 +1,6 @@ from __future__ import annotations +import inspect from typing import * from abc import abstractmethod @@ -89,8 +90,14 @@ def __init__(self, filename: str, lineno: int) -> None: self.lineno = lineno @classmethod - def from_frame(cls, frame: Any) -> SourceLocator: - return cls(frame.f_code.co_filename, frame.f_lineno) + def from_current_frame(cls, back: int) -> SourceLocator: + # back defines the number of frames to go back, 2 is the call site's caller + calling_frame = inspect.currentframe() + assert calling_frame is not None + for i in range(back): + calling_frame = calling_frame.f_back + assert calling_frame is not None + return cls(calling_frame.f_code.co_filename, calling_frame.f_lineno) @override def __repr__(self) -> str: diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index e3a18070a..ac60e2d4a 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -336,7 +336,7 @@ def __init__(self) -> None: self._init_params: Dict[str, ConstraintExpr] for param_name, param in self._init_params.items(): self._parameters.append(param) - self._manager.add_expected(param, SourceLocator.from_frame(inspect.currentframe())) + self._manager.add_expected(param, SourceLocator.from_current_frame(back=2)) self._manager.add_name(param, param_name) delattr(self, "_init_params") diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index 516d5b348..4dc21e7a3 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -10,7 +10,7 @@ from .Blocks import BlockElaborationState from .HdlUserExceptions import BlockDefinitionError, EdgTypeError from .IdentityDict import IdentityDict -from .Core import non_library, SubElementDict +from .Core import non_library, SubElementDict, SourceLocator from .ConstraintExpr import ConstraintExpr, BoolExpr, IntExpr, FloatExpr, RangeExpr, StringExpr from .Ports import BasePort, Port from .HierarchyBlock import Block, BlockPrototype @@ -253,6 +253,7 @@ def PackedParameter(self, packed_param: PackedParamTypes) -> ConstraintExpr: raise TypeError() self.packed_assign(new_param, packed_param) self._parameters.append(new_param) + self._manager.add_expected(new_param, SourceLocator.from_current_frame(back=2)) return new_param def unpacked_assign(self, packed_param: UnpackedParamTypes, self_param: ConstraintExpr) -> None: From 745de0008f5d03bc7259b35d5f2092a882c0e02f Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 12:07:27 -0700 Subject: [PATCH 08/52] improve naming flow --- edg/core/Blocks.py | 2 +- edg/core/Core.py | 25 +++++++++++++------------ edg/core/HdlUserExceptions.py | 4 ++++ edg/core/HierarchyBlock.py | 2 ++ edg/core/test_block_errors.py | 23 +++++++++++++++++++++++ 5 files changed, 43 insertions(+), 13 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index cf43bc184..156e8d047 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -365,7 +365,7 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: or self._elaboration_state == BlockElaborationState.post_generate ) - self._manager.validate_names() + self._manager.validate_names(type(self)) self._ports.finalize() if (self.__class__, AbstractBlockProperty) in self._elt_properties: diff --git a/edg/core/Core.py b/edg/core/Core.py index e876bb60c..f5ddd7d44 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -6,11 +6,15 @@ from typing_extensions import override +from .HdlUserExceptions import ElementNamingError from .. import edgir from .Builder import builder from .IdentityDict import IdentityDict from .IdentitySet import IdentitySet +if TYPE_CHECKING: + from .Block import BaseBlock + ElementType = TypeVar("ElementType") @@ -111,7 +115,7 @@ def __init__(self) -> None: self._base_names = IdentityDict[Any, Union[str, SourceLocator]]() - def validate_names(self) -> None: + def validate_names(self, container_tpe: Type["BaseBlock"]) -> None: # asserts that all names are given and valid seen_names: Set[str] = set() missing_names: List[Tuple[Any, SourceLocator]] = [] @@ -126,11 +130,10 @@ def validate_names(self) -> None: else: raise TypeError("unexpected name entry type") - # TODO replace with EDG error type if missing_names: - raise ValueError(f"no name for objects {missing_names}") + raise ElementNamingError(container_tpe, f"no name for objects {missing_names}") if duplicate_names: - raise ValueError(f"duplicate names {duplicate_names}") + raise ElementNamingError(container_tpe, f"duplicate names {duplicate_names}") def new_dict( self, filter_type: Union[Type[ElementType], Tuple[Type[ElementType], ...]], anon_prefix: Optional[str] = None @@ -175,9 +178,6 @@ def name_of(self, item: Any) -> Optional[str]: if item in self.aliases: item = self.aliases[item] name_candidates = [sub_dict.name_of(item) for sub_dict_type, sub_dict in self.dicts] - base_name_candidate = self._base_names.get(item, None) - if isinstance(base_name_candidate, str): - name_candidates.append(base_name_candidate) name_candidates_filtered = [name_candidate for name_candidate in name_candidates if name_candidate is not None] assert len(name_candidates_filtered) <= 1, f"more than 1 name candidates {name_candidates} for {item}" if not name_candidates_filtered: @@ -186,12 +186,13 @@ def name_of(self, item: Any) -> Optional[str]: return name_candidates_filtered[0] def name(self, item: Any) -> str: - name = self.name_of(item) - if name is None: - base_name_of = self._base_names.get(item, None) - raise ValueError(f"no name for {item} at {base_name_of}") - else: + name = self._base_names.get(item, None) + if isinstance(name, str): return name + elif isinstance(name, SourceLocator): + raise ValueError(f"no name for {item} at {name} (internal error, should have been caught at validation)") + else: + raise ValueError(f"no name for unexpected {item} (internal consistency error)") class ElementDict(Generic[ElementType]): diff --git a/edg/core/HdlUserExceptions.py b/edg/core/HdlUserExceptions.py index b8ca0983e..c7bc7285f 100644 --- a/edg/core/HdlUserExceptions.py +++ b/edg/core/HdlUserExceptions.py @@ -57,3 +57,7 @@ def __init__(self, block_type: Type["BaseBlock"], exc: str, resolution: str = "" class ChainError(BlockDefinitionError): """Base error for bad elements in a chain connect""" + + +class ElementNamingError(BlockDefinitionError): + """Error for when an element has an invalid (missing, duplicated) name""" diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index ac60e2d4a..3001b6a35 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -502,6 +502,8 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab # TODO make this non-overriding? @override def _def_to_proto(self) -> edgir.HierarchyBlock: + self._manager.validate_names(type(self)) + assert not self._mixins # blocks with mixins can only be instantiated anonymously ref_map = self._create_ref_map() diff --git a/edg/core/test_block_errors.py b/edg/core/test_block_errors.py index b853f421c..1765b3372 100644 --- a/edg/core/test_block_errors.py +++ b/edg/core/test_block_errors.py @@ -77,3 +77,26 @@ def contents(self) -> None: def test_ambiguous_join(self) -> None: with self.assertRaises(UnconnectableError): self.AmbiguousJoinBlock()._elaborated_def_to_proto() + + +class BadNamingTestCase(unittest.TestCase): + class UnnamedParamBlock(Block): + def __init__(self) -> None: + super().__init__() + self.Parameter(FloatExpr()) # unnamed param, should error + + def test_unnamed_param(self) -> None: + with self.assertRaises(ElementNamingError) as exc: + self.UnnamedParamBlock()._elaborated_def_to_proto() + self.assertIn("86", str(exc)) + + class DuplicateNameBlock(Block): + def __init__(self) -> None: + super().__init__() + self.duplicate_name = self.Parameter(FloatExpr()) + self.duplicate_name = self.Parameter(FloatExpr()) + + def test_duplicate_name(self) -> None: + with self.assertRaises(ElementNamingError) as exc: + self.DuplicateNameBlock()._elaborated_def_to_proto() + self.assertIn("duplicate_name", str(exc)) From 12080db15ebcd03dafbfd9afee77a3fffc6ddf88 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 12:17:56 -0700 Subject: [PATCH 09/52] Update Connectors.py --- edg/abstract_parts/Connectors.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edg/abstract_parts/Connectors.py b/edg/abstract_parts/Connectors.py index 42261a839..8d07f565a 100644 --- a/edg/abstract_parts/Connectors.py +++ b/edg/abstract_parts/Connectors.py @@ -32,9 +32,9 @@ def __init__(self) -> None: class RfConnectorTestPoint(BlockInterfaceMixin[RfConnector]): """Test point mixin that allows the footprint to take a name""" - def __init__(self, name: StringLike): + def __init__(self, tp_name: StringLike): super().__init__() - self.tp_name = self.ArgParameter(name) + self.tp_name = self.ArgParameter(tp_name) class RfConnectorAntenna(Antenna): From f7e61dcbe01461f94855051d442b6c656b7cb5d8 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 12:20:59 -0700 Subject: [PATCH 10/52] Update DesignTop.py --- edg/core/DesignTop.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/edg/core/DesignTop.py b/edg/core/DesignTop.py index 70de33375..978a04c2b 100644 --- a/edg/core/DesignTop.py +++ b/edg/core/DesignTop.py @@ -141,14 +141,14 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: for multipack_param, packed_param in packing_rule.tunnel_assigns.items(): if isinstance(packed_param, ConstraintExpr): - packed_param_name = multipack_part_block._name_of_child(packed_param, self) + packed_param_name = multipack_part_block._manager.name(packed_param) assign_tunnel = edgir.add_pair( pb.constraints, f"(packed){multipack_name}.{part_name}.{packed_param_name}" ).assignTunnel assign_tunnel.dst.CopyFrom(multipack_ref_map[multipack_param]) assign_tunnel.src.ref.CopyFrom(packed_ref_map[packed_param]) elif isinstance(packed_param, PackedBlockParamArray): - multipack_param_name = multipack_block._name_of_child(multipack_param, self) + multipack_param_name = multipack_block._manager.name(multipack_param) constr_name = f"(packed){multipack_name}.{multipack_param_name}" packed_params.setdefault(constr_name, (multipack_ref_map[multipack_param], []))[1].append( packed_ref_map[packed_param.param] @@ -158,7 +158,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: for multipack_param, unpacked_param in packing_rule.tunnel_unpack_assigns.items(): if isinstance(unpacked_param, ConstraintExpr): - multipack_param_name = multipack_block._name_of_child(multipack_param, self) + multipack_param_name = multipack_block._manager.name(multipack_param) # TODO need better constraint naming scheme assign_tunnel = edgir.add_pair( pb.constraints, f"(unpacked){multipack_name}.{part_name}.{multipack_param_name}" @@ -166,7 +166,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: assign_tunnel.dst.CopyFrom(packed_ref_map[unpacked_param]) assign_tunnel.src.ref.CopyFrom(multipack_ref_map[multipack_param]) elif isinstance(unpacked_param, PackedBlockParam): - multipack_param_name = multipack_block._name_of_child(multipack_param, self) + multipack_param_name = multipack_block._manager.name(multipack_param) # TODO need better constraint naming scheme assign_tunnel = edgir.add_pair( pb.constraints, f"(unpacked){multipack_name}.{part_name}.{multipack_param_name}" From b1a5e844c6516563e6f9429cd4ab2697566fa379 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 12:56:06 -0700 Subject: [PATCH 11/52] lazy source locators --- edg/core/Blocks.py | 2 +- edg/core/Core.py | 30 ++++++++++++++++++------------ edg/core/HierarchyBlock.py | 2 +- edg/core/MultipackBlock.py | 2 +- 4 files changed, 21 insertions(+), 15 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 156e8d047..af1633e73 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -569,7 +569,7 @@ def Parameter(self, tpe: ConstraintType, *, doc: Optional[str] = None) -> Constr elt = tpe._bind(ParamBinding(self)) self._parameters.append(elt) - self._manager.add_expected(elt, SourceLocator.from_current_frame(back=2)) + self._manager.add_expected(elt, SourceLocator.from_caller()) if elt.initializer is not None: self.assign(elt, elt.initializer) diff --git a/edg/core/Core.py b/edg/core/Core.py index f5ddd7d44..4402035d9 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -1,6 +1,7 @@ from __future__ import annotations import inspect +from types import FrameType from typing import * from abc import abstractmethod @@ -89,23 +90,24 @@ def name_of(self, elt: ElementType) -> Optional[str]: class SourceLocator: """Object that can be used to locate the source of a library element, for error reporting.""" - def __init__(self, filename: str, lineno: int) -> None: - self.filename = filename - self.lineno = lineno + def __init__(self, top_frame: Optional[FrameType], depth: int) -> None: + self._top_frame = top_frame + self._depth = depth @classmethod - def from_current_frame(cls, back: int) -> SourceLocator: - # back defines the number of frames to go back, 2 is the call site's caller - calling_frame = inspect.currentframe() - assert calling_frame is not None - for i in range(back): - calling_frame = calling_frame.f_back - assert calling_frame is not None - return cls(calling_frame.f_code.co_filename, calling_frame.f_lineno) + def from_caller(cls) -> SourceLocator: + # returns the source locator for the calling frame of the caller + return cls(inspect.currentframe(), 2) @override def __repr__(self) -> str: - return f"{self.filename}:{self.lineno}" + """Evaluation is lazy since the results are almost never used""" + frame = self._top_frame + assert frame is not None + for i in range(self._depth): + frame = frame.f_back + assert frame is not None + return f"{frame.f_code.co_filename}:{frame.f_lineno}" class NamespaceManager: @@ -135,6 +137,7 @@ def validate_names(self, container_tpe: Type["BaseBlock"]) -> None: if duplicate_names: raise ElementNamingError(container_tpe, f"duplicate names {duplicate_names}") + # TODO remove old API def new_dict( self, filter_type: Union[Type[ElementType], Tuple[Type[ElementType], ...]], anon_prefix: Optional[str] = None ) -> SubElementDict[ElementType]: @@ -145,6 +148,7 @@ def new_dict( def add_alias(self, src: Any, target: Any) -> None: self.aliases[src] = target + # TODO remove old API def add_element(self, name: str, item: Any) -> None: if isinstance(item, ElementDict): item._set_parent((name, self)) @@ -161,6 +165,7 @@ def add_element(self, name: str, item: Any) -> None: def add_expected(self, item: Any, source_locator: SourceLocator) -> None: self._base_names[item] = source_locator + # TODO remove old API def add_name(self, item: Any, name: str) -> None: existing = self._base_names.get(item, None) if existing is None: @@ -174,6 +179,7 @@ def add_name(self, item: Any, name: str) -> None: else: raise ValueError("unexpected name entry type") + # TODO: remove this, replace with debug_name def name_of(self, item: Any) -> Optional[str]: if item in self.aliases: item = self.aliases[item] diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 3001b6a35..20c9a8311 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -336,7 +336,7 @@ def __init__(self) -> None: self._init_params: Dict[str, ConstraintExpr] for param_name, param in self._init_params.items(): self._parameters.append(param) - self._manager.add_expected(param, SourceLocator.from_current_frame(back=2)) + self._manager.add_expected(param, SourceLocator.from_caller()) self._manager.add_name(param, param_name) delattr(self, "_init_params") diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index 4dc21e7a3..354080769 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -253,7 +253,7 @@ def PackedParameter(self, packed_param: PackedParamTypes) -> ConstraintExpr: raise TypeError() self.packed_assign(new_param, packed_param) self._parameters.append(new_param) - self._manager.add_expected(new_param, SourceLocator.from_current_frame(back=2)) + self._manager.add_expected(new_param, SourceLocator.from_caller()) return new_param def unpacked_assign(self, packed_param: UnpackedParamTypes, self_param: ConstraintExpr) -> None: From 4ea3676a20bac343cfbecc59b2bc5e6dadac0fe0 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 13:56:32 -0700 Subject: [PATCH 12/52] basic Block._ports refactor --- edg/core/Blocks.py | 31 ++++++++++--------- edg/core/Core.py | 2 +- edg/core/HierarchyBlock.py | 2 +- edg/core/Util.py | 2 +- .../CircuitPackingBlock.py | 6 ++-- 5 files changed, 22 insertions(+), 21 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index af1633e73..43126c0ff 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -125,7 +125,7 @@ def add_ports(self, ports: Iterable[BasePort]) -> None: assert not self.link_connected_ports assert not self.bridged_ports assert not self.available_link_ports_by_type - for name, port in link._ports.items(): + for port in link._ports: self.available_link_ports_by_type.setdefault(type(self._baseport_leaf_type(port)), []).append(port) else: link = self.link_instance @@ -288,7 +288,7 @@ def __init__(self) -> None: self._parameters: List[ConstraintExpr] = [] self._param_docs = IdentityDict[ConstraintExpr, str]() - self._ports: SubElementDict[BasePort] = self._manager.new_dict(BasePort) + self._ports: List[BasePort] = [] self._required_ports = IdentityDict[BasePort, Optional[BoolExpr]]() # port -> precondition for required self._port_docs = IdentityDict[BasePort, str]() @@ -353,9 +353,9 @@ def _add_doc_metadata(self) -> None: for param in self._parameters: if param in self._param_docs: metadata_dict[self._manager.name(param)] = self._param_docs[param] - for name, port in self._ports.items(): + for port in self._ports: if port in self._port_docs: - metadata_dict[name] = self._port_docs[port] + metadata_dict[self._manager.name(port)] = self._port_docs[port] self._docs = self.Metadata(metadata_dict) def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: @@ -366,7 +366,6 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: ) self._manager.validate_names(type(self)) - self._ports.finalize() if (self.__class__, AbstractBlockProperty) in self._elt_properties: assert isinstance(pb, edgir.HierarchyBlock) @@ -391,11 +390,11 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: assert isinstance(param.binding, ParamBinding) param._populate_decl_proto(edgir.add_pair(pb.params, self._manager.name(param))) - for name, port in self._ports.items(): - port._populate_portlike_proto(edgir.add_pair(pb.ports, name)) + for port in self._ports: + port._populate_portlike_proto(edgir.add_pair(pb.ports, self._manager.name(port))) ref_map = self._create_ref_map() - for name, port in self._ports.items(): + for port in self._ports: if port in self._required_ports: if isinstance(port, Port): connected_condition = port.is_connected() @@ -410,7 +409,10 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: connected_condition = precondition.implies(connected_condition) # add required constraints first - connected_condition._populate_expr_proto(edgir.add_pair(pb.constraints, f"(reqd){name}"), ref_map) + # TODO move to port declaration time + connected_condition._populate_expr_proto( + edgir.add_pair(pb.constraints, f"(reqd){self._manager.name(port)}"), ref_map + ) self._constraints.finalize() # needed for source locator generation @@ -418,8 +420,8 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: self._populate_metadata(pb.meta, self._metadata, ref_map) def _populate_def_proto_port_init(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) -> None: - for name, port in self._ports.items(): - for param, path, initializer in port._get_initializers([name]): + for port in self._ports: + for param, path, initializer in port._get_initializers([self._manager.name(port)]): AssignBinding.populate_assign_proto( edgir.add_pair(pb.constraints, f"(init){'.'.join(path)}"), param, @@ -449,8 +451,8 @@ def _build_ref_map(self, ref_map: Refable.RefMapType, prefix: edgir.LocalPath) - ref_map[self.name()] = edgir.localpath_concat(prefix, edgir.NAME) for param in self._parameters: param._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._manager.name(param))) - for name, port in self._ports.items(): - port._build_ref_map(ref_map, edgir.localpath_concat(prefix, name)) + for port in self._ports: + port._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._manager.name(port))) def _bind_in_place(self, parent: Union[BaseBlock, Port]) -> None: self._parent = parent @@ -539,7 +541,8 @@ def Port(self, tpe: T, *, optional: BoolLike = False, doc: Optional[str] = None) raise EdgTypeError(f"param to Port(...)", tpe, BasePort) elt = tpe._bind(self) - self._ports.register(elt) + self._ports.append(elt) + self._manager.add_expected(elt, SourceLocator.from_caller()) if isinstance(optional, bool): if not optional: diff --git a/edg/core/Core.py b/edg/core/Core.py index 4402035d9..ac24a8f09 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -14,7 +14,7 @@ from .IdentitySet import IdentitySet if TYPE_CHECKING: - from .Block import BaseBlock + from .Blocks import BaseBlock ElementType = TypeVar("ElementType") diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 20c9a8311..545698bda 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -348,7 +348,7 @@ def __init__(self) -> None: def _get_ports_by_tag(self, tags: Set[PortTag]) -> List[BasePort]: out = [] - for block_port_name, block_port in self._ports.items(): + for block_port in self._ports: assert isinstance(block_port, BasePort) port_tags: Set[PortTag] = self._port_tags.get(block_port, set()) if port_tags.issuperset(tags): diff --git a/edg/core/Util.py b/edg/core/Util.py index f76a33162..89d13780f 100644 --- a/edg/core/Util.py +++ b/edg/core/Util.py @@ -22,7 +22,7 @@ def edg_to_dict(obj: Union[BasePort, BaseBlock]) -> Dict[str, Any]: return { "_": edg_obj_name(obj), "params": {obj._manager.name(param): param for param in obj._parameters}, - "ports": {k: edg_to_dict(v) for k, v in obj._ports.items()}, + "ports": {obj._manager.name(port): edg_to_dict(port) for port in obj._ports}, } else: raise ValueError diff --git a/edg/electronics_interfaces/CircuitPackingBlock.py b/edg/electronics_interfaces/CircuitPackingBlock.py index ed52edaaf..2bf990142 100644 --- a/edg/electronics_interfaces/CircuitPackingBlock.py +++ b/edg/electronics_interfaces/CircuitPackingBlock.py @@ -12,7 +12,7 @@ @abstract_block class NetPackingBlock(InternalBlock, Block): def packed(self, elts: BasePort, merged: BasePort) -> None: - self.nets_packed = self.Metadata({"src": self._ports.name_of(elts), "dst": self._ports.name_of(merged)}) + self.nets_packed = self.Metadata({"src": self._manager.name(elts), "dst": self._manager.name(merged)}) class PackedPassive(InternalSubcircuit, GeneratorBlock): @@ -31,9 +31,7 @@ def generate(self) -> None: for request in self.get(self.elts.requested()): self.elts.append_elt(Passive(), request) - self.nets_packed = self.Metadata( - {"src": self._ports.name_of(self.elts), "dst": self._ports.name_of(self.merged)} - ) + self.nets_packed = self.Metadata({"src": self._manager.name(self.elts), "dst": self._manager.name(self.merged)}) class PackedGround(InternalSubcircuit, GeneratorBlock): From f6eaa0ad2f49a5734ba75ac2345e4188db8c4314 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 15:43:13 -0700 Subject: [PATCH 13/52] wip extract base trait --- edg/core/Blocks.py | 13 +++++- edg/core/Core.py | 69 ++++++++++++++++++++++--------- edg/core/DesignTop.py | 2 +- edg/core/HierarchyBlock.py | 2 +- edg/core/Ports.py | 6 +-- edg/core/test_block_portvector.py | 1 + 6 files changed, 67 insertions(+), 26 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 43126c0ff..60dda1981 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -12,7 +12,16 @@ from .Array import BaseVector, Vector from .Binding import AssignBinding, NameBinding from .ConstraintExpr import ConstraintExpr, BoolExpr, ParamBinding, AssignExpr, StringExpr, BoolLike -from .Core import Refable, HasMetadata, builder, SubElementDict, non_library, EltPropertiesBase, SourceLocator +from .Core import ( + Refable, + HasMetadata, + builder, + SubElementDict, + non_library, + EltPropertiesBase, + SourceLocator, + NamingContainer, +) from .HdlUserExceptions import * from .IdentityDict import IdentityDict from .IdentitySet import IdentitySet @@ -268,7 +277,7 @@ def set_elt_proto(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) - @non_library -class BaseBlock(HasMetadata, metaclass=BaseBlockMeta): +class BaseBlock(HasMetadata, NamingContainer, metaclass=BaseBlockMeta): """Base block that has ports (IOs), parameters, and constraints between them.""" # __init__ should initialize the object with structural information (parameters, fields) diff --git a/edg/core/Core.py b/edg/core/Core.py index ac24a8f09..39b305d88 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -191,12 +191,18 @@ def name_of(self, item: Any) -> Optional[str]: else: return name_candidates_filtered[0] - def name(self, item: Any) -> str: + def name(self, item: Any, *, debug: bool = False) -> str: + """Returns the final name of an item""" name = self._base_names.get(item, None) if isinstance(name, str): return name elif isinstance(name, SourceLocator): - raise ValueError(f"no name for {item} at {name} (internal error, should have been caught at validation)") + if debug: + return str(name) + else: + raise ValueError( + f"no name for {item} at {name} (internal error, should have been caught at validation)" + ) else: raise ValueError(f"no name for unexpected {item} (internal consistency error)") @@ -284,8 +290,25 @@ class LibraryElement(Refable): def __repr__(self) -> str: return "%s@%02x" % (self._get_def_name(), (id(self) // 4) & 0xFF) + @classmethod + def _static_def_name(cls) -> str: + """If this library element is defined by class (all instances have an equivalent library definition), + returns the definition name. Otherwise, should crash.""" + return cls.__module__ + "." + cls.__name__ + + def _get_def_name(self) -> str: + """Returns the definition name""" + return self.__class__._static_def_name() + + @abstractmethod + def _def_to_proto(self) -> Union[edgir.PortTypes, edgir.BlockLikeTypes]: ... + + +class NamingContainer: + """Mixin class for objects that contain and name internal objects""" + def __init__(self) -> None: - self._parent: Optional[LibraryElement] = None # set by binding, None means not bound + self._parent: Optional[NamingContainer] = None # set by binding, None means not bound self._manager = NamespaceManager() self._manager_ignored: Set[str] = set(["_parent"]) @@ -298,6 +321,27 @@ def __setattr__(self, name: str, value: Any) -> None: self._manager.add_name(value, name) super().__setattr__(name, value) + def _path_of(self, elt: Any, *, recursive: bool = False, debug: bool = False) -> List[str]: + """Returns the name path of an inner element, optionally recursively (child of child). + If debug is true, will return the best-effort name even if it is not yet assigned. + If debug is false, raises an error for unassigned names.""" + if not recursive: + return [self._manager.name(elt, debug=debug)] + else: + if elt is self: + return [] + else: + assert elt._parent is not None, "can't get path / name for non-bound element" + print(elt._parent) + return self._path_of(elt._parent, recursive=True, debug=debug) + [ + elt._parent._manager.name(elt, debug=debug) + ] + + def _name_of(self, elt: Any, *, recursive: bool = False, debug: bool = False) -> str: + """Convenience stringify wrapper around _path_of.""" + path = self._path_of(elt, recursive=recursive, debug=debug) + return ".".join(path) + def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str: self_name = self._manager.name_of(subelt) if self_name is not None: @@ -308,7 +352,7 @@ def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) else: raise ValueError(f"no name for {subelt}") - def _path_from(self, base: LibraryElement, allow_unknown: bool = False) -> List[str]: + def _path_from(self, base: NamingContainer, allow_unknown: bool = False) -> List[str]: if base is self: return [] else: @@ -317,28 +361,15 @@ def _path_from(self, base: LibraryElement, allow_unknown: bool = False) -> List[ self._parent._name_of_child(self, base, allow_unknown) ] - def _name_from(self, base: LibraryElement, allow_unknown: bool = False) -> str: + def _name_from(self, base: NamingContainer, allow_unknown: bool = False) -> str: """Returns the path name to (inclusive) this element from some starting point. allow_unknown allows elements that haven't been assigned a name yet to not crash, this is useful when called from an error so the _name_from error doesn't stomp the real error.""" return ".".join(self._path_from(base, allow_unknown)) - @classmethod - def _static_def_name(cls) -> str: - """If this library element is defined by class (all instances have an equivalent library definition), - returns the definition name. Otherwise, should crash.""" - return cls.__module__ + "." + cls.__name__ - - def _get_def_name(self) -> str: - """Returns the definition name""" - return self.__class__._static_def_name() - - @abstractmethod - def _def_to_proto(self) -> Union[edgir.PortTypes, edgir.BlockLikeTypes]: ... - @non_library -class HasMetadata(LibraryElement): +class HasMetadata(LibraryElement, NamingContainer): """A library element with the metadata dict-like field""" def __init__(self) -> None: diff --git a/edg/core/DesignTop.py b/edg/core/DesignTop.py index 978a04c2b..a8e4128e8 100644 --- a/edg/core/DesignTop.py +++ b/edg/core/DesignTop.py @@ -128,7 +128,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: packed_port_port = packed_port.port else: raise TypeError - packed_port_name = multipack_part_block._name_of_child(packed_port_port, self) + packed_port_name = multipack_part_block._manager.name(packed_port_port) exported_tunnel = edgir.add_pair( pb.constraints, f"(packed){multipack_name}.{part_name}.{packed_port_name}" ).exportedTunnel diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 545698bda..db2e3e306 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -449,7 +449,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab assert not connect_elts.is_link_array, "bridged arrays not supported" assert self_port.bridge_type is not None - port_name = self_port._name_from(self) + port_name = self._name_of(self_port, recursive=True) edgir.add_pair(pb.blocks, f"(bridge){port_name}").lib_elem.base.target.name = ( self_port.bridge_type._static_def_name() ) diff --git a/edg/core/Ports.py b/edg/core/Ports.py index bd5772767..9cfdb7f01 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -10,7 +10,7 @@ from .Binding import ParamBinding, IsConnectedBinding, NameBinding from .Builder import builder from .ConstraintExpr import ConstraintExpr, BoolExpr, StringExpr -from .Core import Refable, HasMetadata, SubElementDict, non_library +from .Core import Refable, HasMetadata, SubElementDict, non_library, NamingContainer from .HdlUserExceptions import * from .IdentityDict import IdentityDict from .. import edgir @@ -36,7 +36,7 @@ def __call__(cls, *args: Any, **kwargs: Any) -> Any: @non_library -class BasePort(HasMetadata, metaclass=InitializerContextMeta): +class BasePort(HasMetadata, NamingContainer, metaclass=InitializerContextMeta): SelfType = TypeVar("SelfType", bound="BasePort") def __init__(self) -> None: @@ -215,7 +215,7 @@ def _convert(self, adapter: PortAdapter[ConvertTargetType]) -> ConvertTargetType adapter_inst = enclosing_block.Block(adapter) adapter_name_suffix = f"_{self._adapter_count}" if self._adapter_count > 0 else "" enclosing_block._manager.add_element( - f"(adapter){self._name_from(enclosing_block)}{adapter_name_suffix}", adapter_inst + f"(adapter){enclosing_block._name_of(self, recursive=True)}{adapter_name_suffix}", adapter_inst ) enclosing_block.connect(self, adapter_inst.src) # we don't name it to avoid explicit name conflicts self._adapter_count += 1 diff --git a/edg/core/test_block_portvector.py b/edg/core/test_block_portvector.py index a777cfade..a3c0131d4 100644 --- a/edg/core/test_block_portvector.py +++ b/edg/core/test_block_portvector.py @@ -41,6 +41,7 @@ def __init__(self) -> None: class TestBlockPortVectorBridged(TestBlockPortVectorBase): def __init__(self) -> None: super().__init__() + print(self._name_of(self.vector)) vector_port = self.vector.append_elt(TestPortSink()) self.block0 = self.Block(TestBlockSink()) self.block1 = self.Block(TestBlockSink()) From e37573f45d0b36e2f5e876f8686320001f11eb32 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 15:45:13 -0700 Subject: [PATCH 14/52] move code --- edg/core/BlockInterfaceMixin.py | 4 +- edg/core/Core.py | 66 ++++++++++++++++----------------- 2 files changed, 35 insertions(+), 35 deletions(-) diff --git a/edg/core/BlockInterfaceMixin.py b/edg/core/BlockInterfaceMixin.py index 699cfee86..fe6daed77 100644 --- a/edg/core/BlockInterfaceMixin.py +++ b/edg/core/BlockInterfaceMixin.py @@ -1,7 +1,7 @@ from typing import Generic, Type, List, Optional, get_args, get_origin, Tuple, Callable, Any from typing_extensions import TypeVar, override -from .Core import non_library, HasMetadata +from .Core import non_library, HasMetadata, LibraryElement from .Blocks import AbstractBlockProperty from .HdlUserExceptions import BlockDefinitionError from .HierarchyBlock import Block @@ -30,7 +30,7 @@ class BlockInterfaceMixin(Block, Generic[MixinBaseType]): TODO: what about cases where it's a bit mixed, e.g. HasI2s also needs to register the self.i2s port? """ - BaseType = TypeVar("BaseType", bound=HasMetadata) + BaseType = TypeVar("BaseType", bound=LibraryElement) @classmethod @override diff --git a/edg/core/Core.py b/edg/core/Core.py index 39b305d88..837f0c818 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -290,6 +290,39 @@ class LibraryElement(Refable): def __repr__(self) -> str: return "%s@%02x" % (self._get_def_name(), (id(self) // 4) & 0xFF) + BaseType = TypeVar("BaseType", bound="LibraryElement") + + @classmethod + def _get_bases_of(cls, base_type: Type[BaseType]) -> Tuple[List[Type[BaseType]], List[Type[BaseType]]]: + """Returns all the base classes of this class, as a list of direct superclasses (including through non_library + elements) and a list of additional (indirect) superclasses. Direct superclasses are in MRO order, indirect + superclasses order is not defined (but MRO in current practice). + + mypy currently does not allow passing in abstract types, so generally calls to this need type: ignore.""" + direct_bases: Set[Type[LibraryElement.BaseType]] = set() + + def process_direct_base(bcls: Type[LibraryElement.BaseType]) -> None: + if not issubclass(bcls, base_type): + return # ignore above base_type + if (bcls, NonLibraryProperty) in bcls._elt_properties: # non-library, recurse into parents + for bcls_base in bcls.__bases__: + process_direct_base(bcls_base) + else: # anything else, directly append if not existing + direct_bases.add(bcls) + + for bcls in cls.__bases__: + process_direct_base(bcls) + + ordered_direct_bases: List[Type[LibraryElement.BaseType]] = [] + ordered_indirect_bases: List[Type[LibraryElement.BaseType]] = [] + for mro_base in cls.__mro__[1:]: # ignore self + if mro_base in direct_bases: + ordered_direct_bases.append(mro_base) + elif issubclass(mro_base, base_type) and (mro_base, NonLibraryProperty) not in mro_base._elt_properties: + ordered_indirect_bases.append(mro_base) + + return ordered_direct_bases, ordered_indirect_bases + @classmethod def _static_def_name(cls) -> str: """If this library element is defined by class (all instances have an equivalent library definition), @@ -387,39 +420,6 @@ def Metadata(self, value: MetadataType) -> MetadataType: self._metadata.register(value) return value - BaseType = TypeVar("BaseType", bound="HasMetadata") - - @classmethod - def _get_bases_of(cls, base_type: Type[BaseType]) -> Tuple[List[Type[BaseType]], List[Type[BaseType]]]: - """Returns all the base classes of this class, as a list of direct superclasses (including through non_library - elements) and a list of additional (indirect) superclasses. Direct superclasses are in MRO order, indirect - superclasses order is not defined (but MRO in current practice). - - mypy currently does not allow passing in abstract types, so generally calls to this need type: ignore.""" - direct_bases: Set[Type[HasMetadata.BaseType]] = set() - - def process_direct_base(bcls: Type[HasMetadata.BaseType]) -> None: - if not issubclass(bcls, base_type): - return # ignore above base_type - if (bcls, NonLibraryProperty) in bcls._elt_properties: # non-library, recurse into parents - for bcls_base in bcls.__bases__: - process_direct_base(bcls_base) - else: # anything else, directly append if not existing - direct_bases.add(bcls) - - for bcls in cls.__bases__: - process_direct_base(bcls) - - ordered_direct_bases: List[Type[HasMetadata.BaseType]] = [] - ordered_indirect_bases: List[Type[HasMetadata.BaseType]] = [] - for mro_base in cls.__mro__[1:]: # ignore self - if mro_base in direct_bases: - ordered_direct_bases.append(mro_base) - elif issubclass(mro_base, base_type) and (mro_base, NonLibraryProperty) not in mro_base._elt_properties: - ordered_indirect_bases.append(mro_base) - - return ordered_direct_bases, ordered_indirect_bases - def _populate_metadata(self, pb: edgir.Metadata, src: Any, ref_map: Refable.RefMapType) -> None: """Generate metadata from a given object.""" if isinstance(src, str): From 5d1b29fb637476fb13b576cc84fdd2058468965b Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 16:21:26 -0700 Subject: [PATCH 15/52] refactoring --- edg/abstract_parts/IoControllerWrapper.py | 8 ++-- edg/abstract_parts/PinMappable.py | 3 +- edg/core/Blocks.py | 16 ++++---- edg/core/Core.py | 14 ++++--- edg/core/DesignTop.py | 10 ++--- edg/core/HierarchyBlock.py | 6 +-- edg/core/Link.py | 2 +- edg/core/Ports.py | 37 +++++++++---------- edg/core/Util.py | 6 +-- .../CircuitPackingBlock.py | 4 +- edg/electronics_model/PinAssignmentUtil.py | 2 +- 11 files changed, 56 insertions(+), 52 deletions(-) diff --git a/edg/abstract_parts/IoControllerWrapper.py b/edg/abstract_parts/IoControllerWrapper.py index 6a8a453d6..a04af675b 100644 --- a/edg/abstract_parts/IoControllerWrapper.py +++ b/edg/abstract_parts/IoControllerWrapper.py @@ -80,8 +80,8 @@ def recurse_port(port: Port, prefix: str) -> None: assert prefix not in pin_dict, f"duplicate pin name {prefix}" pin_dict[prefix] = port - for subport_name, subport in port._ports.items(): - recurse_port(subport, f"{prefix}.{subport_name}") + for subport in port._ports: + recurse_port(subport, f"{prefix}.{port._name_of(subport)}") for io_port in self._io_ports: if isinstance(io_port, Vector): @@ -203,8 +203,8 @@ def recurse_port(port: Port, prefix: str) -> None: assert prefix not in pin_type_dict, f"duplicate pin name {prefix}" pin_type_dict[prefix] = type(port) - for subport_name, subport in port._ports.items(): - recurse_port(subport, f"{prefix}.{subport_name}") + for subport in port._ports: + recurse_port(subport, f"{prefix}.{self._name_of(subport)}") for io_port in self._io_ports: if isinstance(io_port, Vector): diff --git a/edg/abstract_parts/PinMappable.py b/edg/abstract_parts/PinMappable.py index 2f6f84969..63d1b135c 100644 --- a/edg/abstract_parts/PinMappable.py +++ b/edg/abstract_parts/PinMappable.py @@ -435,7 +435,8 @@ def try_allocate_resource( resource_model = resource.port_model # typer gets confused if this is put directly where it is used inner_models = {} resource_name = resource.name # typer gets confused if this is put directly where it is used - for inner_name, inner_model in resource.port_model._ports.items(): + for inner_model in resource.port_model._ports: + inner_name = resource.port_model._name_of(inner_model) inner_type = type(inner_model) if inner_type in self.transforms: # apply transform to search for the resource type, if needed inner_type = self.transforms[inner_type][0] diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 60dda1981..74b12dd4e 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -361,10 +361,10 @@ def _add_doc_metadata(self) -> None: metadata_dict[""] = inspect.cleandoc(self.__doc__) for param in self._parameters: if param in self._param_docs: - metadata_dict[self._manager.name(param)] = self._param_docs[param] + metadata_dict[self._name_of(param)] = self._param_docs[param] for port in self._ports: if port in self._port_docs: - metadata_dict[self._manager.name(port)] = self._port_docs[port] + metadata_dict[self._name_of(port)] = self._port_docs[port] self._docs = self.Metadata(metadata_dict) def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: @@ -397,10 +397,10 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: for param in self._parameters: assert isinstance(param.binding, ParamBinding) - param._populate_decl_proto(edgir.add_pair(pb.params, self._manager.name(param))) + param._populate_decl_proto(edgir.add_pair(pb.params, self._name_of(param))) for port in self._ports: - port._populate_portlike_proto(edgir.add_pair(pb.ports, self._manager.name(port))) + port._populate_portlike_proto(edgir.add_pair(pb.ports, self._name_of(port))) ref_map = self._create_ref_map() for port in self._ports: @@ -420,7 +420,7 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: # add required constraints first # TODO move to port declaration time connected_condition._populate_expr_proto( - edgir.add_pair(pb.constraints, f"(reqd){self._manager.name(port)}"), ref_map + edgir.add_pair(pb.constraints, f"(reqd){self._name_of(port)}"), ref_map ) self._constraints.finalize() # needed for source locator generation @@ -430,7 +430,7 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: def _populate_def_proto_port_init(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) -> None: for port in self._ports: - for param, path, initializer in port._get_initializers([self._manager.name(port)]): + for param, path, initializer in port._get_initializers([self._name_of(port)]): AssignBinding.populate_assign_proto( edgir.add_pair(pb.constraints, f"(init){'.'.join(path)}"), param, @@ -459,9 +459,9 @@ def _build_ref_map(self, ref_map: Refable.RefMapType, prefix: edgir.LocalPath) - super()._build_ref_map(ref_map, prefix) ref_map[self.name()] = edgir.localpath_concat(prefix, edgir.NAME) for param in self._parameters: - param._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._manager.name(param))) + param._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._name_of(param))) for port in self._ports: - port._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._manager.name(port))) + port._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._name_of(port))) def _bind_in_place(self, parent: Union[BaseBlock, Port]) -> None: self._parent = parent diff --git a/edg/core/Core.py b/edg/core/Core.py index 837f0c818..211fe612a 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -13,9 +13,6 @@ from .IdentityDict import IdentityDict from .IdentitySet import IdentitySet -if TYPE_CHECKING: - from .Blocks import BaseBlock - ElementType = TypeVar("ElementType") @@ -117,7 +114,10 @@ def __init__(self) -> None: self._base_names = IdentityDict[Any, Union[str, SourceLocator]]() - def validate_names(self, container_tpe: Type["BaseBlock"]) -> None: + def __contains__(self, item: Any) -> bool: + return item in self._base_names + + def validate_names(self, container_tpe: Type[Any]) -> None: # asserts that all names are given and valid seen_names: Set[str] = set() missing_names: List[Tuple[Any, SourceLocator]] = [] @@ -359,13 +359,14 @@ def _path_of(self, elt: Any, *, recursive: bool = False, debug: bool = False) -> If debug is true, will return the best-effort name even if it is not yet assigned. If debug is false, raises an error for unassigned names.""" if not recursive: + assert elt in self._manager, f"{self.__class__.__name__} has no named {elt}" return [self._manager.name(elt, debug=debug)] else: if elt is self: return [] else: assert elt._parent is not None, "can't get path / name for non-bound element" - print(elt._parent) + assert elt in elt._parent._manager, f"{elt._parent.__class__.__name__} has no named {elt}" return self._path_of(elt._parent, recursive=True, debug=debug) + [ elt._parent._manager.name(elt, debug=debug) ] @@ -375,6 +376,7 @@ def _name_of(self, elt: Any, *, recursive: bool = False, debug: bool = False) -> path = self._path_of(elt, recursive=recursive, debug=debug) return ".".join(path) + # TODO remove deprecated API def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str: self_name = self._manager.name_of(subelt) if self_name is not None: @@ -385,6 +387,7 @@ def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) else: raise ValueError(f"no name for {subelt}") + # TODO remove deprecated API def _path_from(self, base: NamingContainer, allow_unknown: bool = False) -> List[str]: if base is self: return [] @@ -394,6 +397,7 @@ def _path_from(self, base: NamingContainer, allow_unknown: bool = False) -> List self._parent._name_of_child(self, base, allow_unknown) ] + # TODO remove deprecated API def _name_from(self, base: NamingContainer, allow_unknown: bool = False) -> str: """Returns the path name to (inclusive) this element from some starting point. allow_unknown allows elements that haven't been assigned a name yet to not crash, diff --git a/edg/core/DesignTop.py b/edg/core/DesignTop.py index a8e4128e8..2efb3ded8 100644 --- a/edg/core/DesignTop.py +++ b/edg/core/DesignTop.py @@ -128,7 +128,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: packed_port_port = packed_port.port else: raise TypeError - packed_port_name = multipack_part_block._manager.name(packed_port_port) + packed_port_name = multipack_part_block._name_of(packed_port_port) exported_tunnel = edgir.add_pair( pb.constraints, f"(packed){multipack_name}.{part_name}.{packed_port_name}" ).exportedTunnel @@ -141,14 +141,14 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: for multipack_param, packed_param in packing_rule.tunnel_assigns.items(): if isinstance(packed_param, ConstraintExpr): - packed_param_name = multipack_part_block._manager.name(packed_param) + packed_param_name = multipack_part_block._name_of(packed_param) assign_tunnel = edgir.add_pair( pb.constraints, f"(packed){multipack_name}.{part_name}.{packed_param_name}" ).assignTunnel assign_tunnel.dst.CopyFrom(multipack_ref_map[multipack_param]) assign_tunnel.src.ref.CopyFrom(packed_ref_map[packed_param]) elif isinstance(packed_param, PackedBlockParamArray): - multipack_param_name = multipack_block._manager.name(multipack_param) + multipack_param_name = multipack_block._name_of(multipack_param) constr_name = f"(packed){multipack_name}.{multipack_param_name}" packed_params.setdefault(constr_name, (multipack_ref_map[multipack_param], []))[1].append( packed_ref_map[packed_param.param] @@ -158,7 +158,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: for multipack_param, unpacked_param in packing_rule.tunnel_unpack_assigns.items(): if isinstance(unpacked_param, ConstraintExpr): - multipack_param_name = multipack_block._manager.name(multipack_param) + multipack_param_name = multipack_block._name_of(multipack_param) # TODO need better constraint naming scheme assign_tunnel = edgir.add_pair( pb.constraints, f"(unpacked){multipack_name}.{part_name}.{multipack_param_name}" @@ -166,7 +166,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: assign_tunnel.dst.CopyFrom(packed_ref_map[unpacked_param]) assign_tunnel.src.ref.CopyFrom(multipack_ref_map[multipack_param]) elif isinstance(unpacked_param, PackedBlockParam): - multipack_param_name = multipack_block._manager.name(multipack_param) + multipack_param_name = multipack_block._name_of(multipack_param) # TODO need better constraint naming scheme assign_tunnel = edgir.add_pair( pb.constraints, f"(unpacked){multipack_name}.{part_name}.{multipack_param_name}" diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index db2e3e306..16a181577 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -377,7 +377,7 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: if isinstance(param.binding, InitParamBinding) and param.binding.value is not None: # default values can't depend on anything so the ref_map is empty param_typed_value = param._to_expr_type(param.binding.value) - param_typed_value._populate_expr_proto(pb.param_defaults[self._manager.name(param)], IdentityDict()) + param_typed_value._populate_expr_proto(pb.param_defaults[self._name_of(param)], IdentityDict()) def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refable.RefMapType) -> None: self._blocks.finalize() @@ -486,9 +486,9 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab # generate block initializers for block_name, block in self._blocks.items(): - all_block_params = {block._manager.name(param): param for param in block._parameters} + all_block_params = {block._name_of(param): param for param in block._parameters} for mixin in block._mixins: - all_block_params.update({mixin._manager.name(param): param for param in mixin._parameters}) + all_block_params.update({mixin._name_of(param): param for param in mixin._parameters}) for block_param_name, block_param in all_block_params.items(): if isinstance(block_param.binding, InitParamBinding) and block_param.binding.value is not None: param_typed_value = block_param._to_expr_type(block_param.binding.value) diff --git a/edg/core/Link.py b/edg/core/Link.py index 03c369439..23134e208 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -79,7 +79,7 @@ def _def_to_proto(self) -> edgir.Link: ref_map[self_port.base] ) constraint_pb.exportedArray.exterior_port.map_extract.path.steps.add().name = ( - self_port.target._name_from(self_port.base._get_elt_sample()) + self_port.base._get_elt_sample()._name_of(self_port.target) ) constraint_pb.exportedArray.internal_block_port.ref.CopyFrom( edgir.localpath_concat(link_path, link_port_path) diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 9cfdb7f01..7543f4b91 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -10,7 +10,7 @@ from .Binding import ParamBinding, IsConnectedBinding, NameBinding from .Builder import builder from .ConstraintExpr import ConstraintExpr, BoolExpr, StringExpr -from .Core import Refable, HasMetadata, SubElementDict, non_library, NamingContainer +from .Core import Refable, HasMetadata, SubElementDict, non_library, NamingContainer, SourceLocator from .HdlUserExceptions import * from .IdentityDict import IdentityDict from .. import edgir @@ -150,7 +150,7 @@ def __init__(self) -> None: self._adapter_count: int = 0 self._parameters: SubElementDict[ConstraintExpr] = self._manager.new_dict(ConstraintExpr) - self._ports: SubElementDict[Port] = self._manager.new_dict(Port) + self._ports: List[Port] = [] self._manager_ignored.update(["_is_connected", "_name"]) self._is_connected = BoolExpr()._bind(IsConnectedBinding(self)) @@ -160,8 +160,7 @@ def _clear_initializers(self) -> None: self._parameters.finalize() for name, param in self._parameters.items(): param.initializer = None - self._ports.finalize() - for name, port in self._ports.items(): + for port in self._ports: port._clear_initializers() @override @@ -172,11 +171,10 @@ def _cloned_from(self: SelfType, other: SelfType) -> None: other_param = other._parameters[name] assert isinstance(other_param, type(param)) param.initializer = other_param.initializer - self._ports.finalize() - for name, port in self._ports.items(): - other_port = other._ports[name] - assert isinstance(other_port, type(port)) - port._cloned_from(other_port) + assert len(other._ports) == len(self._ports) + for self_port, other_port in zip(self._ports, other._ports): + assert type(other_port) is type(self_port) + other_port._cloned_from(self_port) def init_from(self: SelfType, other: SelfType) -> None: assert self._parent is not None, "may only init_from on an bound port" @@ -212,6 +210,7 @@ def _convert(self, adapter: PortAdapter[ConvertTargetType]) -> ConvertTargetType if (block_parent is not enclosing_block) and (block_parent._parent is not enclosing_block): raise UnconnectableError(f"can only create adapters on own ports or subblock ports") + # TODO proper programmatic naming API adapter_inst = enclosing_block.Block(adapter) adapter_name_suffix = f"_{self._adapter_count}" if self._adapter_count > 0 else "" enclosing_block._manager.add_element( @@ -227,8 +226,8 @@ def _populate_portlike_proto(self, pb: edgir.PortLike) -> None: @override def _def_to_proto(self) -> edgir.Port: + self._manager.validate_names(type(self)) self._parameters.finalize() - self._ports.finalize() pb = edgir.Port() @@ -242,8 +241,8 @@ def _def_to_proto(self) -> edgir.Port: for name, param in self._parameters.items(): param._populate_decl_proto(edgir.add_pair(pb.params, name)) - for name, port in self._ports.items(): - port._populate_portlike_proto(edgir.add_pair(pb.ports, name)) + for port in self._ports: + port._populate_portlike_proto(edgir.add_pair(pb.ports, self._name_of(port))) self._populate_metadata(pb.meta, self._metadata, IdentityDict()) # TODO use ref map @@ -260,8 +259,8 @@ def _build_ref_map(self, ref_map: Refable.RefMapType, prefix: edgir.LocalPath) - ref_map[self.name()] = edgir.localpath_concat(prefix, edgir.NAME) for name, param in self._parameters.items(): param._build_ref_map(ref_map, edgir.localpath_concat(prefix, name)) - for name, port in self._ports.items(): - port._build_ref_map(ref_map, edgir.localpath_concat(prefix, name)) + for port in self._ports: + port._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._name_of(port))) if self._link_instance is not None: self._link_instance._build_ref_map(ref_map, edgir.localpath_concat(prefix, edgir.CONNECTED_LINK)) @@ -273,9 +272,8 @@ def _get_initializers(self, path_prefix: List[str]) -> List[Tuple[ConstraintExpr for (name, param) in self._parameters.items() if param.initializer is not None ] - self._ports.finalize() - for name, port in self._ports.items(): - initializers.extend(port._get_initializers(path_prefix + [name])) + for port in self._ports: + initializers.extend(port._get_initializers(path_prefix + [self._name_of(port)])) return initializers def _with_elt_initializers(self: SelfType, replace_elts: dict[str, "Port"]) -> SelfType: @@ -285,7 +283,7 @@ def _with_elt_initializers(self: SelfType, replace_elts: dict[str, "Port"]) -> S cloned = self._clone() for name, replace_port in replace_elts.items(): assert replace_port._parent is None, "replace_elts must not be bound" - cloned_port = cloned._ports[name] + cloned_port = getattr(cloned, name) assert isinstance(replace_port, type(cloned_port)) assert not cloned_port._get_initializers([]), f"replace_elts sub-port {name} was not empty" cloned_port._cloned_from(replace_port) @@ -324,7 +322,8 @@ def Port(self, tpe: T, *, desc: Optional[str] = None) -> T: raise EdgTypeError(f"param to Port(...)", tpe, Port) elt = tpe._bind(self) - self._ports.register(elt) + self._ports.append(elt) + self._manager.add_expected(elt, SourceLocator.from_caller()) return elt diff --git a/edg/core/Util.py b/edg/core/Util.py index 89d13780f..66a447576 100644 --- a/edg/core/Util.py +++ b/edg/core/Util.py @@ -13,7 +13,7 @@ def edg_to_dict(obj: Union[BasePort, BaseBlock]) -> Dict[str, Any]: result = { "_": edg_obj_name(obj), "params": obj._parameters, - "fields": {k: edg_to_dict(v) for k, v in obj._ports.items()}, + "fields": {obj._name_of(port): edg_to_dict(port) for port in obj._ports}, } if obj._link_instance is not None: result["link"] = edg_to_dict(obj._link_instance) @@ -21,8 +21,8 @@ def edg_to_dict(obj: Union[BasePort, BaseBlock]) -> Dict[str, Any]: elif isinstance(obj, Block) or isinstance(obj, Link): return { "_": edg_obj_name(obj), - "params": {obj._manager.name(param): param for param in obj._parameters}, - "ports": {obj._manager.name(port): edg_to_dict(port) for port in obj._ports}, + "params": {obj._name_of(param): param for param in obj._parameters}, + "ports": {obj._name_of(port): edg_to_dict(port) for port in obj._ports}, } else: raise ValueError diff --git a/edg/electronics_interfaces/CircuitPackingBlock.py b/edg/electronics_interfaces/CircuitPackingBlock.py index 2bf990142..1ff081716 100644 --- a/edg/electronics_interfaces/CircuitPackingBlock.py +++ b/edg/electronics_interfaces/CircuitPackingBlock.py @@ -12,7 +12,7 @@ @abstract_block class NetPackingBlock(InternalBlock, Block): def packed(self, elts: BasePort, merged: BasePort) -> None: - self.nets_packed = self.Metadata({"src": self._manager.name(elts), "dst": self._manager.name(merged)}) + self.nets_packed = self.Metadata({"src": self._name_of(elts), "dst": self._name_of(merged)}) class PackedPassive(InternalSubcircuit, GeneratorBlock): @@ -31,7 +31,7 @@ def generate(self) -> None: for request in self.get(self.elts.requested()): self.elts.append_elt(Passive(), request) - self.nets_packed = self.Metadata({"src": self._manager.name(self.elts), "dst": self._manager.name(self.merged)}) + self.nets_packed = self.Metadata({"src": self._name_of(self.elts), "dst": self._name_of(self.merged)}) class PackedGround(InternalSubcircuit, GeneratorBlock): diff --git a/edg/electronics_model/PinAssignmentUtil.py b/edg/electronics_model/PinAssignmentUtil.py index 1f02b3506..3c1bff516 100644 --- a/edg/electronics_model/PinAssignmentUtil.py +++ b/edg/electronics_model/PinAssignmentUtil.py @@ -16,7 +16,7 @@ def leaf_circuit_ports(prefix: str, port: Port) -> Iterable[Tuple[str, Passive]] return [(prefix, port)] # type: ignore else: assert port._ports, "must be Passive or Port bundle with sub-Ports" - return chain(*[leaf_circuit_ports(f"{prefix}.{name}", port) for (name, port) in port._ports.items()]) + return chain(*[leaf_circuit_ports(f"{prefix}.{port._name_of(subport)}", subport) for subport in port._ports]) class SpecialPin: From e0c2a12e79c6c7252026ef0320037bc7d8586185 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 16:24:33 -0700 Subject: [PATCH 16/52] Update HierarchyBlock.py --- edg/core/HierarchyBlock.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 16a181577..9affdcd68 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -414,13 +414,13 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab if connect in link_chain_names and not link_chain_names[connect][0].startswith("anon_"): name = link_chain_names[connect][0] # arbitrarily pick the first chain name elif isinstance(connect_elts, Connection.Export): - port_pathname = connect_elts.external_port._name_from(self).replace(".", "_") + port_pathname = self._name_of(connect_elts.external_port, recursive=True).replace(".", "_") name = f"_{port_pathname}_link" elif isinstance(connect_elts, Connection.ConnectedLink) and connect_elts.bridged_connects: - port_pathname = connect_elts.bridged_connects[0][0]._name_from(self).replace(".", "_") + port_pathname = self._name_of(connect_elts.bridged_connects[0][0], recursive=True).replace(".", "_") name = f"_{port_pathname}_link" elif isinstance(connect_elts, Connection.ConnectedLink) and connect_elts.link_connects: - port_pathname = connect_elts.link_connects[0][0]._name_from(self).replace(".", "_") + port_pathname = self._name_of(connect_elts.link_connects[0][0], recursive=True).replace(".", "_") name = f"_{port_pathname}_link" elif connect in link_chain_names: # if there's really nothing better, anon_chain is better than nothing name = link_chain_names[connect][0] From 863d52f2306a1f7be5cf70c2c1ed5d518d42e624 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 16:34:29 -0700 Subject: [PATCH 17/52] refactor blocks --- edg/core/DesignTop.py | 8 ++++---- edg/core/HierarchyBlock.py | 19 +++++++++---------- edg/electronics_model/SubboardBlock.py | 2 +- 3 files changed, 14 insertions(+), 15 deletions(-) diff --git a/edg/core/DesignTop.py b/edg/core/DesignTop.py index 2efb3ded8..fdb3b22f5 100644 --- a/edg/core/DesignTop.py +++ b/edg/core/DesignTop.py @@ -101,7 +101,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: else: raise TypeError assert isinstance(multipack_block, MultipackBlock) - multipack_name = self._name_of_child(multipack_block, self) + multipack_name = self._name_of(multipack_block) multipack_ref_base = edgir.LocalPath() multipack_ref_base.steps.add().name = multipack_name multipack_ref_map = multipack_block._create_ref_map(multipack_ref_base) @@ -113,9 +113,9 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: packed_ref_map = multipack_part_block._create_ref_map(packed_ref_base) if isinstance(multipack_part, Block): - part_name = multipack_block._name_of_child(multipack_part, self) + part_name = multipack_block._name_of(multipack_part) elif isinstance(multipack_part, PackedBlockAllocate): - part_name = multipack_block._name_of_child(multipack_part.parent, self) + part_name = multipack_block._name_of(multipack_part.parent) assert multipack_part.suggested_name, "multipack parts must have suggested name, for consistency" part_name += f"[{multipack_part.suggested_name}]" else: @@ -206,5 +206,5 @@ def pack(self, multipack_part: Union[Block, PackedBlockAllocate], path: DesignPa else: raise TypeError assert isinstance(multipack_block, MultipackBlock), "block must be a part of a MultipackBlock" - assert self._blocks.name_of(multipack_block), "containing MultipackBlock must be a PackedBlock" + assert multipack_block._parent is self, "block must be in self" self._packed_blocks[multipack_part] = path diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 9affdcd68..2d7daf966 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -342,7 +342,7 @@ def __init__(self) -> None: self._mixins: List["BlockInterfaceMixin"] = [] - self._blocks = self._manager.new_dict(Block) + self._blocks: List[Block] = [] self._chains = self._manager.new_dict(ChainConnect, anon_prefix="anon_chain") self._port_tags = IdentityDict[BasePort, Set[PortTag]]() @@ -363,9 +363,8 @@ def _build_ref_map( for mixin in self._mixins: mixin._build_ref_map(ref_map, prefix) if not interface_only: - for name, block in self._blocks.items(): - assert isinstance(block, Block) - block._build_ref_map(ref_map, edgir.localpath_concat(prefix, name), interface_only=True) + for block in self._blocks: + block._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._name_of(block)), interface_only=True) @override def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: @@ -380,12 +379,11 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: param_typed_value._populate_expr_proto(pb.param_defaults[self._name_of(param)], IdentityDict()) def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refable.RefMapType) -> None: - self._blocks.finalize() self._connects.finalize() self._chains.finalize() - for name, block in self._blocks.items(): - new_block_lib = edgir.add_pair(pb.blocks, name).lib_elem + for block in self._blocks: + new_block_lib = edgir.add_pair(pb.blocks, self._name_of(block)).lib_elem new_block_lib.base.target.name = block._get_def_name() for mixin in block._mixins: new_block_lib.mixins.add().target.name = mixin._get_def_name() @@ -485,7 +483,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab raise ValueError("unknown connect type") # generate block initializers - for block_name, block in self._blocks.items(): + for block in self._blocks: all_block_params = {block._name_of(param): param for param in block._parameters} for mixin in block._mixins: all_block_params.update({mixin._name_of(param): param for param in mixin._parameters}) @@ -493,7 +491,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab if isinstance(block_param.binding, InitParamBinding) and block_param.binding.value is not None: param_typed_value = block_param._to_expr_type(block_param.binding.value) AssignBinding.populate_assign_proto( - edgir.add_pair(pb.constraints, f"(init){block_name}.{block_param_name}"), + edgir.add_pair(pb.constraints, f"(init){self._name_of(block)}.{block_param_name}"), block_param, param_typed_value, ref_map, @@ -743,7 +741,8 @@ def Block(self, tpe: BlockType) -> BlockType: raise TypeError(f"param to Block(...) may not be DesignTop") elt = tpe._bind(self) - self._blocks.register(elt) + self._blocks.append(elt) + self._manager.add_expected(elt, SourceLocator.from_caller()) return elt diff --git a/edg/electronics_model/SubboardBlock.py b/edg/electronics_model/SubboardBlock.py index 3c9ffb94e..da2cec560 100644 --- a/edg/electronics_model/SubboardBlock.py +++ b/edg/electronics_model/SubboardBlock.py @@ -49,7 +49,7 @@ def export_tap(self, exterior_port: BasePort, internal_port: BasePort) -> None: @override def _def_to_proto(self) -> edgir.HierarchyBlock: # create this assign after the block definition has run - self.assign(self.fp_external_blocks, [self._blocks.name_of(block) for block in self._external_blocks]) + self.assign(self.fp_external_blocks, [self._name_of(block) for block in self._external_blocks]) return super()._def_to_proto() @override From b75ff398c658f9c0aa8e091afe51dc05f75c20d1 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 16:37:05 -0700 Subject: [PATCH 18/52] fix multipack manager --- edg/core/MultipackBlock.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index 354080769..664e6857d 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -132,7 +132,7 @@ class MultipackBlock(Block): def __init__(self) -> None: super().__init__() - self._packed_blocks: SubElementDict[PackedBlockTypes] = self._manager.new_dict((Block, PackedBlockArray)) + # TODO should these be defined in terms of Refs? # packed block -> (exterior port -> packed block port) self._packed_connects_by_packed_block = IdentityDict[ @@ -163,7 +163,7 @@ def PackedPart(self, tpe: PackedPartType) -> PackedPartType: raise BlockDefinitionError(type(self), "can only define multipack in init") elt = tpe._bind(self) # TODO: does this actually need to be bound? - self._packed_blocks.register(elt) + self._manager.add_expected(elt, SourceLocator.from_caller()) self._packed_connects_by_packed_block[elt] = IdentityDict[BasePort, PackedPortTypes]() self._packed_assigns_by_packed_block[elt] = IdentityDict[ConstraintExpr, PackedParamTypes]() self._unpacked_assigns_by_packed_block[elt] = IdentityDict[ConstraintExpr, UnpackedParamTypes]() @@ -275,7 +275,6 @@ def unpacked_assign(self, packed_param: UnpackedParamTypes, self_param: Constrai def _get_block_packing_rule(self, packed_part: Union[Block, PackedBlockAllocate]) -> MultipackPackingRule: """Internal API, returns the packing rules (tunnel exports and assigns) for a constituent PackedPart.""" - self._packed_blocks.finalize() if isinstance(packed_part, PackedBlockAllocate): packed_block: PackedBlockTypes = packed_part.parent else: From f4c5348fb8c3437f7d01b9585de4d15688a89aaf Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 16:40:49 -0700 Subject: [PATCH 19/52] update elementdict --- edg/core/Core.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index 211fe612a..d85e41b94 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -151,7 +151,7 @@ def add_alias(self, src: Any, target: Any) -> None: # TODO remove old API def add_element(self, name: str, item: Any) -> None: if isinstance(item, ElementDict): - item._set_parent((name, self)) + pass # moved to add_name else: assigned = [] for tpe, dict in self.dicts: @@ -165,11 +165,13 @@ def add_element(self, name: str, item: Any) -> None: def add_expected(self, item: Any, source_locator: SourceLocator) -> None: self._base_names[item] = source_locator - # TODO remove old API def add_name(self, item: Any, name: str) -> None: existing = self._base_names.get(item, None) if existing is None: - return # not expected, ignore + if isinstance(item, ElementDict): + item._set_parent((name, self)) # TODO re-examine the architecture + else: + return # not expected, ignore elif existing == name: return # already assigned, ignore elif isinstance(existing, str): @@ -220,8 +222,8 @@ def _set_parent(self, parent: Tuple[str, NamespaceManager]) -> None: def __setitem__(self, key: Union[str, int], value: ElementType) -> None: assert self._parent is not None - self._parent[1].add_element( - f"{self._parent[0]}[{key}]", value + self._parent[1].add_name( + value, f"{self._parent[0]}[{key}]" ) # TODO perhaps some kind of check to make sure this is required? self.container[key] = value From f54fcfc8081f95db92c8e2d0aec62fb6ad1b65e3 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 16:44:39 -0700 Subject: [PATCH 20/52] Update test_block_portvector.py --- edg/core/test_block_portvector.py | 1 - 1 file changed, 1 deletion(-) diff --git a/edg/core/test_block_portvector.py b/edg/core/test_block_portvector.py index a3c0131d4..a777cfade 100644 --- a/edg/core/test_block_portvector.py +++ b/edg/core/test_block_portvector.py @@ -41,7 +41,6 @@ def __init__(self) -> None: class TestBlockPortVectorBridged(TestBlockPortVectorBase): def __init__(self) -> None: super().__init__() - print(self._name_of(self.vector)) vector_port = self.vector.append_elt(TestPortSink()) self.block0 = self.Block(TestBlockSink()) self.block1 = self.Block(TestBlockSink()) From df1fd0388ee7039be329c51ddcda5f63e8d14ef4 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 16:54:22 -0700 Subject: [PATCH 21/52] split single and path case --- edg/core/Core.py | 30 ++++++++++++++---------------- edg/core/HierarchyBlock.py | 8 ++++---- edg/core/Ports.py | 2 +- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index d85e41b94..63e123339 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -356,27 +356,25 @@ def __setattr__(self, name: str, value: Any) -> None: self._manager.add_name(value, name) super().__setattr__(name, value) - def _path_of(self, elt: Any, *, recursive: bool = False, debug: bool = False) -> List[str]: - """Returns the name path of an inner element, optionally recursively (child of child). + def _path_of(self, elt: Any, *, debug: bool = False) -> List[str]: + """Returns the name path of an inner element. If debug is true, will return the best-effort name even if it is not yet assigned. If debug is false, raises an error for unassigned names.""" - if not recursive: - assert elt in self._manager, f"{self.__class__.__name__} has no named {elt}" - return [self._manager.name(elt, debug=debug)] + if elt is self: + return [] else: - if elt is self: - return [] - else: - assert elt._parent is not None, "can't get path / name for non-bound element" - assert elt in elt._parent._manager, f"{elt._parent.__class__.__name__} has no named {elt}" - return self._path_of(elt._parent, recursive=True, debug=debug) + [ - elt._parent._manager.name(elt, debug=debug) - ] + assert elt._parent is not None, "can't get path / name for non-bound element" + assert elt in elt._parent._manager, f"{elt._parent.__class__.__name__} has no named {elt}" + return self._path_of(elt._parent, debug=debug) + [elt._parent._name_of(elt, debug=debug)] - def _name_of(self, elt: Any, *, recursive: bool = False, debug: bool = False) -> str: + def _path_name_of(self, elt: Any, *, debug: bool = False) -> str: """Convenience stringify wrapper around _path_of.""" - path = self._path_of(elt, recursive=recursive, debug=debug) - return ".".join(path) + return ".".join(self._path_of(elt, debug=debug)) + + def _name_of(self, elt: Any, *, debug: bool = False) -> str: + """Returns the name of a contained element, non-recursively.""" + assert elt in self._manager, f"{self.__class__.__name__} has no named {elt}" + return self._manager.name(elt, debug=debug) # TODO remove deprecated API def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str: diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 2d7daf966..92115ee16 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -412,13 +412,13 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab if connect in link_chain_names and not link_chain_names[connect][0].startswith("anon_"): name = link_chain_names[connect][0] # arbitrarily pick the first chain name elif isinstance(connect_elts, Connection.Export): - port_pathname = self._name_of(connect_elts.external_port, recursive=True).replace(".", "_") + port_pathname = "_".join(self._path_name_of(connect_elts.external_port)) name = f"_{port_pathname}_link" elif isinstance(connect_elts, Connection.ConnectedLink) and connect_elts.bridged_connects: - port_pathname = self._name_of(connect_elts.bridged_connects[0][0], recursive=True).replace(".", "_") + port_pathname = "_".join(self._path_of(connect_elts.bridged_connects[0][0])) name = f"_{port_pathname}_link" elif isinstance(connect_elts, Connection.ConnectedLink) and connect_elts.link_connects: - port_pathname = self._name_of(connect_elts.link_connects[0][0], recursive=True).replace(".", "_") + port_pathname = "_".join(self._path_of(connect_elts.link_connects[0][0])) name = f"_{port_pathname}_link" elif connect in link_chain_names: # if there's really nothing better, anon_chain is better than nothing name = link_chain_names[connect][0] @@ -447,7 +447,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab assert not connect_elts.is_link_array, "bridged arrays not supported" assert self_port.bridge_type is not None - port_name = self._name_of(self_port, recursive=True) + port_name = self._path_name_of(self_port) edgir.add_pair(pb.blocks, f"(bridge){port_name}").lib_elem.base.target.name = ( self_port.bridge_type._static_def_name() ) diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 7543f4b91..92e6daa29 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -214,7 +214,7 @@ def _convert(self, adapter: PortAdapter[ConvertTargetType]) -> ConvertTargetType adapter_inst = enclosing_block.Block(adapter) adapter_name_suffix = f"_{self._adapter_count}" if self._adapter_count > 0 else "" enclosing_block._manager.add_element( - f"(adapter){enclosing_block._name_of(self, recursive=True)}{adapter_name_suffix}", adapter_inst + f"(adapter){enclosing_block._path_name_of(self)}{adapter_name_suffix}", adapter_inst ) enclosing_block.connect(self, adapter_inst.src) # we don't name it to avoid explicit name conflicts self._adapter_count += 1 From f3d4af3d5579c9d064c2fc8d93a16171ea841bda Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:02:37 -0700 Subject: [PATCH 22/52] cleaning --- edg/core/Array.py | 45 ++++++++++++++++++--------------------------- edg/core/Core.py | 1 - 2 files changed, 18 insertions(+), 28 deletions(-) diff --git a/edg/core/Array.py b/edg/core/Array.py index bd5824db2..11d5e67d1 100644 --- a/edg/core/Array.py +++ b/edg/core/Array.py @@ -155,36 +155,20 @@ def items(self) -> ItemsView[str, VectorType]: # unlike most other LibraryElement types, the names are stored in _elts and _allocates @override - def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str: - from .HierarchyBlock import Block - - block_parent = self._block_parent() - assert isinstance(block_parent, Block) - - if context is block_parent: - # in block defining this port (direct elt definition), or in test top - assert self._elts is not None, "can't get name on undefined vector" + def _name_of(self, subelt: Any, *, debug: bool = False) -> str: + if self._elts is not None: for name, elt in self._elts.items(): if subelt is elt: return name - if allow_unknown: - return f"(unknown {subelt.__class__.__name__})" - else: - raise ValueError(f"no name for {subelt}") - elif context is block_parent._parent: - # in block enclosing the block defining this port (allocate required) - for i, (suggested_name, allocate_elt) in enumerate(self._requests): - if subelt is allocate_elt: - if suggested_name is not None: - return suggested_name - else: - return f"_allocate_{i}" - if allow_unknown: - return f"(unknown {subelt.__class__.__name__})" - else: - raise ValueError(f"allocated elt not found {subelt}") - else: - raise ValueError(f"unknown context of array") + + for i, (suggested_name, allocate_elt) in enumerate(self._requests): + if subelt is allocate_elt: + if suggested_name is not None: + return suggested_name + else: + return f"_allocate_{i}" + + raise ValueError(f"{subelt} not in Vector") @override def _get_elt_sample(self) -> BasePort: @@ -338,14 +322,19 @@ def validate_selector(expected: Type[SelectorType], result: ConstraintExpr) -> S # See the note in ArrayExpr for why this is expanded. @overload def map_extract(self, selector: Callable[[VectorType], BoolExpr]) -> ArrayBoolExpr: ... + @overload def map_extract(self, selector: Callable[[VectorType], IntExpr]) -> ArrayIntExpr: ... + @overload def map_extract(self, selector: Callable[[VectorType], FloatExpr]) -> ArrayFloatExpr: ... + @overload def map_extract(self, selector: Callable[[VectorType], RangeExpr]) -> ArrayRangeExpr: ... + @overload def map_extract(self, selector: Callable[[VectorType], StringExpr]) -> ArrayStringExpr: ... + @overload def map_extract(self, selector: Callable[[VectorType], ExtractPortType]) -> DerivedVector[ExtractPortType]: ... @@ -377,8 +366,10 @@ def count(self, selector: Callable[[VectorType], BoolExpr]) -> IntExpr: @overload def sum(self, selector: Callable[[VectorType], RangeExpr]) -> RangeExpr: ... + @overload def sum(self, selector: Callable[[VectorType], FloatExpr]) -> FloatExpr: ... + def sum(self, selector: Callable[[VectorType], Union[RangeExpr, FloatExpr]]) -> Union[RangeExpr, FloatExpr]: param = selector(self._elt_sample) if isinstance(param, FloatExpr): diff --git a/edg/core/Core.py b/edg/core/Core.py index 63e123339..c83cde162 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -364,7 +364,6 @@ def _path_of(self, elt: Any, *, debug: bool = False) -> List[str]: return [] else: assert elt._parent is not None, "can't get path / name for non-bound element" - assert elt in elt._parent._manager, f"{elt._parent.__class__.__name__} has no named {elt}" return self._path_of(elt._parent, debug=debug) + [elt._parent._name_of(elt, debug=debug)] def _path_name_of(self, elt: Any, *, debug: bool = False) -> str: From 43b5938125478b638b70c56dc8bbbe076cb7560c Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:04:02 -0700 Subject: [PATCH 23/52] Update Ports.py --- edg/core/Ports.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 92e6daa29..ec9f44a36 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -213,8 +213,8 @@ def _convert(self, adapter: PortAdapter[ConvertTargetType]) -> ConvertTargetType # TODO proper programmatic naming API adapter_inst = enclosing_block.Block(adapter) adapter_name_suffix = f"_{self._adapter_count}" if self._adapter_count > 0 else "" - enclosing_block._manager.add_element( - f"(adapter){enclosing_block._path_name_of(self)}{adapter_name_suffix}", adapter_inst + enclosing_block._manager.add_name( + adapter_inst, f"(adapter){enclosing_block._path_name_of(self)}{adapter_name_suffix}" ) enclosing_block.connect(self, adapter_inst.src) # we don't name it to avoid explicit name conflicts self._adapter_count += 1 From 10b0842126ebc2f924d7bd5e4c2cab22d6c68107 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:06:25 -0700 Subject: [PATCH 24/52] Update CircuitBlock.py --- edg/electronics_model/CircuitBlock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/electronics_model/CircuitBlock.py b/edg/electronics_model/CircuitBlock.py index 8c9847f78..fa40b7c21 100644 --- a/edg/electronics_model/CircuitBlock.py +++ b/edg/electronics_model/CircuitBlock.py @@ -117,7 +117,7 @@ def footprint( else: pin_tuples = tuple(iter(pin_name)) for pin in pin_tuples: - pinning_array.append(f"{pin}={pin_port._name_from(self)}") + pinning_array.append(f"{pin}={self._path_name_of(pin_port)}") assert pin not in assigned_pins, f"duplicate pin name {pin} in footprint pinning" assigned_pins.add(pin) self.assign(self.fp_pinning, pinning_array) From 34e034722e26197abcc22028ed8098d500376dd6 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:13:02 -0700 Subject: [PATCH 25/52] Update Blocks.py --- edg/core/Blocks.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 74b12dd4e..ad26075f5 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -145,23 +145,23 @@ def add_ports(self, ports: Iterable[BasePort]) -> None: if port._block_parent() is self.parent: if port._get_initializers([]): raise UnconnectableError( - f"Connected boundary port {port._name_from(self.parent, allow_unknown=True)} may not have initializers" + f"Connected boundary port {self.parent._path_name_of(port, debug=True)} may not have initializers" ) if not isinstance(port, Port): raise UnconnectableError( - f"Can't generate bridge for non-Port {port._name_from(self.parent, allow_unknown=True)}" + f"Can't generate bridge for non-Port {self.parent._path_name_of(port, debug=True)}" ) bridge = port._bridge() if bridge is None: - raise UnconnectableError(f"No bridge for {port._name_from(self.parent, allow_unknown=True)}") + raise UnconnectableError(f"No bridge for {self.parent._path_name_of(port, debug=True)}") link_facing_port = self.bridged_ports[port] = bridge.inner_link else: link_facing_port = port elif isinstance(self.parent, Link): # links don't bridge, all ports are treated as internal if port._block_parent() is not self.parent: raise UnconnectableError( - f"Port {port._name_from(self.parent, allow_unknown=True)} not in containing link" + f"Port {self.parent._path_name_of(port, debug=True)} not in containing link" ) link_facing_port = port else: @@ -174,18 +174,16 @@ def add_ports(self, ports: Iterable[BasePort]) -> None: # allocate the connection if self._baseport_leaf_type(link_facing_port).link_type is not type(link): raise UnconnectableError( - f"Can't connect {port._name_from(self.parent, allow_unknown=True)} to link of type {type(link)}" + f"Can't connect {self.parent._path_name_of(port, debug=True)} to link of type {type(link)}" ) port_type = type(self._baseport_leaf_type(link_facing_port)) allocatable_link_ports = self.available_link_ports_by_type.get(port_type, None) if allocatable_link_ports is None: raise UnconnectableError( - f"No link port for {port._name_from(self.parent, allow_unknown=True)} of type {port_type}" + f"No link port for {self.parent._path_name_of(port, debug=True)} of type {port_type}" ) if not allocatable_link_ports: - raise UnconnectableError( - f"No remaining link ports to {port._name_from(self.parent, allow_unknown=True)}" - ) + raise UnconnectableError(f"No remaining link ports to {self.parent._path_name_of(port, debug=True)}") allocated_link_port = allocatable_link_ports[0] if isinstance(allocated_link_port, BaseVector): # array on link side, can connected multiple ports From eaf23c7d79fabb6dddb3203dbc54900366d46963 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:16:00 -0700 Subject: [PATCH 26/52] Update Ports.py --- edg/core/Ports.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/core/Ports.py b/edg/core/Ports.py index ec9f44a36..7b8eb74de 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -174,7 +174,7 @@ def _cloned_from(self: SelfType, other: SelfType) -> None: assert len(other._ports) == len(self._ports) for self_port, other_port in zip(self._ports, other._ports): assert type(other_port) is type(self_port) - other_port._cloned_from(self_port) + self_port._cloned_from(other_port) def init_from(self: SelfType, other: SelfType) -> None: assert self._parent is not None, "may only init_from on an bound port" From a795a91902d27c91e4c881572d3988ff52f420b7 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:17:02 -0700 Subject: [PATCH 27/52] Update SubboardBlock.py --- edg/electronics_model/SubboardBlock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/electronics_model/SubboardBlock.py b/edg/electronics_model/SubboardBlock.py index da2cec560..0721e7c98 100644 --- a/edg/electronics_model/SubboardBlock.py +++ b/edg/electronics_model/SubboardBlock.py @@ -57,7 +57,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab super()._populate_def_proto_hierarchy(pb, ref_map) for exterior_port, internal_port in self._export_taps: - internal_port_name = internal_port._name_from(self).replace(".", "_") + internal_port_name = "_".join(self._path_of(internal_port)) constraint_pb = edgir.add_pair(pb.constraints, f"(export_tap){internal_port_name}") if isinstance(exterior_port, Vector): constraint_pb.exportedArray.exterior_port.ref.CopyFrom(ref_map[exterior_port]) From 8cf81e3e1ceb38c14a48dddb3ba7c0e8ac609db1 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:21:16 -0700 Subject: [PATCH 28/52] Update IoControllerWrapper.py --- edg/abstract_parts/IoControllerWrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/abstract_parts/IoControllerWrapper.py b/edg/abstract_parts/IoControllerWrapper.py index a04af675b..9e7eb50fd 100644 --- a/edg/abstract_parts/IoControllerWrapper.py +++ b/edg/abstract_parts/IoControllerWrapper.py @@ -204,7 +204,7 @@ def recurse_port(port: Port, prefix: str) -> None: pin_type_dict[prefix] = type(port) for subport in port._ports: - recurse_port(subport, f"{prefix}.{self._name_of(subport)}") + recurse_port(subport, f"{prefix}.{port._name_of(subport)}") for io_port in self._io_ports: if isinstance(io_port, Vector): From 9382b4ddac9596bba16ac7314f5204a0d6a52cd5 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:25:15 -0700 Subject: [PATCH 29/52] fix --- edg/core/Core.py | 6 ++++++ edg/core/Generator.py | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index c83cde162..953c7059b 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -115,6 +115,9 @@ def __init__(self) -> None: self._base_names = IdentityDict[Any, Union[str, SourceLocator]]() def __contains__(self, item: Any) -> bool: + if item in self.aliases: + return self.aliases[item] in self + return item in self._base_names def validate_names(self, container_tpe: Type[Any]) -> None: @@ -195,6 +198,9 @@ def name_of(self, item: Any) -> Optional[str]: def name(self, item: Any, *, debug: bool = False) -> str: """Returns the final name of an item""" + if item in self.aliases: + return self.name(self.aliases[item], debug=debug) + name = self._base_names.get(item, None) if isinstance(name, str): return name diff --git a/edg/core/Generator.py b/edg/core/Generator.py index b1a0d6bf2..ad3e454b7 100644 --- a/edg/core/Generator.py +++ b/edg/core/Generator.py @@ -146,11 +146,11 @@ def _generated_def_to_proto( self.generate() except Exception as e: params_list_str = ", ".join( - f"{self._name_of_child(param, self, allow_unknown=True)}={self._generator_param_values[param]!r}" + f"{self._path_name_of(param, debug=True)}={self._generator_param_values[param]!r}" for param in self._generator_params_list ) raise RuntimeError( - f"Generator {self.__class__.__name__} with parameters {{{params_list_str}}} raised exception: {e!r}" + f"Generator {self.__class__.__name__} with parameters {params_list_str} raised exception: {e!r}" ) from e elif self._generator is not None: # legacy generator style fn_args = [ From 6bf035c05e362e806cf626e1d9a1fec181e8e0e4 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:26:56 -0700 Subject: [PATCH 30/52] revert --- edg/core/Generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/core/Generator.py b/edg/core/Generator.py index ad3e454b7..41b70d808 100644 --- a/edg/core/Generator.py +++ b/edg/core/Generator.py @@ -146,7 +146,7 @@ def _generated_def_to_proto( self.generate() except Exception as e: params_list_str = ", ".join( - f"{self._path_name_of(param, debug=True)}={self._generator_param_values[param]!r}" + f"{self._name_of_child(param, self, allow_unknown=True)}={self._generator_param_values[param]!r}" for param in self._generator_params_list ) raise RuntimeError( From 46262d55c05af4abdd77980e6a40e66b0b6e6dc7 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:27:52 -0700 Subject: [PATCH 31/52] Update Generator.py --- edg/core/Generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/core/Generator.py b/edg/core/Generator.py index 41b70d808..8f8e670b0 100644 --- a/edg/core/Generator.py +++ b/edg/core/Generator.py @@ -146,7 +146,7 @@ def _generated_def_to_proto( self.generate() except Exception as e: params_list_str = ", ".join( - f"{self._name_of_child(param, self, allow_unknown=True)}={self._generator_param_values[param]!r}" + f"{self._name_of(param, debug=True)}={self._generator_param_values[param]!r}" for param in self._generator_params_list ) raise RuntimeError( From 60f1dd2681b7ebc506975c034ef18af60908b809 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:28:56 -0700 Subject: [PATCH 32/52] Update Generator.py --- edg/core/Generator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/core/Generator.py b/edg/core/Generator.py index 8f8e670b0..41b70d808 100644 --- a/edg/core/Generator.py +++ b/edg/core/Generator.py @@ -146,7 +146,7 @@ def _generated_def_to_proto( self.generate() except Exception as e: params_list_str = ", ".join( - f"{self._name_of(param, debug=True)}={self._generator_param_values[param]!r}" + f"{self._name_of_child(param, self, allow_unknown=True)}={self._generator_param_values[param]!r}" for param in self._generator_params_list ) raise RuntimeError( From 7031287675ee7c2ae213257eb57b5e785672544d Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:40:07 -0700 Subject: [PATCH 33/52] cleaning --- edg/abstract_parts/IoController.py | 6 ++--- edg/abstract_parts/IoControllerWrapper.py | 4 +-- edg/core/Blocks.py | 2 +- edg/core/Core.py | 33 ++++------------------- edg/core/Generator.py | 2 +- 5 files changed, 11 insertions(+), 36 deletions(-) diff --git a/edg/abstract_parts/IoController.py b/edg/abstract_parts/IoController.py index 4826c02da..394269463 100644 --- a/edg/abstract_parts/IoController.py +++ b/edg/abstract_parts/IoController.py @@ -172,7 +172,7 @@ def connect_port_transformed(self_io: BasePort, inner_io: Callable[[], BasePort] if transform_fn is None: self.connect(self_io, inner_io) else: - connect_port_transformed(self_io, lambda: inner_io, self_io._name_from(self)) + connect_port_transformed(self_io, lambda: inner_io, self._path_name_of(self_io)) else: raise NotImplementedError(f"unknown port type {self_io}") @@ -293,9 +293,7 @@ def _make_pinning(self) -> Mapping[Union[Iterable[str], str], Union[Passive, Has allocation_list.append((io_port.elt_type(), self.get(io_port.requested()))) elif isinstance(io_port, Port): # derive Port connections from is_connected if self.get(io_port.is_connected()): - requested = [ - self._name_of_child(io_port, self) - ] # generate requested name from port name if connected + requested = [self._name_of(io_port)] # generate requested name from port name if connected else: requested = [] allocation_list.append((type(io_port), requested)) diff --git a/edg/abstract_parts/IoControllerWrapper.py b/edg/abstract_parts/IoControllerWrapper.py index 9e7eb50fd..332aa08f7 100644 --- a/edg/abstract_parts/IoControllerWrapper.py +++ b/edg/abstract_parts/IoControllerWrapper.py @@ -91,7 +91,7 @@ def recurse_port(port: Port, prefix: str) -> None: recurse_port(subport, subport_name) elif isinstance(io_port, Port): if self.get(io_port.is_connected()): - port_name = io_port._name_from(self) + port_name = self._path_name_of(io_port) recurse_port(io_port, port_name) else: raise NotImplementedError(f"unknown port type {io_port}") @@ -212,7 +212,7 @@ def recurse_port(port: Port, prefix: str) -> None: recurse_port(io_port._tpe.empty(), subport_name) elif isinstance(io_port, Port): if self.get(io_port.is_connected()): - port_name = io_port._name_from(self) + port_name = self._name_of(io_port) recurse_port(io_port, port_name) else: raise NotImplementedError(f"unknown port type {io_port}") diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index ad26075f5..44acc855b 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -119,7 +119,7 @@ def add_ports(self, ports: Iterable[BasePort]) -> None: initializers = ext_port._get_initializers([]) if initializers: raise UnconnectableError( - f"Connected boundary port {ext_port._name_from(self.parent, allow_unknown=True)} may not have initializers, " + f"Connected boundary port {self.parent._path_name_of(ext_port, debug=True)} may not have initializers, " f"got {', '.join(['.'.join(path) + '=' + str(value) for _, path, value in initializers])}" ) return # is an export, not a connection diff --git a/edg/core/Core.py b/edg/core/Core.py index 953c7059b..234a58dbe 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -378,36 +378,13 @@ def _path_name_of(self, elt: Any, *, debug: bool = False) -> str: def _name_of(self, elt: Any, *, debug: bool = False) -> str: """Returns the name of a contained element, non-recursively.""" - assert elt in self._manager, f"{self.__class__.__name__} has no named {elt}" - return self._manager.name(elt, debug=debug) - - # TODO remove deprecated API - def _name_of_child(self, subelt: Any, context: Any, allow_unknown: bool = False) -> str: - self_name = self._manager.name_of(subelt) - if self_name is not None: - return self_name - else: - if allow_unknown: - return f"(unknown {subelt.__class__.__name__})" + if not elt in self._manager: + if not debug: + raise ValueError(f"{self.__class__.__name__} has no named {elt}") else: - raise ValueError(f"no name for {subelt}") - - # TODO remove deprecated API - def _path_from(self, base: NamingContainer, allow_unknown: bool = False) -> List[str]: - if base is self: - return [] + return f"(unknown {elt.__class__.__name__})" else: - assert self._parent is not None, "can't get path / name for non-bound element" - return self._parent._path_from(base, allow_unknown) + [ - self._parent._name_of_child(self, base, allow_unknown) - ] - - # TODO remove deprecated API - def _name_from(self, base: NamingContainer, allow_unknown: bool = False) -> str: - """Returns the path name to (inclusive) this element from some starting point. - allow_unknown allows elements that haven't been assigned a name yet to not crash, - this is useful when called from an error so the _name_from error doesn't stomp the real error.""" - return ".".join(self._path_from(base, allow_unknown)) + return self._manager.name(elt, debug=debug) @non_library diff --git a/edg/core/Generator.py b/edg/core/Generator.py index 41b70d808..8f8e670b0 100644 --- a/edg/core/Generator.py +++ b/edg/core/Generator.py @@ -146,7 +146,7 @@ def _generated_def_to_proto( self.generate() except Exception as e: params_list_str = ", ".join( - f"{self._name_of_child(param, self, allow_unknown=True)}={self._generator_param_values[param]!r}" + f"{self._name_of(param, debug=True)}={self._generator_param_values[param]!r}" for param in self._generator_params_list ) raise RuntimeError( From c07ed624e0633ea97c700a5e56eaf3c3472ca3ff Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:46:06 -0700 Subject: [PATCH 34/52] Update Core.py --- edg/core/Core.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index 234a58dbe..786013461 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -175,10 +175,8 @@ def add_name(self, item: Any, name: str) -> None: item._set_parent((name, self)) # TODO re-examine the architecture else: return # not expected, ignore - elif existing == name: - return # already assigned, ignore elif isinstance(existing, str): - raise ValueError(f"attempted to reassign name {name} to item with prior name {existing}") + return # reassignments, eg for aliasing for deprecation, ignored; first one takes elif isinstance(existing, SourceLocator): self._base_names.update(item, name) else: From a67fd2c4eb5c28ca0bb7d4ae27bff044c7412d38 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 17:58:52 -0700 Subject: [PATCH 35/52] refactor port params --- edg/core/Ports.py | 31 +++++++++++++--------------- edg/core/Util.py | 2 +- edg/electronics_model/PassivePort.py | 3 ++- 3 files changed, 17 insertions(+), 19 deletions(-) diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 7b8eb74de..053ca977b 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -149,7 +149,7 @@ def __init__(self) -> None: self._bridge_instance: Optional[PortBridge] = None # internal only self._adapter_count: int = 0 - self._parameters: SubElementDict[ConstraintExpr] = self._manager.new_dict(ConstraintExpr) + self._parameters: List[ConstraintExpr] = [] self._ports: List[Port] = [] self._manager_ignored.update(["_is_connected", "_name"]) @@ -157,8 +157,7 @@ def __init__(self) -> None: self._name = StringExpr()._bind(NameBinding(self)) def _clear_initializers(self) -> None: - self._parameters.finalize() - for name, param in self._parameters.items(): + for param in self._parameters: param.initializer = None for port in self._ports: port._clear_initializers() @@ -166,11 +165,10 @@ def _clear_initializers(self) -> None: @override def _cloned_from(self: SelfType, other: SelfType) -> None: super()._cloned_from(other) - self._parameters.finalize() - for name, param in self._parameters.items(): - other_param = other._parameters[name] - assert isinstance(other_param, type(param)) - param.initializer = other_param.initializer + assert len(other._parameters) == len(self._parameters) + for self_param, other_param in zip(self._parameters, other._parameters): + assert type(other_param) is type(self_param) + self_param.initializer = other_param.initializer assert len(other._ports) == len(self._ports) for self_port, other_port in zip(self._ports, other._ports): assert type(other_port) is type(self_port) @@ -227,7 +225,6 @@ def _populate_portlike_proto(self, pb: edgir.PortLike) -> None: @override def _def_to_proto(self) -> edgir.Port: self._manager.validate_names(type(self)) - self._parameters.finalize() pb = edgir.Port() @@ -239,8 +236,8 @@ def _def_to_proto(self) -> edgir.Port: for cls in indirect_bases: pb.super_superclasses.add().target.name = cls._static_def_name() - for name, param in self._parameters.items(): - param._populate_decl_proto(edgir.add_pair(pb.params, name)) + for param in self._parameters: + param._populate_decl_proto(edgir.add_pair(pb.params, self._name_of(param))) for port in self._ports: port._populate_portlike_proto(edgir.add_pair(pb.ports, self._name_of(port))) @@ -257,8 +254,8 @@ def _build_ref_map(self, ref_map: Refable.RefMapType, prefix: edgir.LocalPath) - super()._build_ref_map(ref_map, prefix) ref_map[self.is_connected()] = edgir.localpath_concat(prefix, edgir.IS_CONNECTED) ref_map[self.name()] = edgir.localpath_concat(prefix, edgir.NAME) - for name, param in self._parameters.items(): - param._build_ref_map(ref_map, edgir.localpath_concat(prefix, name)) + for param in self._parameters: + param._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._name_of(param))) for port in self._ports: port._build_ref_map(ref_map, edgir.localpath_concat(prefix, self._name_of(port))) if self._link_instance is not None: @@ -266,10 +263,9 @@ def _build_ref_map(self, ref_map: Refable.RefMapType, prefix: edgir.LocalPath) - @override def _get_initializers(self, path_prefix: List[str]) -> List[Tuple[ConstraintExpr, List[str], ConstraintExpr]]: - self._parameters.finalize() initializers: List[Tuple[ConstraintExpr, List[str], ConstraintExpr]] = [ - (param, path_prefix + [name], param.initializer) - for (name, param) in self._parameters.items() + (param, path_prefix + [self._name_of(param)], param.initializer) + for param in self._parameters if param.initializer is not None ] for port in self._ports: @@ -311,7 +307,8 @@ def link(self) -> PortLinkType: def Parameter(self, tpe: U) -> U: """Registers a parameter for this Port""" elt = tpe._bind(ParamBinding(self)) - self._parameters.register(elt) + self._parameters.append(elt) + self._manager.add_expected(elt, SourceLocator.from_caller()) return elt T = TypeVar("T", bound="Port") diff --git a/edg/core/Util.py b/edg/core/Util.py index 66a447576..df81f86ec 100644 --- a/edg/core/Util.py +++ b/edg/core/Util.py @@ -12,7 +12,7 @@ def edg_to_dict(obj: Union[BasePort, BaseBlock]) -> Dict[str, Any]: if isinstance(obj, Port): result = { "_": edg_obj_name(obj), - "params": obj._parameters, + "params": {obj._name_of(param): param for param in obj._parameters}, "fields": {obj._name_of(port): edg_to_dict(port) for port in obj._ports}, } if obj._link_instance is not None: diff --git a/edg/electronics_model/PassivePort.py b/edg/electronics_model/PassivePort.py index 142654319..65bf22d1c 100644 --- a/edg/electronics_model/PassivePort.py +++ b/edg/electronics_model/PassivePort.py @@ -271,7 +271,8 @@ def adapt_to(self, that: AdaptTargetType) -> AdaptTargetType: # map initializers from that to constructor args adapter_init_kwargs = {} # make everything kwargs for simplicity - for param_name, param in that._parameters.items(): + for param in that._parameters: + param_name = that._name_of(param) assert param.initializer is not None, f"missing initializer for {param_name}" adapter_init_kwargs[param_name] = param.initializer From 520c8980cf106d55d718fbee2c8256fe459c4615 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 18:13:33 -0700 Subject: [PATCH 36/52] bulk rename --- edg/core/Blocks.py | 14 +++++++------- edg/core/Core.py | 14 +++++++------- edg/core/HierarchyBlock.py | 12 ++++++------ edg/core/MultipackBlock.py | 4 ++-- edg/core/Ports.py | 6 +++--- 5 files changed, 25 insertions(+), 25 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 44acc855b..529f80286 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -299,12 +299,12 @@ def __init__(self) -> None: self._required_ports = IdentityDict[BasePort, Optional[BoolExpr]]() # port -> precondition for required self._port_docs = IdentityDict[BasePort, str]() - self._connects = self._manager.new_dict(Connection, anon_prefix="anon_link") + self._connects = self._namer.new_dict(Connection, anon_prefix="anon_link") self._connects_by_port = IdentityDict[BasePort, Connection]() # port -> connection self._connect_delegateds = IdentityDict[ Connection, List[Connection] ]() # for net joins, joined connect -> prior connects - self._constraints: SubElementDict[ConstraintExpr] = self._manager.new_dict( + self._constraints: SubElementDict[ConstraintExpr] = self._namer.new_dict( ConstraintExpr, anon_prefix="anon_constr" ) @@ -372,7 +372,7 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: or self._elaboration_state == BlockElaborationState.post_generate ) - self._manager.validate_names(type(self)) + self._namer.validate_names(type(self)) if (self.__class__, AbstractBlockProperty) in self._elt_properties: assert isinstance(pb, edgir.HierarchyBlock) @@ -505,7 +505,7 @@ def require(self, constraint: BoolLike, name: Optional[str] = None, *, _unchecke self._constraints.register(constraint_typed) if name: # TODO unify naming API with everything else? - self._manager.add_element(name, constraint_typed) + self._namer.add_element(name, constraint_typed) return constraint_typed @@ -530,7 +530,7 @@ def assign( self._constraints.register(constraint) if name: # TODO unify naming API with everything else? - self._manager.add_element(name, constraint) + self._namer.add_element(name, constraint) return constraint @@ -549,7 +549,7 @@ def Port(self, tpe: T, *, optional: BoolLike = False, doc: Optional[str] = None) elt = tpe._bind(self) self._ports.append(elt) - self._manager.add_expected(elt, SourceLocator.from_caller()) + self._namer.add_expected(elt, SourceLocator.from_caller()) if isinstance(optional, bool): if not optional: @@ -579,7 +579,7 @@ def Parameter(self, tpe: ConstraintType, *, doc: Optional[str] = None) -> Constr elt = tpe._bind(ParamBinding(self)) self._parameters.append(elt) - self._manager.add_expected(elt, SourceLocator.from_caller()) + self._namer.add_expected(elt, SourceLocator.from_caller()) if elt.initializer is not None: self.assign(elt, elt.initializer) diff --git a/edg/core/Core.py b/edg/core/Core.py index 786013461..efcbc956d 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -349,15 +349,15 @@ class NamingContainer: def __init__(self) -> None: self._parent: Optional[NamingContainer] = None # set by binding, None means not bound - self._manager = NamespaceManager() - self._manager_ignored: Set[str] = set(["_parent"]) + self._namer = NamespaceManager() + self._manager_ignored: Set[str] = set(["_parent"]) # TODO REMOVE ME @override def __setattr__(self, name: str, value: Any) -> None: if hasattr(self, "_manager_ignored") and name not in self._manager_ignored: - self._manager.add_element(name, value) + self._namer.add_element(name, value) if hasattr(self, "_manager"): # for before _manager is defined - self._manager.add_name(value, name) + self._namer.add_name(value, name) super().__setattr__(name, value) def _path_of(self, elt: Any, *, debug: bool = False) -> List[str]: @@ -376,13 +376,13 @@ def _path_name_of(self, elt: Any, *, debug: bool = False) -> str: def _name_of(self, elt: Any, *, debug: bool = False) -> str: """Returns the name of a contained element, non-recursively.""" - if not elt in self._manager: + if not elt in self._namer: if not debug: raise ValueError(f"{self.__class__.__name__} has no named {elt}") else: return f"(unknown {elt.__class__.__name__})" else: - return self._manager.name(elt, debug=debug) + return self._namer.name(elt, debug=debug) @non_library @@ -391,7 +391,7 @@ class HasMetadata(LibraryElement, NamingContainer): def __init__(self) -> None: super().__init__() - self._metadata: SubElementDict[Any] = self._manager.new_dict(Any) + self._metadata: SubElementDict[Any] = self._namer.new_dict(Any) MetadataType = TypeVar( "MetadataType", bound=Union[str, Mapping[str, Any], SubElementDict[Any], IdentityDict[Any, Any]] diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 92115ee16..fc779c65c 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -336,14 +336,14 @@ def __init__(self) -> None: self._init_params: Dict[str, ConstraintExpr] for param_name, param in self._init_params.items(): self._parameters.append(param) - self._manager.add_expected(param, SourceLocator.from_caller()) - self._manager.add_name(param, param_name) + self._namer.add_expected(param, SourceLocator.from_caller()) + self._namer.add_name(param, param_name) delattr(self, "_init_params") self._mixins: List["BlockInterfaceMixin"] = [] self._blocks: List[Block] = [] - self._chains = self._manager.new_dict(ChainConnect, anon_prefix="anon_chain") + self._chains = self._namer.new_dict(ChainConnect, anon_prefix="anon_chain") self._port_tags = IdentityDict[BasePort, Set[PortTag]]() def _get_ports_by_tag(self, tags: Set[PortTag]) -> List[BasePort]: @@ -500,7 +500,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab # TODO make this non-overriding? @override def _def_to_proto(self) -> edgir.HierarchyBlock: - self._manager.validate_names(type(self)) + self._namer.validate_names(type(self)) assert not self._mixins # blocks with mixins can only be instantiated anonymously ref_map = self._create_ref_map() @@ -543,7 +543,7 @@ def with_mixin(self, tpe: MixinType) -> MixinType: assert self._parent is not None elt = tpe._bind(self._parent) - self._parent._manager.add_alias(elt, self) + self._parent._namer.add_alias(elt, self) self._mixins.append(elt) return elt @@ -742,7 +742,7 @@ def Block(self, tpe: BlockType) -> BlockType: elt = tpe._bind(self) self._blocks.append(elt) - self._manager.add_expected(elt, SourceLocator.from_caller()) + self._namer.add_expected(elt, SourceLocator.from_caller()) return elt diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index 664e6857d..670421b4b 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -163,7 +163,7 @@ def PackedPart(self, tpe: PackedPartType) -> PackedPartType: raise BlockDefinitionError(type(self), "can only define multipack in init") elt = tpe._bind(self) # TODO: does this actually need to be bound? - self._manager.add_expected(elt, SourceLocator.from_caller()) + self._namer.add_expected(elt, SourceLocator.from_caller()) self._packed_connects_by_packed_block[elt] = IdentityDict[BasePort, PackedPortTypes]() self._packed_assigns_by_packed_block[elt] = IdentityDict[ConstraintExpr, PackedParamTypes]() self._unpacked_assigns_by_packed_block[elt] = IdentityDict[ConstraintExpr, UnpackedParamTypes]() @@ -253,7 +253,7 @@ def PackedParameter(self, packed_param: PackedParamTypes) -> ConstraintExpr: raise TypeError() self.packed_assign(new_param, packed_param) self._parameters.append(new_param) - self._manager.add_expected(new_param, SourceLocator.from_caller()) + self._namer.add_expected(new_param, SourceLocator.from_caller()) return new_param def unpacked_assign(self, packed_param: UnpackedParamTypes, self_param: ConstraintExpr) -> None: diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 053ca977b..83ccdb515 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -224,7 +224,7 @@ def _populate_portlike_proto(self, pb: edgir.PortLike) -> None: @override def _def_to_proto(self) -> edgir.Port: - self._manager.validate_names(type(self)) + self._namer.validate_names(type(self)) pb = edgir.Port() @@ -308,7 +308,7 @@ def Parameter(self, tpe: U) -> U: """Registers a parameter for this Port""" elt = tpe._bind(ParamBinding(self)) self._parameters.append(elt) - self._manager.add_expected(elt, SourceLocator.from_caller()) + self._namer.add_expected(elt, SourceLocator.from_caller()) return elt T = TypeVar("T", bound="Port") @@ -320,7 +320,7 @@ def Port(self, tpe: T, *, desc: Optional[str] = None) -> T: elt = tpe._bind(self) self._ports.append(elt) - self._manager.add_expected(elt, SourceLocator.from_caller()) + self._namer.add_expected(elt, SourceLocator.from_caller()) return elt From 01002b8da7cfd2b36e245155834442ddfec1a1da Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 18:41:26 -0700 Subject: [PATCH 37/52] refactor connect naming --- edg/core/Blocks.py | 5 ++-- edg/core/Core.py | 50 +++++++++++++++++++++++--------------- edg/core/HierarchyBlock.py | 19 ++++++++------- edg/core/Link.py | 7 +++--- edg/core/Ports.py | 2 +- 5 files changed, 48 insertions(+), 35 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 529f80286..8164b3c90 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -299,7 +299,7 @@ def __init__(self) -> None: self._required_ports = IdentityDict[BasePort, Optional[BoolExpr]]() # port -> precondition for required self._port_docs = IdentityDict[BasePort, str]() - self._connects = self._namer.new_dict(Connection, anon_prefix="anon_link") + self._connects: List[Connection] = [] self._connects_by_port = IdentityDict[BasePort, Connection]() # port -> connection self._connect_delegateds = IdentityDict[ Connection, List[Connection] @@ -608,7 +608,8 @@ def connect(self, *connects: Union[BasePort, Connection], flatten: bool = False) existing_connects = connects_connects + connects_ports_connects if not existing_connects: connect = Connection(self, flatten) - self._connects.register(connect) + self._connects.append(connect) + self._namer.add_expected(connect, SourceLocator.from_caller(), optional=True) else: # append to (potentially multiple) existing connect connect = existing_connects[0] # first one is authoritative connect_flattens = set([connect.flatten for connect in existing_connects]) diff --git a/edg/core/Core.py b/edg/core/Core.py index efcbc956d..2cefe37de 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -109,21 +109,23 @@ def __repr__(self) -> str: class NamespaceManager: def __init__(self) -> None: + # TODO REMOVE ME self.dicts: List[Tuple[Union[Type[Any], Tuple[Type[Any], ...]], SubElementDict[Any]]] = [] - self.aliases = IdentityDict[Any, Any]() self._base_names = IdentityDict[Any, Union[str, SourceLocator]]() + self._optional_names = IdentityDict[Any, bool]() + self._aliases = IdentityDict[Any, Any]() def __contains__(self, item: Any) -> bool: - if item in self.aliases: - return self.aliases[item] in self + if item in self._aliases: + return self._aliases[item] in self return item in self._base_names def validate_names(self, container_tpe: Type[Any]) -> None: # asserts that all names are given and valid seen_names: Set[str] = set() - missing_names: List[Tuple[Any, SourceLocator]] = [] + missing_names: List[str] = [] duplicate_names: List[str] = [] for obj, name in self._base_names.items(): if isinstance(name, str): @@ -131,7 +133,8 @@ def validate_names(self, container_tpe: Type[Any]) -> None: duplicate_names.append(name) seen_names.add(name) elif isinstance(name, SourceLocator): - missing_names.append((obj, name)) + if obj not in self._optional_names: + missing_names.append(f"{obj}@{name}") else: raise TypeError("unexpected name entry type") @@ -149,7 +152,7 @@ def new_dict( return sub_dict def add_alias(self, src: Any, target: Any) -> None: - self.aliases[src] = target + self._aliases[src] = target # TODO remove old API def add_element(self, name: str, item: Any) -> None: @@ -165,8 +168,10 @@ def add_element(self, name: str, item: Any) -> None: assert name not in dict.container or dict.names.get(item, None) == name, f"duplicate name {name}" assert len(assigned) <= 1, f"assigned {item} to multiple SubElementDict {assigned}" - def add_expected(self, item: Any, source_locator: SourceLocator) -> None: + def add_expected(self, item: Any, source_locator: SourceLocator, *, optional: bool = False) -> None: self._base_names[item] = source_locator + if optional: + self._optional_names[item] = True def add_name(self, item: Any, name: str) -> None: existing = self._base_names.get(item, None) @@ -184,8 +189,8 @@ def add_name(self, item: Any, name: str) -> None: # TODO: remove this, replace with debug_name def name_of(self, item: Any) -> Optional[str]: - if item in self.aliases: - item = self.aliases[item] + if item in self._aliases: + item = self._aliases[item] name_candidates = [sub_dict.name_of(item) for sub_dict_type, sub_dict in self.dicts] name_candidates_filtered = [name_candidate for name_candidate in name_candidates if name_candidate is not None] assert len(name_candidates_filtered) <= 1, f"more than 1 name candidates {name_candidates} for {item}" @@ -194,10 +199,10 @@ def name_of(self, item: Any) -> Optional[str]: else: return name_candidates_filtered[0] - def name(self, item: Any, *, debug: bool = False) -> str: - """Returns the final name of an item""" - if item in self.aliases: - return self.name(self.aliases[item], debug=debug) + def name(self, item: Any, *, debug: bool = False) -> Optional[str]: + """Returns the final name of an item, if available.""" + if item in self._aliases: + return self.name(self._aliases[item], debug=debug) name = self._base_names.get(item, None) if isinstance(name, str): @@ -206,9 +211,7 @@ def name(self, item: Any, *, debug: bool = False) -> str: if debug: return str(name) else: - raise ValueError( - f"no name for {item} at {name} (internal error, should have been caught at validation)" - ) + return None else: raise ValueError(f"no name for unexpected {item} (internal consistency error)") @@ -356,7 +359,7 @@ def __init__(self) -> None: def __setattr__(self, name: str, value: Any) -> None: if hasattr(self, "_manager_ignored") and name not in self._manager_ignored: self._namer.add_element(name, value) - if hasattr(self, "_manager"): # for before _manager is defined + if hasattr(self, "_namer"): # don't access before definition self._namer.add_name(value, name) super().__setattr__(name, value) @@ -376,13 +379,20 @@ def _path_name_of(self, elt: Any, *, debug: bool = False) -> str: def _name_of(self, elt: Any, *, debug: bool = False) -> str: """Returns the name of a contained element, non-recursively.""" - if not elt in self._namer: + if elt not in self._namer: if not debug: - raise ValueError(f"{self.__class__.__name__} has no named {elt}") + raise ValueError(f"{self.__class__.__name__} has no {elt}") else: return f"(unknown {elt.__class__.__name__})" else: - return self._namer.name(elt, debug=debug) + name = self._namer.name(elt, debug=debug) + if name is None: + if debug: + return f"(unnamed {elt.__class__.__name__})" + else: + raise ValueError(f"{self.__class__.__name__} has no name for {elt}") + else: + return name @non_library diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index fc779c65c..652fdcb4b 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -379,7 +379,6 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: param_typed_value._populate_expr_proto(pb.param_defaults[self._name_of(param)], IdentityDict()) def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refable.RefMapType) -> None: - self._connects.finalize() self._chains.finalize() for block in self._blocks: @@ -396,19 +395,19 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab link_chain_names.setdefault(connect, []).append(f"{name}_{i}") delegated_connects = self._all_delegated_connects() - for name, connect in self._connects.items_ordered(): + for i, connect in enumerate(self._connects): if connect in delegated_connects: continue - connect_names_opt = [self._connects.name_of(c) for c in self._all_connects_of(connect)] - connect_names = [c for c in connect_names_opt if c is not None and not c.startswith("anon_")] - if len(connect_names) > 1: - raise UnconnectableError(f"Multiple names {connect_names} for connect") - elif len(connect_names) == 1: - name = connect_names[0] connect_elts = connect.make_connection() - if name.startswith("anon_"): # infer a non-anon name if possible + connect_names_opt = [self._namer.name(c) for c in self._all_connects_of(connect)] + connect_names = [c for c in connect_names_opt if c is not None] + if len(connect_names) > 1: + raise UnconnectableError(f"Multiple assigned names {connect_names} for connect") + elif len(connect_names) == 1: + name = connect_names[0] + else: # infer a better name if possible if connect in link_chain_names and not link_chain_names[connect][0].startswith("anon_"): name = link_chain_names[connect][0] # arbitrarily pick the first chain name elif isinstance(connect_elts, Connection.Export): @@ -422,6 +421,8 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab name = f"_{port_pathname}_link" elif connect in link_chain_names: # if there's really nothing better, anon_chain is better than nothing name = link_chain_names[connect][0] + else: + name = f"anon_link_{i}" if connect_elts is None: # single port net - effectively discard pass diff --git a/edg/core/Link.py b/edg/core/Link.py index 23134e208..7f7896de6 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -51,17 +51,18 @@ def _def_to_proto(self) -> edgir.Link: # specifically ignore the port initializers # actually generate the links and connects - self._connects.finalize() delegated_connects = self._all_delegated_connects() - for name, connect in self._connects.items_ordered(): + for i, connect in enumerate(self._connects): if connect in delegated_connects: continue - connect_names_opt = [self._connects.name_of(c) for c in self._all_connects_of(connect)] + connect_names_opt = [self._namer.name(c) for c in self._all_connects_of(connect)] connect_names = [c for c in connect_names_opt if c is not None and not c.startswith("anon_")] if len(connect_names) > 1: raise UnconnectableError(f"Multiple names {connect_names} for connect") elif len(connect_names) == 1: name = connect_names[0] + else: + name = f"anon_link_{i}" connect_elts = connect.make_connection() assert isinstance(connect_elts, Connection.ConnectedLink) diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 83ccdb515..900d26361 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -211,7 +211,7 @@ def _convert(self, adapter: PortAdapter[ConvertTargetType]) -> ConvertTargetType # TODO proper programmatic naming API adapter_inst = enclosing_block.Block(adapter) adapter_name_suffix = f"_{self._adapter_count}" if self._adapter_count > 0 else "" - enclosing_block._manager.add_name( + enclosing_block._namer.add_name( adapter_inst, f"(adapter){enclosing_block._path_name_of(self)}{adapter_name_suffix}" ) enclosing_block.connect(self, adapter_inst.src) # we don't name it to avoid explicit name conflicts From 5a94dc53b5ec14ce0fea36274d4bc8f6b976bcd8 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 18:50:16 -0700 Subject: [PATCH 38/52] catch dupe name on use --- edg/core/Blocks.py | 2 +- edg/core/Core.py | 22 +++++++++++----------- edg/core/HierarchyBlock.py | 2 +- edg/core/Ports.py | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 8164b3c90..87103d70f 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -372,7 +372,7 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: or self._elaboration_state == BlockElaborationState.post_generate ) - self._namer.validate_names(type(self)) + self._namer.validate_names() if (self.__class__, AbstractBlockProperty) in self._elt_properties: assert isinstance(pb, edgir.HierarchyBlock) diff --git a/edg/core/Core.py b/edg/core/Core.py index 2cefe37de..866490e30 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -108,12 +108,14 @@ def __repr__(self) -> str: class NamespaceManager: - def __init__(self) -> None: + def __init__(self, parent: Any) -> None: # TODO REMOVE ME self.dicts: List[Tuple[Union[Type[Any], Tuple[Type[Any], ...]], SubElementDict[Any]]] = [] + self._parent = parent self._base_names = IdentityDict[Any, Union[str, SourceLocator]]() self._optional_names = IdentityDict[Any, bool]() + self._assigned_names: Set[str] = set() self._aliases = IdentityDict[Any, Any]() def __contains__(self, item: Any) -> bool: @@ -122,16 +124,12 @@ def __contains__(self, item: Any) -> bool: return item in self._base_names - def validate_names(self, container_tpe: Type[Any]) -> None: + def validate_names(self) -> None: # asserts that all names are given and valid - seen_names: Set[str] = set() missing_names: List[str] = [] - duplicate_names: List[str] = [] for obj, name in self._base_names.items(): if isinstance(name, str): - if name in seen_names: - duplicate_names.append(name) - seen_names.add(name) + pass elif isinstance(name, SourceLocator): if obj not in self._optional_names: missing_names.append(f"{obj}@{name}") @@ -139,9 +137,7 @@ def validate_names(self, container_tpe: Type[Any]) -> None: raise TypeError("unexpected name entry type") if missing_names: - raise ElementNamingError(container_tpe, f"no name for objects {missing_names}") - if duplicate_names: - raise ElementNamingError(container_tpe, f"duplicate names {duplicate_names}") + raise ElementNamingError(type(self._parent), f"no name for objects {missing_names}") # TODO remove old API def new_dict( @@ -183,6 +179,10 @@ def add_name(self, item: Any, name: str) -> None: elif isinstance(existing, str): return # reassignments, eg for aliasing for deprecation, ignored; first one takes elif isinstance(existing, SourceLocator): + if name in self._assigned_names: + raise ElementNamingError(type(self._parent), f"duplicate name {name}") + self._assigned_names.add(name) + self._base_names.update(item, name) else: raise ValueError("unexpected name entry type") @@ -352,7 +352,7 @@ class NamingContainer: def __init__(self) -> None: self._parent: Optional[NamingContainer] = None # set by binding, None means not bound - self._namer = NamespaceManager() + self._namer = NamespaceManager(self) self._manager_ignored: Set[str] = set(["_parent"]) # TODO REMOVE ME @override diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 652fdcb4b..57d0600c5 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -501,7 +501,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab # TODO make this non-overriding? @override def _def_to_proto(self) -> edgir.HierarchyBlock: - self._namer.validate_names(type(self)) + self._namer.validate_names() assert not self._mixins # blocks with mixins can only be instantiated anonymously ref_map = self._create_ref_map() diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 900d26361..932fbd117 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -224,7 +224,7 @@ def _populate_portlike_proto(self, pb: edgir.PortLike) -> None: @override def _def_to_proto(self) -> edgir.Port: - self._namer.validate_names(type(self)) + self._namer.validate_names() pb = edgir.Port() From 06c9d619dea875ec689c34d92e7f29fae93d44cf Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 18:54:19 -0700 Subject: [PATCH 39/52] refactor metadata --- edg/core/Blocks.py | 3 ++- edg/core/Core.py | 5 +++-- edg/core/Ports.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 87103d70f..034e61883 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -424,7 +424,8 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: self._constraints.finalize() # needed for source locator generation self._add_doc_metadata() - self._populate_metadata(pb.meta, self._metadata, ref_map) + named_metadata = {self._name_of(metadata): metadata for metadata in self._metadata} + self._populate_metadata(pb.meta, named_metadata, ref_map) def _populate_def_proto_port_init(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) -> None: for port in self._ports: diff --git a/edg/core/Core.py b/edg/core/Core.py index 866490e30..662638bec 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -401,7 +401,7 @@ class HasMetadata(LibraryElement, NamingContainer): def __init__(self) -> None: super().__init__() - self._metadata: SubElementDict[Any] = self._namer.new_dict(Any) + self._metadata: List[Any] = [] MetadataType = TypeVar( "MetadataType", bound=Union[str, Mapping[str, Any], SubElementDict[Any], IdentityDict[Any, Any]] @@ -411,7 +411,8 @@ def Metadata(self, value: MetadataType) -> MetadataType: """Adds a metadata field to this object. Reference to the value must not change, and reassignment will error. Value may be changed until proto generation. Utility method for library builders.""" - self._metadata.register(value) + self._metadata.append(value) + self._namer.add_expected(value, SourceLocator.from_caller()) return value def _populate_metadata(self, pb: edgir.Metadata, src: Any, ref_map: Refable.RefMapType) -> None: diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 932fbd117..4ecd11e80 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -241,8 +241,8 @@ def _def_to_proto(self) -> edgir.Port: for port in self._ports: port._populate_portlike_proto(edgir.add_pair(pb.ports, self._name_of(port))) - self._populate_metadata(pb.meta, self._metadata, IdentityDict()) # TODO use ref map - + named_metadata = {self._name_of(metadata): metadata for metadata in self._metadata} + self._populate_metadata(pb.meta, named_metadata, IdentityDict()) return pb @override From f8d02934c184f9fb99d9f39c16d3e70f973ad738 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 19:05:46 -0700 Subject: [PATCH 40/52] refactor constraints --- edg/core/Blocks.py | 21 +++++++++++---------- edg/core/HierarchyBlock.py | 37 ++++++++++++++++++++++++++++--------- 2 files changed, 39 insertions(+), 19 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 034e61883..0e14c5f04 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -304,9 +304,7 @@ def __init__(self) -> None: self._connect_delegateds = IdentityDict[ Connection, List[Connection] ]() # for net joins, joined connect -> prior connects - self._constraints: SubElementDict[ConstraintExpr] = self._namer.new_dict( - ConstraintExpr, anon_prefix="anon_constr" - ) + self._constraints: List[ConstraintExpr] = [] self._name = StringExpr()._bind(NameBinding(self)) @@ -421,8 +419,6 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: edgir.add_pair(pb.constraints, f"(reqd){self._name_of(port)}"), ref_map ) - self._constraints.finalize() # needed for source locator generation - self._add_doc_metadata() named_metadata = {self._name_of(metadata): metadata for metadata in self._metadata} self._populate_metadata(pb.meta, named_metadata, ref_map) @@ -444,7 +440,10 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: or self._elaboration_state == BlockElaborationState.post_generate ) - for name, constraint in self._constraints.items(): + for i, constraint in enumerate(self._constraints): + name = self._namer.name(constraint) + if name is None: + name = f"anon_constr_{i}" constraint._populate_expr_proto(edgir.add_pair(pb.constraints, name), ref_map) def _populate_def_proto_description(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) -> None: @@ -503,10 +502,11 @@ def require(self, constraint: BoolLike, name: Optional[str] = None, *, _unchecke if not _unchecked: # allow breaking structural / abstraction rules, used in some unit tests self._check_constraint(constraint_typed) - self._constraints.register(constraint_typed) + self._constraints.append(constraint_typed) + self._namer.add_expected(constraint_typed, SourceLocator.from_caller(), optional=True) if name: # TODO unify naming API with everything else? - self._namer.add_element(name, constraint_typed) + self._namer.add_name(constraint_typed, name) return constraint_typed @@ -528,10 +528,11 @@ def assign( self._check_constraint(expr_value) constraint = AssignExpr()._bind(AssignBinding(target, expr_value)) - self._constraints.register(constraint) + self._constraints.append(constraint) + self._namer.add_expected(constraint, SourceLocator.from_caller(), optional=True) if name: # TODO unify naming API with everything else? - self._namer.add_element(name, constraint) + self._namer.add_name(constraint, name) return constraint diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 57d0600c5..9959cc2b8 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -343,7 +343,7 @@ def __init__(self) -> None: self._mixins: List["BlockInterfaceMixin"] = [] self._blocks: List[Block] = [] - self._chains = self._namer.new_dict(ChainConnect, anon_prefix="anon_chain") + self._chains: List[ChainConnect] = [] self._port_tags = IdentityDict[BasePort, Set[PortTag]]() def _get_ports_by_tag(self, tags: Set[PortTag]) -> List[BasePort]: @@ -379,8 +379,6 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: param_typed_value._populate_expr_proto(pb.param_defaults[self._name_of(param)], IdentityDict()) def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refable.RefMapType) -> None: - self._chains.finalize() - for block in self._blocks: new_block_lib = edgir.add_pair(pb.blocks, self._name_of(block)).lib_elem new_block_lib.base.target.name = block._get_def_name() @@ -390,7 +388,10 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab # actually generate the links and connects link_chain_names = IdentityDict[Connection, List[str]]() # prefer chain name where applicable # TODO generate into primary data structures - for name, chain in self._chains.items_ordered(): # TODO work with net join + for i, chain in enumerate(self._chains): # TODO work with net join + name = self._namer.name(chain) + if name is None: + name = f"anon_chain_{i}" for i, connect in enumerate(chain.links): link_chain_names.setdefault(connect, []).append(f"{name}_{i}") @@ -550,12 +551,12 @@ def with_mixin(self, tpe: MixinType) -> MixinType: return elt def chain(self, *elts: Union[Connection, BasePort, Block]) -> Any: - if not elts: - return self._chains.register(ChainConnect([], [])) chain_blocks = [] chain_links = [] - if isinstance(elts[0], (BasePort, Connection)): + if not elts: + current_port: Optional[Union[BasePort, Connection]] = None + elif isinstance(elts[0], (BasePort, Connection)): current_port = elts[0] elif isinstance(elts[0], Block): outable_ports = elts[0]._get_ports_by_tag({Output}) + elts[0]._get_ports_by_tag({InOut}) @@ -570,6 +571,8 @@ def chain(self, *elts: Union[Connection, BasePort, Block]) -> Any: raise EdgTypeError(f"first element 0 to chain", elts[0], (BasePort, Connection, Block)) for i, elt in list(enumerate(elts))[1:-1]: + assert current_port is not None + if not isinstance(elt, Block): raise EdgTypeError(f"middle arguments elts[{i}] in chain", elt, Block) if elt._get_ports_by_tag({Input}) and elt._get_ports_by_tag({Output}): @@ -600,9 +603,13 @@ def chain(self, *elts: Union[Connection, BasePort, Block]) -> Any: type(self), f"element {i} to chain {type(elt)} has no Input and Output, or InOut ports" ) - if isinstance(elts[-1], (BasePort, Connection)): + if not elts: + pass + elif isinstance(elts[-1], (BasePort, Connection)): + assert current_port is not None chain_links.append(self.connect(current_port, elts[-1])) elif isinstance(elts[-1], Block): + assert current_port is not None inable_ports = elts[-1]._get_ports_by_tag({Input}) + elts[-1]._get_ports_by_tag({InOut}) if len(inable_ports) != 1: raise BlockDefinitionError( @@ -614,7 +621,10 @@ def chain(self, *elts: Union[Connection, BasePort, Block]) -> Any: else: raise EdgTypeError(f"last argument {len(elts) - 1} to chain", elts[-1], (BasePort, Connection, Block)) - return self._chains.register(ChainConnect(chain_blocks, chain_links)) + chain = ChainConnect(chain_blocks, chain_links) + self._chains.append(chain) + self._namer.add_expected(chain, SourceLocator.from_caller(), optional=True) + return chain def implicit_connect(self, *implicits: ImplicitConnect) -> ImplicitScope: for implicit in implicits: @@ -634,22 +644,31 @@ def connect(self, *connects: Union[BasePort, Connection], flatten: bool = False) # type ignore is needed because IntLike overlaps BoolLike @overload def ArgParameter(self, param: BoolLike, *, doc: Optional[str] = None) -> BoolExpr: ... # type: ignore + @overload def ArgParameter(self, param: IntLike, *, doc: Optional[str] = None) -> IntExpr: ... # type: ignore + @overload def ArgParameter(self, param: FloatLike, *, doc: Optional[str] = None) -> FloatExpr: ... + @overload def ArgParameter(self, param: RangeLike, *, doc: Optional[str] = None) -> RangeExpr: ... # type: ignore + @overload def ArgParameter(self, param: StringLike, *, doc: Optional[str] = None) -> StringExpr: ... # type: ignore + @overload def ArgParameter(self, param: ArrayBoolLike, *, doc: Optional[str] = None) -> ArrayBoolExpr: ... # type: ignore + @overload def ArgParameter(self, param: ArrayIntLike, *, doc: Optional[str] = None) -> ArrayIntExpr: ... # type: ignore + @overload def ArgParameter(self, param: ArrayFloatLike, *, doc: Optional[str] = None) -> ArrayFloatExpr: ... + @overload def ArgParameter(self, param: ArrayRangeLike, *, doc: Optional[str] = None) -> ArrayRangeExpr: ... + @overload def ArgParameter(self, param: ArrayStringLike, *, doc: Optional[str] = None) -> ArrayStringExpr: ... From 47261cc045a85169a9482d177f8094c04b63abe8 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 19:15:59 -0700 Subject: [PATCH 41/52] dce --- edg/core/Blocks.py | 1 - edg/core/Core.py | 116 +------------------------------------ edg/core/MultipackBlock.py | 2 +- edg/core/Ports.py | 2 +- edg/core/__init__.py | 2 +- 5 files changed, 5 insertions(+), 118 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 0e14c5f04..3c93e5c74 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -16,7 +16,6 @@ Refable, HasMetadata, builder, - SubElementDict, non_library, EltPropertiesBase, SourceLocator, diff --git a/edg/core/Core.py b/edg/core/Core.py index 662638bec..f61632bcc 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -11,79 +11,10 @@ from .. import edgir from .Builder import builder from .IdentityDict import IdentityDict -from .IdentitySet import IdentitySet ElementType = TypeVar("ElementType") -class SubElementDict(Generic[ElementType]): - def __init__(self, anon_prefix: Optional[str] = None) -> None: - self.anons = IdentitySet[ElementType]() # TODO this should be order-preserving? - self.anon_prefix = anon_prefix - self.container: Dict[str, ElementType] = {} - self.names = IdentityDict[ElementType, str]() # TODO unify w/ container? - self.keys_list: List[str] = [] - self.closed = False - - def register(self, item: ElementType) -> ElementType: - assert not self.closed, "adding items after closed" - self.anons.add(item) - return item - - def add_element(self, name: str, item: Any) -> None: - assert not self.closed, "naming items after closed" - assert item in self.anons, f"attempted to add {name}={item}, but did not pre-register" - self.anons.remove(item) - assert name not in self.container, f"attempted to reassign {name}={item}" - - self.container[name] = item - self.names[item] = name - self.keys_list.append(name) - - # TODO should this be automatically called? - def finalize(self) -> None: - if self.anon_prefix is None: - assert not self.anons, f"can't have unnamed objects: {self.anons}" - else: - for id, elt in enumerate(self.anons): - name = f"{self.anon_prefix}_{id}" - assert name not in self.container, f"duplicate name {name}" - self.container[name] = elt - self.names[elt] = name - self.keys_list.append(name) - self.anons = IdentitySet[ElementType]() # TODO needs clear operation - self.closed = True - - def all_values_temp(self) -> Iterable[ElementType]: # TODO needs better API name, reconcile w/ values? - return list(self.container.values()) + list(self.anons) - - # TODO the below allows this to be used like a dict, is this a good idea? - # TODO should these make sure the dict is closed? - def items(self) -> ItemsView[str, ElementType]: - return self.container.items() - - def items_ordered(self) -> Iterable[Tuple[str, ElementType]]: - return [(key, self.container[key]) for key in self.keys_list] - - def keys_ordered(self) -> Iterable[str]: # TODO should this clone? - return self.keys_list - - def values(self) -> ValuesView[ElementType]: - return self.container.values() - - def __getitem__(self, item: str) -> ElementType: - return self.container[item] - - def __contains__(self, item: str) -> bool: - return item in self.container - - def name_of(self, elt: ElementType) -> Optional[str]: - if elt in self.names: - return self.names[elt] - else: - return None - - class SourceLocator: """Object that can be used to locate the source of a library element, for error reporting.""" @@ -109,9 +40,6 @@ def __repr__(self) -> str: class NamespaceManager: def __init__(self, parent: Any) -> None: - # TODO REMOVE ME - self.dicts: List[Tuple[Union[Type[Any], Tuple[Type[Any], ...]], SubElementDict[Any]]] = [] - self._parent = parent self._base_names = IdentityDict[Any, Union[str, SourceLocator]]() self._optional_names = IdentityDict[Any, bool]() @@ -139,31 +67,9 @@ def validate_names(self) -> None: if missing_names: raise ElementNamingError(type(self._parent), f"no name for objects {missing_names}") - # TODO remove old API - def new_dict( - self, filter_type: Union[Type[ElementType], Tuple[Type[ElementType], ...]], anon_prefix: Optional[str] = None - ) -> SubElementDict[ElementType]: - sub_dict = SubElementDict[ElementType](anon_prefix) - self.dicts.append((filter_type, sub_dict)) - return sub_dict - def add_alias(self, src: Any, target: Any) -> None: self._aliases[src] = target - # TODO remove old API - def add_element(self, name: str, item: Any) -> None: - if isinstance(item, ElementDict): - pass # moved to add_name - else: - assigned = [] - for tpe, dict in self.dicts: - if item in dict.anons: - dict.add_element(name, item) - assigned.append(dict) - else: # require not conflicting name, or direct reassignment - assert name not in dict.container or dict.names.get(item, None) == name, f"duplicate name {name}" - assert len(assigned) <= 1, f"assigned {item} to multiple SubElementDict {assigned}" - def add_expected(self, item: Any, source_locator: SourceLocator, *, optional: bool = False) -> None: self._base_names[item] = source_locator if optional: @@ -187,18 +93,6 @@ def add_name(self, item: Any, name: str) -> None: else: raise ValueError("unexpected name entry type") - # TODO: remove this, replace with debug_name - def name_of(self, item: Any) -> Optional[str]: - if item in self._aliases: - item = self._aliases[item] - name_candidates = [sub_dict.name_of(item) for sub_dict_type, sub_dict in self.dicts] - name_candidates_filtered = [name_candidate for name_candidate in name_candidates if name_candidate is not None] - assert len(name_candidates_filtered) <= 1, f"more than 1 name candidates {name_candidates} for {item}" - if not name_candidates_filtered: - return None - else: - return name_candidates_filtered[0] - def name(self, item: Any, *, debug: bool = False) -> Optional[str]: """Returns the final name of an item, if available.""" if item in self._aliases: @@ -357,8 +251,6 @@ def __init__(self) -> None: @override def __setattr__(self, name: str, value: Any) -> None: - if hasattr(self, "_manager_ignored") and name not in self._manager_ignored: - self._namer.add_element(name, value) if hasattr(self, "_namer"): # don't access before definition self._namer.add_name(value, name) super().__setattr__(name, value) @@ -403,9 +295,7 @@ def __init__(self) -> None: super().__init__() self._metadata: List[Any] = [] - MetadataType = TypeVar( - "MetadataType", bound=Union[str, Mapping[str, Any], SubElementDict[Any], IdentityDict[Any, Any]] - ) + MetadataType = TypeVar("MetadataType", bound=Union[str, Mapping[str, Any], IdentityDict[Any, Any]]) def Metadata(self, value: MetadataType) -> MetadataType: """Adds a metadata field to this object. Reference to the value must not change, and reassignment will error. @@ -421,9 +311,7 @@ def _populate_metadata(self, pb: edgir.Metadata, src: Any, ref_map: Refable.RefM pb.text_leaf = src elif isinstance(src, bytes): pb.bin_leaf = src - elif isinstance(src, dict) or isinstance(src, SubElementDict) or isinstance(src, IdentityDict): - if isinstance(src, SubElementDict): # used at the top-level, for Metadata(...) - src.finalize() # TODO should this be here? + elif isinstance(src, dict) or isinstance(src, IdentityDict): for key, val in src.items(): assert isinstance(key, str), f"must overload _metadata_to_proto for non-str dict key {key}" self._populate_metadata(pb.members.node[key], val, ref_map) diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index 670421b4b..e0b64c466 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -10,7 +10,7 @@ from .Blocks import BlockElaborationState from .HdlUserExceptions import BlockDefinitionError, EdgTypeError from .IdentityDict import IdentityDict -from .Core import non_library, SubElementDict, SourceLocator +from .Core import non_library, SourceLocator from .ConstraintExpr import ConstraintExpr, BoolExpr, IntExpr, FloatExpr, RangeExpr, StringExpr from .Ports import BasePort, Port from .HierarchyBlock import Block, BlockPrototype diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 4ecd11e80..e31d99331 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -10,7 +10,7 @@ from .Binding import ParamBinding, IsConnectedBinding, NameBinding from .Builder import builder from .ConstraintExpr import ConstraintExpr, BoolExpr, StringExpr -from .Core import Refable, HasMetadata, SubElementDict, non_library, NamingContainer, SourceLocator +from .Core import Refable, HasMetadata, non_library, NamingContainer, SourceLocator from .HdlUserExceptions import * from .IdentityDict import IdentityDict from .. import edgir diff --git a/edg/core/__init__.py b/edg/core/__init__.py index 8857e8a26..83582ea12 100644 --- a/edg/core/__init__.py +++ b/edg/core/__init__.py @@ -21,7 +21,7 @@ from .MultiBiDict import MultiBiDict # Features for library builders -from .Core import LibraryElement, SubElementDict, ElementDict, non_library +from .Core import LibraryElement, ElementDict, non_library from .Blocks import BasePort, BaseBlock from .Categories import InternalBlock from .Builder import builder From e0eac6655939d20380847d03bb50e825a94b632f Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 19:19:46 -0700 Subject: [PATCH 42/52] Update Blocks.py --- edg/core/Blocks.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 3c93e5c74..0ef69579d 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -495,6 +495,11 @@ def check_subexpr(expr: Union[ConstraintExpr, BasePort]) -> None: # TODO rewrit def require(self, constraint: BoolLike, name: Optional[str] = None, *, _unchecked: bool = False) -> BoolExpr: constraint_typed = BoolExpr._to_expr_type(constraint) + if isinstance(constraint_typed.binding, ParamBinding): + # if this otherwise would return the raw parameter, wrap in an expression + # otherwise, this may redefine the name of the parameter + constraint_typed = constraint_typed == True + if not isinstance(name, (str, type(None))): raise EdgTypeError(f"require(...) name", name, (str, None)) From d9333caaa9178ea156ad6845769f5d9e0b68e30b Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 19:23:06 -0700 Subject: [PATCH 43/52] cleaning --- edg/core/Core.py | 2 -- edg/core/Ports.py | 1 - 2 files changed, 3 deletions(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index f61632bcc..aec4bf4dc 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -245,9 +245,7 @@ class NamingContainer: def __init__(self) -> None: self._parent: Optional[NamingContainer] = None # set by binding, None means not bound - self._namer = NamespaceManager(self) - self._manager_ignored: Set[str] = set(["_parent"]) # TODO REMOVE ME @override def __setattr__(self, name: str, value: Any) -> None: diff --git a/edg/core/Ports.py b/edg/core/Ports.py index e31d99331..80cb54d35 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -152,7 +152,6 @@ def __init__(self) -> None: self._parameters: List[ConstraintExpr] = [] self._ports: List[Port] = [] - self._manager_ignored.update(["_is_connected", "_name"]) self._is_connected = BoolExpr()._bind(IsConnectedBinding(self)) self._name = StringExpr()._bind(NameBinding(self)) From ba6c9d57f53ce837f1fcaaa5808b0b7642c2d30c Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 19:30:56 -0700 Subject: [PATCH 44/52] rebaseline netlist, only ordering changes --- examples/Multimeter/Multimeter.net.ref | 16 ++++++++-------- examples/PcbBot/PcbBot.net.ref | 22 +++++++++++----------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/Multimeter/Multimeter.net.ref b/examples/Multimeter/Multimeter.net.ref index 0f9ec7cb4..e068c45e7 100644 --- a/examples/Multimeter/Multimeter.net.ref +++ b/examples/Multimeter/Multimeter.net.ref @@ -1431,24 +1431,24 @@ (node (ref R28) (pin 1)) (node (ref U19) (pin 5)) (node (ref C30) (pin 1))) -(net (code 6) (name "vcenter") - (node (ref U8) (pin 1)) - (node (ref U8) (pin 4)) - (node (ref U9) (pin 1))) -(net (code 7) (name "spk_chain_0") +(net (code 6) (name "spk_chain_0") (node (ref U6) (pin 36)) (node (ref R14) (pin 1))) -(net (code 8) (name "spk_chain_1") +(net (code 7) (name "spk_chain_1") (node (ref R14) (pin 2)) (node (ref C10) (pin 1)) (node (ref TP4) (pin 1)) (node (ref C13) (pin 2))) -(net (code 9) (name "spk_chain_2.a") +(net (code 8) (name "spk_chain_2.a") (node (ref U7) (pin 8)) (node (ref J4) (pin 1))) -(net (code 10) (name "spk_chain_2.b") +(net (code 9) (name "spk_chain_2.b") (node (ref U7) (pin 5)) (node (ref J4) (pin 2))) +(net (code 10) (name "vcenter") + (node (ref U8) (pin 1)) + (node (ref U8) (pin 4)) + (node (ref U9) (pin 1))) (net (code 11) (name "meas_chain_0") (node (ref R19) (pin 2)) (node (ref R20) (pin 1)) diff --git a/examples/PcbBot/PcbBot.net.ref b/examples/PcbBot/PcbBot.net.ref index d9b885d9f..be81dc67f 100644 --- a/examples/PcbBot/PcbBot.net.ref +++ b/examples/PcbBot/PcbBot.net.ref @@ -1491,17 +1491,9 @@ (node (ref J13) (pin 14)) (node (ref C25) (pin 1)) (node (ref R18) (pin 1))) -(net (code 6) (name "v2v5") - (node (ref U9) (pin 2)) - (node (ref C22) (pin 1)) - (node (ref J13) (pin 21))) -(net (code 7) (name "v1v2") - (node (ref U10) (pin 2)) - (node (ref C24) (pin 1)) - (node (ref J13) (pin 15))) -(net (code 8) (name "touch_0") +(net (code 6) (name "touch_0") (node (ref U4) (pin 7))) -(net (code 9) (name "i2c_chain_0.scl") +(net (code 7) (name "i2c_chain_0.scl") (node (ref U4) (pin 38)) (node (ref J4) (pin 3)) (node (ref J5) (pin 3)) @@ -1514,7 +1506,7 @@ (node (ref U7) (pin 14)) (node (ref J8) (pin 18)) (node (ref J13) (pin 20))) -(net (code 10) (name "i2c_chain_0.sda") +(net (code 8) (name "i2c_chain_0.sda") (node (ref U4) (pin 4)) (node (ref J4) (pin 4)) (node (ref J5) (pin 4)) @@ -1528,6 +1520,14 @@ (node (ref J8) (pin 19)) (node (ref J8) (pin 20)) (node (ref J13) (pin 22))) +(net (code 9) (name "v2v5") + (node (ref U9) (pin 2)) + (node (ref C22) (pin 1)) + (node (ref J13) (pin 21))) +(net (code 10) (name "v1v2") + (node (ref U10) (pin 2)) + (node (ref C24) (pin 1)) + (node (ref J13) (pin 15))) (net (code 11) (name "usb.usb.dp") (node (ref J1) (pin A6)) (node (ref J1) (pin B6)) From b386ac7f77b7d4f734703d3af1c15f268201ae95 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 19:31:06 -0700 Subject: [PATCH 45/52] rebaseline --- examples/Multimeter/Multimeter.svgpcb.js | 2 +- examples/PcbBot/PcbBot.svgpcb.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/Multimeter/Multimeter.svgpcb.js b/examples/Multimeter/Multimeter.svgpcb.js index 7c174973f..a823d916d 100644 --- a/examples/Multimeter/Multimeter.svgpcb.js +++ b/examples/Multimeter/Multimeter.svgpcb.js @@ -542,11 +542,11 @@ board.setNetlist([ {name: "v5v", pads: [["U3", "4"], ["C2", "1"], ["TP1", "1"], ["D3", "1"], ["U4", "1"], ["U4", "3"], ["C3", "1"], ["U5", "1"], ["U5", "3"], ["C5", "1"], ["U7", "1"], ["U7", "6"], ["C11", "1"], ["C12", "1"]]}, {name: "v3v3", pads: [["U4", "5"], ["C4", "1"], ["TP2", "1"], ["D4", "1"], ["U6", "28"], ["U6", "30"], ["C7", "1"], ["J2", "1"], ["D6", "2"], ["J3", "7"], ["R13", "1"], ["C9", "1"], ["R24", "1"]]}, {name: "vanalog", pads: [["U5", "5"], ["C6", "1"], ["TP3", "1"], ["D5", "1"], ["R17", "1"], ["U8", "5"], ["U8", "6"], ["C15", "1"], ["U9", "5"], ["C16", "1"], ["U10", "5"], ["C17", "1"], ["U11", "5"], ["C18", "1"], ["U12", "5"], ["C19", "1"], ["U13", "5"], ["U13", "6"], ["C20", "1"], ["R23", "1"], ["U15", "5"], ["U15", "6"], ["C26", "1"], ["U16", "5"], ["C27", "1"], ["U17", "5"], ["C28", "1"], ["U18", "5"], ["C29", "1"], ["R25", "1"], ["R26", "1"], ["R27", "1"], ["R28", "1"], ["U19", "5"], ["C30", "1"]]}, - {name: "vcenter", pads: [["U8", "1"], ["U8", "4"], ["U9", "1"]]}, {name: "spk_chain_0", pads: [["U6", "36"], ["R14", "1"]]}, {name: "spk_chain_1", pads: [["R14", "2"], ["C10", "1"], ["TP4", "1"], ["C13", "2"]]}, {name: "spk_chain_2.a", pads: [["U7", "8"], ["J4", "1"]]}, {name: "spk_chain_2.b", pads: [["U7", "5"], ["J4", "2"]]}, + {name: "vcenter", pads: [["U8", "1"], ["U8", "4"], ["U9", "1"]]}, {name: "meas_chain_0", pads: [["R19", "2"], ["R20", "1"], ["R21", "1"], ["R22", "1"], ["U13", "3"]]}, {name: "meas_chain_1", pads: [["U13", "1"], ["U13", "4"], ["TP5", "1"], ["U14", "5"]]}, {name: "data_usb.pwr", pads: [["J1", "A4"], ["J1", "A9"], ["J1", "B4"], ["J1", "B9"], ["U6", "32"], ["C8", "1"]]}, diff --git a/examples/PcbBot/PcbBot.svgpcb.js b/examples/PcbBot/PcbBot.svgpcb.js index 5fbc6bd43..6669f3dd0 100644 --- a/examples/PcbBot/PcbBot.svgpcb.js +++ b/examples/PcbBot/PcbBot.svgpcb.js @@ -547,11 +547,11 @@ board.setNetlist([ {name: "vbatt", pads: [["Q1", "3"], ["D3", "1"], ["TP2", "1"], ["Q3", "3"], ["R16", "1"], ["J9", "2"], ["J10", "2"], ["J11", "2"], ["J12", "2"], ["D9", "1"], ["D10", "1"], ["D11", "1"], ["D12", "1"], ["D13", "1"], ["D14", "1"], ["D15", "1"], ["D16", "1"], ["D17", "1"], ["D18", "1"], ["D19", "1"], ["D20", "1"], ["D21", "1"], ["D22", "1"], ["D23", "1"], ["D24", "1"], ["D25", "1"], ["C20", "1"]]}, {name: "pwr", pads: [["D4", "1"], ["Q3", "2"], ["U2", "3"], ["C1", "1"], ["U9", "3"], ["C21", "1"], ["U10", "3"], ["C23", "1"]]}, {name: "v3v3", pads: [["U2", "2"], ["C2", "1"], ["D5", "1"], ["TP3", "1"], ["J3", "1"], ["U4", "2"], ["C5", "1"], ["C6", "1"], ["R8", "1"], ["J4", "1"], ["J4", "6"], ["J5", "1"], ["J6", "1"], ["J7", "1"], ["R10", "1"], ["R11", "1"], ["U5", "12"], ["U5", "5"], ["U5", "8"], ["C8", "1"], ["C9", "1"], ["U6", "13"], ["U6", "2"], ["U6", "4"], ["C10", "1"], ["U7", "16"], ["C13", "1"], ["D8", "2"], ["J8", "11"], ["J8", "6"], ["J8", "9"], ["C17", "1"], ["C18", "1"], ["J13", "14"], ["C25", "1"], ["R18", "1"]]}, - {name: "v2v5", pads: [["U9", "2"], ["C22", "1"], ["J13", "21"]]}, - {name: "v1v2", pads: [["U10", "2"], ["C24", "1"], ["J13", "15"]]}, {name: "touch_0", pads: [["U4", "7"]]}, {name: "i2c_chain_0.scl", pads: [["U4", "38"], ["J4", "3"], ["J5", "3"], ["J6", "3"], ["J7", "3"], ["R10", "2"], ["TP4", "1"], ["U5", "13"], ["U6", "1"], ["U7", "14"], ["J8", "18"], ["J13", "20"]]}, {name: "i2c_chain_0.sda", pads: [["U4", "4"], ["J4", "4"], ["J5", "4"], ["J6", "4"], ["J7", "4"], ["R11", "2"], ["TP5", "1"], ["U5", "14"], ["U6", "16"], ["U7", "15"], ["J8", "19"], ["J8", "20"], ["J13", "22"]]}, + {name: "v2v5", pads: [["U9", "2"], ["C22", "1"], ["J13", "21"]]}, + {name: "v1v2", pads: [["U10", "2"], ["C24", "1"], ["J13", "15"]]}, {name: "usb.usb.dp", pads: [["J1", "A6"], ["J1", "B6"], ["U1", "2"], ["U4", "14"]]}, {name: "usb.usb.dm", pads: [["J1", "A7"], ["J1", "B7"], ["U1", "1"], ["U4", "13"]]}, {name: "usb.conn.cc.cc1", pads: [["J1", "A5"], ["R1", "2"]]}, From ee1901d39f219289ab8f9e0e132591b0ec835169 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 19:36:46 -0700 Subject: [PATCH 46/52] dependent names --- edg/core/Blocks.py | 3 ++- edg/core/Core.py | 24 +++++++++++++++--------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 0ef69579d..8a45ffffb 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -587,7 +587,8 @@ def Parameter(self, tpe: ConstraintType, *, doc: Optional[str] = None) -> Constr self._parameters.append(elt) self._namer.add_expected(elt, SourceLocator.from_caller()) if elt.initializer is not None: - self.assign(elt, elt.initializer) + assign = self.assign(elt, elt.initializer) + self._namer.add_name(assign, lambda: f"(init){self._name_of(elt)}") if doc is not None: self._param_docs[elt] = doc diff --git a/edg/core/Core.py b/edg/core/Core.py index aec4bf4dc..b4b956e0d 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -39,9 +39,14 @@ def __repr__(self) -> str: class NamespaceManager: + """Manages names of objects in a naming container. + Supports source locators via inspect, before an object is given a name. + Supports delayed naming via callables, allowing names to depend on other names that may not yet be assigned. + """ + def __init__(self, parent: Any) -> None: self._parent = parent - self._base_names = IdentityDict[Any, Union[str, SourceLocator]]() + self._base_names = IdentityDict[Any, Union[SourceLocator, str, Callable[[], str]]]() self._optional_names = IdentityDict[Any, bool]() self._assigned_names: Set[str] = set() self._aliases = IdentityDict[Any, Any]() @@ -56,13 +61,11 @@ def validate_names(self) -> None: # asserts that all names are given and valid missing_names: List[str] = [] for obj, name in self._base_names.items(): - if isinstance(name, str): - pass - elif isinstance(name, SourceLocator): + if isinstance(name, SourceLocator): if obj not in self._optional_names: missing_names.append(f"{obj}@{name}") else: - raise TypeError("unexpected name entry type") + pass # has valid name if missing_names: raise ElementNamingError(type(self._parent), f"no name for objects {missing_names}") @@ -75,21 +78,22 @@ def add_expected(self, item: Any, source_locator: SourceLocator, *, optional: bo if optional: self._optional_names[item] = True - def add_name(self, item: Any, name: str) -> None: + def add_name(self, item: Any, name: Union[str, Callable[[], str]]) -> None: existing = self._base_names.get(item, None) if existing is None: if isinstance(item, ElementDict): item._set_parent((name, self)) # TODO re-examine the architecture else: return # not expected, ignore - elif isinstance(existing, str): - return # reassignments, eg for aliasing for deprecation, ignored; first one takes elif isinstance(existing, SourceLocator): if name in self._assigned_names: raise ElementNamingError(type(self._parent), f"duplicate name {name}") - self._assigned_names.add(name) + if isinstance(name, str): + self._assigned_names.add(name) self._base_names.update(item, name) + elif existing is not None: + return # reassignments, eg for aliasing for deprecation, ignored; first one takes else: raise ValueError("unexpected name entry type") @@ -101,6 +105,8 @@ def name(self, item: Any, *, debug: bool = False) -> Optional[str]: name = self._base_names.get(item, None) if isinstance(name, str): return name + elif isinstance(name, Callable): + return name() elif isinstance(name, SourceLocator): if debug: return str(name) From 94ed9dd618e7c7c790df66b6f1cb0c0181ced3ba Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 19:59:23 -0700 Subject: [PATCH 47/52] cleaning --- edg/core/Core.py | 6 ++---- edg/core/Ports.py | 1 - 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index b4b956e0d..9033787e6 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -82,7 +82,7 @@ def add_name(self, item: Any, name: Union[str, Callable[[], str]]) -> None: existing = self._base_names.get(item, None) if existing is None: if isinstance(item, ElementDict): - item._set_parent((name, self)) # TODO re-examine the architecture + item._set_parent((name, self)) else: return # not expected, ignore elif isinstance(existing, SourceLocator): @@ -129,9 +129,7 @@ def _set_parent(self, parent: Tuple[str, NamespaceManager]) -> None: def __setitem__(self, key: Union[str, int], value: ElementType) -> None: assert self._parent is not None - self._parent[1].add_name( - value, f"{self._parent[0]}[{key}]" - ) # TODO perhaps some kind of check to make sure this is required? + self._parent[1].add_name(value, f"{self._parent[0]}[{key}]") self.container[key] = value def __getitem__(self, item: Union[str, int]) -> ElementType: diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 80cb54d35..471017130 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -207,7 +207,6 @@ def _convert(self, adapter: PortAdapter[ConvertTargetType]) -> ConvertTargetType if (block_parent is not enclosing_block) and (block_parent._parent is not enclosing_block): raise UnconnectableError(f"can only create adapters on own ports or subblock ports") - # TODO proper programmatic naming API adapter_inst = enclosing_block.Block(adapter) adapter_name_suffix = f"_{self._adapter_count}" if self._adapter_count > 0 else "" enclosing_block._namer.add_name( From 07be628ea378efc0bb592f25ecfc17ca30a9f0b5 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 20:07:40 -0700 Subject: [PATCH 48/52] Update Core.py --- edg/core/Core.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index 9033787e6..b27580789 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -1,7 +1,7 @@ from __future__ import annotations import inspect -from types import FrameType +from types import FrameType, FunctionType from typing import * from abc import abstractmethod @@ -82,7 +82,8 @@ def add_name(self, item: Any, name: Union[str, Callable[[], str]]) -> None: existing = self._base_names.get(item, None) if existing is None: if isinstance(item, ElementDict): - item._set_parent((name, self)) + item._set_parent(self) + self._base_names[item] = name else: return # not expected, ignore elif isinstance(existing, SourceLocator): @@ -105,8 +106,10 @@ def name(self, item: Any, *, debug: bool = False) -> Optional[str]: name = self._base_names.get(item, None) if isinstance(name, str): return name - elif isinstance(name, Callable): - return name() + elif isinstance(name, FunctionType): + name = name() + assert isinstance(name, str) + return name elif isinstance(name, SourceLocator): if debug: return str(name) @@ -122,14 +125,14 @@ class ElementDict(Generic[ElementType]): # TODO also have a KeyType? Perhaps enforce type bounds, eg must be str-able? def __init__(self) -> None: self.container: Dict[Union[str, int], ElementType] = {} - self._parent: Optional[Tuple[str, NamespaceManager]] = None # name prefix, pointer to top level + self._parent: Optional[NamespaceManager] = None # name prefix, pointer to top level - def _set_parent(self, parent: Tuple[str, NamespaceManager]) -> None: + def _set_parent(self, parent: NamespaceManager) -> None: self._parent = parent def __setitem__(self, key: Union[str, int], value: ElementType) -> None: assert self._parent is not None - self._parent[1].add_name(value, f"{self._parent[0]}[{key}]") + self._parent.add_name(value, lambda: f"{self._parent.name(self)}[{key}]") # type: ignore[union-attr] self.container[key] = value def __getitem__(self, item: Union[str, int]) -> ElementType: From a2fd26401cff160395c05aae709918bf67bd8bb2 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 20:08:47 -0700 Subject: [PATCH 49/52] Update Core.py --- edg/core/Core.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index b27580789..cc8d351e2 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -98,10 +98,10 @@ def add_name(self, item: Any, name: Union[str, Callable[[], str]]) -> None: else: raise ValueError("unexpected name entry type") - def name(self, item: Any, *, debug: bool = False) -> Optional[str]: + def name(self, item: Any) -> Optional[str]: """Returns the final name of an item, if available.""" if item in self._aliases: - return self.name(self._aliases[item], debug=debug) + return self.name(self._aliases[item]) name = self._base_names.get(item, None) if isinstance(name, str): @@ -111,10 +111,7 @@ def name(self, item: Any, *, debug: bool = False) -> Optional[str]: assert isinstance(name, str) return name elif isinstance(name, SourceLocator): - if debug: - return str(name) - else: - return None + return None else: raise ValueError(f"no name for unexpected {item} (internal consistency error)") @@ -282,7 +279,7 @@ def _name_of(self, elt: Any, *, debug: bool = False) -> str: else: return f"(unknown {elt.__class__.__name__})" else: - name = self._namer.name(elt, debug=debug) + name = self._namer.name(elt) if name is None: if debug: return f"(unnamed {elt.__class__.__name__})" From 4ed00268b2421a80c0363aaeb70f91eb42de4236 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 22:10:40 -0700 Subject: [PATCH 50/52] fix mypy --- edg/electronics_interfaces/AnalogPort.py | 2 +- edg/electronics_interfaces/DigitalPorts.py | 8 ++++---- edg/electronics_interfaces/VoltagePorts.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/edg/electronics_interfaces/AnalogPort.py b/edg/electronics_interfaces/AnalogPort.py index 182ebc26d..835049a9b 100644 --- a/edg/electronics_interfaces/AnalogPort.py +++ b/edg/electronics_interfaces/AnalogPort.py @@ -252,7 +252,7 @@ def from_supply( else: # generic default signal = supply_range - return AnalogSource(voltage=supply_range, signal=signal, current_limits=current_limits, impedance=impedance) # type: ignore + return AnalogSource(voltage=supply_range, signal=signal, current_limits=current_limits, impedance=impedance) @deprecated_param_remap(("voltage_out", "voltage"), ("signal_out", "signal")) def __init__( diff --git a/edg/electronics_interfaces/DigitalPorts.py b/edg/electronics_interfaces/DigitalPorts.py index a7605f1c9..8c96e570f 100644 --- a/edg/electronics_interfaces/DigitalPorts.py +++ b/edg/electronics_interfaces/DigitalPorts.py @@ -357,13 +357,13 @@ def from_supply( else: output_threshold = (GroundLink._voltage_range(neg).upper(), VoltageLink._voltage_range(pos).lower()) - return DigitalSource(voltage=supply_range, current_limits=current_limits, output_thresholds=output_threshold) # type: ignore + return DigitalSource(voltage=supply_range, current_limits=current_limits, output_thresholds=output_threshold) @staticmethod def from_bidir(model: DigitalBidir) -> DigitalSource: model_is_empty = not model._get_initializers([]) if not model_is_empty: # DigitalSource has additional high_driver and low_driver fields - return DigitalSource( # type: ignore + return DigitalSource( model.voltage, model.current_limits, output_thresholds=model.output_thresholds, @@ -371,7 +371,7 @@ def from_bidir(model: DigitalBidir) -> DigitalSource: pulldown_capable=model.pulldown_capable, ) else: - return DigitalSource.empty() # type: ignore + return DigitalSource.empty() @deprecated_param_remap(("voltage_out", "voltage")) def __init__( @@ -579,7 +579,7 @@ def from_supply( else: # assumed ideal output_threshold = (neg_base, VoltageLink._voltage_range(pos).lower()) - return DigitalBidir( # type: ignore + return DigitalBidir( voltage_limits=voltage_limit, current_draw=current_draw, voltage=supply_range, diff --git a/edg/electronics_interfaces/VoltagePorts.py b/edg/electronics_interfaces/VoltagePorts.py index 329ebca0d..4131db299 100644 --- a/edg/electronics_interfaces/VoltagePorts.py +++ b/edg/electronics_interfaces/VoltagePorts.py @@ -19,7 +19,7 @@ class VoltageLink(Link): def _voltage_range(cls, port: Port[VoltageLink]) -> RangeExpr: """Returns the voltage for a Voltage port, either sink or source""" if isinstance(port, VoltageSource): - return port.voltage # type: ignore + return port.voltage elif isinstance(port, VoltageSink): return port.link().voltage else: From ebe754a9c513fbdd6d24ea292844140f2d6cda1c Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 22:42:06 -0700 Subject: [PATCH 51/52] fix frame mutability, stage name resolution --- edg/core/Blocks.py | 12 +++-- edg/core/Core.py | 83 +++++++++++++++++++++-------------- edg/core/HierarchyBlock.py | 10 ++--- edg/core/Link.py | 4 +- edg/core/Ports.py | 2 +- edg/core/test_block_errors.py | 18 ++++++-- 6 files changed, 79 insertions(+), 50 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 8a45ffffb..040094f09 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -369,7 +369,7 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: or self._elaboration_state == BlockElaborationState.post_generate ) - self._namer.validate_names() + self._namer.resolve_names() if (self.__class__, AbstractBlockProperty) in self._elt_properties: assert isinstance(pb, edgir.HierarchyBlock) @@ -440,9 +440,7 @@ def _populate_def_proto_block_contents(self, pb: edgir.BlockLikeTypes, ref_map: ) for i, constraint in enumerate(self._constraints): - name = self._namer.name(constraint) - if name is None: - name = f"anon_constr_{i}" + name = self._name_of(constraint) constraint._populate_expr_proto(edgir.add_pair(pb.constraints, name), ref_map) def _populate_def_proto_description(self, pb: edgir.BlockLikeTypes, ref_map: Refable.RefMapType) -> None: @@ -507,7 +505,7 @@ def require(self, constraint: BoolLike, name: Optional[str] = None, *, _unchecke self._check_constraint(constraint_typed) self._constraints.append(constraint_typed) - self._namer.add_expected(constraint_typed, SourceLocator.from_caller(), optional=True) + self._namer.add_expected(constraint_typed, SourceLocator.from_caller(), default_prefix="anon_constr") if name: # TODO unify naming API with everything else? self._namer.add_name(constraint_typed, name) @@ -533,7 +531,7 @@ def assign( constraint = AssignExpr()._bind(AssignBinding(target, expr_value)) self._constraints.append(constraint) - self._namer.add_expected(constraint, SourceLocator.from_caller(), optional=True) + self._namer.add_expected(constraint, SourceLocator.from_caller(), default_prefix="anon_constr") if name: # TODO unify naming API with everything else? self._namer.add_name(constraint, name) @@ -616,7 +614,7 @@ def connect(self, *connects: Union[BasePort, Connection], flatten: bool = False) if not existing_connects: connect = Connection(self, flatten) self._connects.append(connect) - self._namer.add_expected(connect, SourceLocator.from_caller(), optional=True) + self._namer.add_expected(connect, SourceLocator.from_caller(), default_prefix="anon_link") else: # append to (potentially multiple) existing connect connect = existing_connects[0] # first one is authoritative connect_flattens = set([connect.flatten for connect in existing_connects]) diff --git a/edg/core/Core.py b/edg/core/Core.py index cc8d351e2..7422cfae5 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -19,8 +19,13 @@ class SourceLocator: """Object that can be used to locate the source of a library element, for error reporting.""" def __init__(self, top_frame: Optional[FrameType], depth: int) -> None: - self._top_frame = top_frame - self._depth = depth + frame = top_frame + assert frame is not None + for i in range(depth): + frame = frame.f_back + assert frame is not None + self._filename = frame.f_code.co_filename + self._line = frame.f_lineno @classmethod def from_caller(cls) -> SourceLocator: @@ -29,13 +34,7 @@ def from_caller(cls) -> SourceLocator: @override def __repr__(self) -> str: - """Evaluation is lazy since the results are almost never used""" - frame = self._top_frame - assert frame is not None - for i in range(self._depth): - frame = frame.f_back - assert frame is not None - return f"{frame.f_code.co_filename}:{frame.f_lineno}" + return f"{self._filename}:{self._line}" class NamespaceManager: @@ -47,7 +46,8 @@ class NamespaceManager: def __init__(self, parent: Any) -> None: self._parent = parent self._base_names = IdentityDict[Any, Union[SourceLocator, str, Callable[[], str]]]() - self._optional_names = IdentityDict[Any, bool]() + self._default_names = IdentityDict[Any, str]() + self._default_name_prefix_count: Dict[str, int] = {} self._assigned_names: Set[str] = set() self._aliases = IdentityDict[Any, Any]() @@ -57,26 +57,47 @@ def __contains__(self, item: Any) -> bool: return item in self._base_names - def validate_names(self) -> None: - # asserts that all names are given and valid + def resolve_names(self) -> None: + # asserts that all names are given and valid and resolves dependent and anonymous names missing_names: List[str] = [] + error_names: List[Any] = [] for obj, name in self._base_names.items(): if isinstance(name, SourceLocator): - if obj not in self._optional_names: - missing_names.append(f"{obj}@{name}") + default_name = self._default_names.get(obj, None) + if default_name is not None: + self._base_names.update(obj, default_name) + if default_name in self._assigned_names: + raise ElementNamingError(type(self._parent), f"duplicate name {default_name}") + self._assigned_names.add(default_name) + else: + missing_names.append(f"{obj} @ {name}") + elif isinstance(name, FunctionType): + try: + name = name() + assert isinstance(name, str) + if name in self._assigned_names: + raise ElementNamingError(type(self._parent), f"duplicate name {name}") + self._assigned_names.add(name) + self._base_names.update(obj, name) + except Exception as e: + error_names.append(f"{obj} = {e}") else: pass # has valid name if missing_names: raise ElementNamingError(type(self._parent), f"no name for objects {missing_names}") + if error_names: # errors may be due to missing names, so check after missing names are resolved + raise ElementNamingError(type(self._parent), f"error generating names for objects {error_names}") def add_alias(self, src: Any, target: Any) -> None: self._aliases[src] = target - def add_expected(self, item: Any, source_locator: SourceLocator, *, optional: bool = False) -> None: + def add_expected(self, item: Any, source_locator: SourceLocator, *, default_prefix: Optional[str] = None) -> None: self._base_names[item] = source_locator - if optional: - self._optional_names[item] = True + if default_prefix: + count = self._default_name_prefix_count.get(default_prefix, 0) + self._default_name_prefix_count[default_prefix] = count + 1 + self._default_names[item] = f"{default_prefix}{count}" def add_name(self, item: Any, name: Union[str, Callable[[], str]]) -> None: existing = self._base_names.get(item, None) @@ -98,20 +119,25 @@ def add_name(self, item: Any, name: Union[str, Callable[[], str]]) -> None: else: raise ValueError("unexpected name entry type") - def name(self, item: Any) -> Optional[str]: - """Returns the final name of an item, if available.""" + def name(self, item: Any, *, debug: bool = False) -> str: + """Returns the final name of an item, if available. + If debug, returns a debugging name if the final name is not available, otherwise raises.""" if item in self._aliases: - return self.name(self._aliases[item]) + return self.name(self._aliases[item], debug=debug) name = self._base_names.get(item, None) if isinstance(name, str): return name elif isinstance(name, FunctionType): - name = name() - assert isinstance(name, str) - return name + if debug: + return f"(name function for {item})" + else: + raise ValueError(f"name not resolved for function name for {item} (internal consistency error)") elif isinstance(name, SourceLocator): - return None + if debug: + return f"(unnamed {item})" + else: + raise ValueError(f"no name for {item}") else: raise ValueError(f"no name for unexpected {item} (internal consistency error)") @@ -279,14 +305,7 @@ def _name_of(self, elt: Any, *, debug: bool = False) -> str: else: return f"(unknown {elt.__class__.__name__})" else: - name = self._namer.name(elt) - if name is None: - if debug: - return f"(unnamed {elt.__class__.__name__})" - else: - raise ValueError(f"{self.__class__.__name__} has no name for {elt}") - else: - return name + return self._namer.name(elt, debug=debug) @non_library diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 9959cc2b8..bdd8df545 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -403,7 +403,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab connect_elts = connect.make_connection() connect_names_opt = [self._namer.name(c) for c in self._all_connects_of(connect)] - connect_names = [c for c in connect_names_opt if c is not None] + connect_names = [c for c in connect_names_opt if c is not None and not c.startswith("anon_")] if len(connect_names) > 1: raise UnconnectableError(f"Multiple assigned names {connect_names} for connect") elif len(connect_names) == 1: @@ -412,7 +412,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab if connect in link_chain_names and not link_chain_names[connect][0].startswith("anon_"): name = link_chain_names[connect][0] # arbitrarily pick the first chain name elif isinstance(connect_elts, Connection.Export): - port_pathname = "_".join(self._path_name_of(connect_elts.external_port)) + port_pathname = "_".join(self._path_of(connect_elts.external_port)) name = f"_{port_pathname}_link" elif isinstance(connect_elts, Connection.ConnectedLink) and connect_elts.bridged_connects: port_pathname = "_".join(self._path_of(connect_elts.bridged_connects[0][0])) @@ -423,7 +423,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab elif connect in link_chain_names: # if there's really nothing better, anon_chain is better than nothing name = link_chain_names[connect][0] else: - name = f"anon_link_{i}" + name = connect_names_opt[0] if connect_elts is None: # single port net - effectively discard pass @@ -502,7 +502,7 @@ def _populate_def_proto_hierarchy(self, pb: edgir.HierarchyBlock, ref_map: Refab # TODO make this non-overriding? @override def _def_to_proto(self) -> edgir.HierarchyBlock: - self._namer.validate_names() + self._namer.resolve_names() assert not self._mixins # blocks with mixins can only be instantiated anonymously ref_map = self._create_ref_map() @@ -623,7 +623,7 @@ def chain(self, *elts: Union[Connection, BasePort, Block]) -> Any: chain = ChainConnect(chain_blocks, chain_links) self._chains.append(chain) - self._namer.add_expected(chain, SourceLocator.from_caller(), optional=True) + self._namer.add_expected(chain, SourceLocator.from_caller(), default_prefix="anon_chain") return chain def implicit_connect(self, *implicits: ImplicitConnect) -> ImplicitScope: diff --git a/edg/core/Link.py b/edg/core/Link.py index 7f7896de6..9c820c633 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -57,12 +57,12 @@ def _def_to_proto(self) -> edgir.Link: continue connect_names_opt = [self._namer.name(c) for c in self._all_connects_of(connect)] connect_names = [c for c in connect_names_opt if c is not None and not c.startswith("anon_")] - if len(connect_names) > 1: + if len(connect_names) != 1: raise UnconnectableError(f"Multiple names {connect_names} for connect") elif len(connect_names) == 1: name = connect_names[0] else: - name = f"anon_link_{i}" + name = connect_names_opt[0] connect_elts = connect.make_connection() assert isinstance(connect_elts, Connection.ConnectedLink) diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 471017130..c7399f8c5 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -222,7 +222,7 @@ def _populate_portlike_proto(self, pb: edgir.PortLike) -> None: @override def _def_to_proto(self) -> edgir.Port: - self._namer.validate_names() + self._namer.resolve_names() pb = edgir.Port() diff --git a/edg/core/test_block_errors.py b/edg/core/test_block_errors.py index 1765b3372..c379816fc 100644 --- a/edg/core/test_block_errors.py +++ b/edg/core/test_block_errors.py @@ -83,12 +83,13 @@ class BadNamingTestCase(unittest.TestCase): class UnnamedParamBlock(Block): def __init__(self) -> None: super().__init__() - self.Parameter(FloatExpr()) # unnamed param, should error + self.Parameter(FloatExpr(0.0)) # unnamed param, should error + self.param = self.Parameter(IntExpr(2)) # frame continues, checks captured lineno is unaffected def test_unnamed_param(self) -> None: with self.assertRaises(ElementNamingError) as exc: self.UnnamedParamBlock()._elaborated_def_to_proto() - self.assertIn("86", str(exc)) + self.assertIn("86", str(exc.exception)) class DuplicateNameBlock(Block): def __init__(self) -> None: @@ -99,4 +100,15 @@ def __init__(self) -> None: def test_duplicate_name(self) -> None: with self.assertRaises(ElementNamingError) as exc: self.DuplicateNameBlock()._elaborated_def_to_proto() - self.assertIn("duplicate_name", str(exc)) + self.assertIn("duplicate_name", str(exc.exception)) + + class DuplicateAnonNameBlock(Block): + def __init__(self) -> None: + super().__init__() + self.require(True) + self.anon_constr0 = self.require(True) # duplicates a default unassigned name + + def test_duplicate_anon_name(self) -> None: + with self.assertRaises(ElementNamingError) as exc: + self.DuplicateAnonNameBlock()._elaborated_def_to_proto() + self.assertIn("anon_constr0", str(exc.exception)) From 08dead4d9355bb6cc0804fd4da187aa61ce001bd Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Sat, 31 Oct 2026 23:23:03 -0700 Subject: [PATCH 52/52] resolve function names on use --- edg/core/Blocks.py | 2 -- edg/core/Core.py | 29 +++++++++++------------------ edg/core/Generator.py | 2 ++ edg/core/Link.py | 3 ++- edg/core/Ports.py | 2 +- 5 files changed, 16 insertions(+), 22 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 040094f09..4d8512ab9 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -369,8 +369,6 @@ def _populate_def_proto_block_base(self, pb: edgir.BlockLikeTypes) -> None: or self._elaboration_state == BlockElaborationState.post_generate ) - self._namer.resolve_names() - if (self.__class__, AbstractBlockProperty) in self._elt_properties: assert isinstance(pb, edgir.HierarchyBlock) pb.is_abstract = True diff --git a/edg/core/Core.py b/edg/core/Core.py index 7422cfae5..9229f3654 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -58,7 +58,7 @@ def __contains__(self, item: Any) -> bool: return item in self._base_names def resolve_names(self) -> None: - # asserts that all names are given and valid and resolves dependent and anonymous names + # asserts that all names are given and valid and resolves anonymous names missing_names: List[str] = [] error_names: List[Any] = [] for obj, name in self._base_names.items(): @@ -71,16 +71,6 @@ def resolve_names(self) -> None: self._assigned_names.add(default_name) else: missing_names.append(f"{obj} @ {name}") - elif isinstance(name, FunctionType): - try: - name = name() - assert isinstance(name, str) - if name in self._assigned_names: - raise ElementNamingError(type(self._parent), f"duplicate name {name}") - self._assigned_names.add(name) - self._base_names.update(obj, name) - except Exception as e: - error_names.append(f"{obj} = {e}") else: pass # has valid name @@ -120,8 +110,8 @@ def add_name(self, item: Any, name: Union[str, Callable[[], str]]) -> None: raise ValueError("unexpected name entry type") def name(self, item: Any, *, debug: bool = False) -> str: - """Returns the final name of an item, if available. - If debug, returns a debugging name if the final name is not available, otherwise raises.""" + """Returns the name of an item. + If debug, returns a debugging name if the final name is not resolved, otherwise raises.""" if item in self._aliases: return self.name(self._aliases[item], debug=debug) @@ -129,13 +119,16 @@ def name(self, item: Any, *, debug: bool = False) -> str: if isinstance(name, str): return name elif isinstance(name, FunctionType): - if debug: - return f"(name function for {item})" - else: - raise ValueError(f"name not resolved for function name for {item} (internal consistency error)") + name = name() + assert isinstance(name, str) + self._base_names.update(item, name) + if name in self._assigned_names: + raise ElementNamingError(type(self._parent), f"duplicate name {name}") + self._assigned_names.add(name) + return name elif isinstance(name, SourceLocator): if debug: - return f"(unnamed {item})" + return f"(unnamed {item}@{name})" else: raise ValueError(f"no name for {item}") else: diff --git a/edg/core/Generator.py b/edg/core/Generator.py index 8f8e670b0..19e3fdde2 100644 --- a/edg/core/Generator.py +++ b/edg/core/Generator.py @@ -94,6 +94,8 @@ def generate(self) -> None: # @override def _def_to_proto(self) -> edgir.HierarchyBlock: + self._namer.resolve_names() + if ( self._elaboration_state != BlockElaborationState.post_generate ): # only write generator on the stub definition diff --git a/edg/core/Link.py b/edg/core/Link.py index 9c820c633..fc01e621f 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -42,6 +42,7 @@ def __init__(self) -> None: @override def _def_to_proto(self) -> edgir.Link: + self._namer.resolve_names() ref_map = self._create_ref_map() pb = edgir.Link() @@ -57,7 +58,7 @@ def _def_to_proto(self) -> edgir.Link: continue connect_names_opt = [self._namer.name(c) for c in self._all_connects_of(connect)] connect_names = [c for c in connect_names_opt if c is not None and not c.startswith("anon_")] - if len(connect_names) != 1: + if len(connect_names) > 1: raise UnconnectableError(f"Multiple names {connect_names} for connect") elif len(connect_names) == 1: name = connect_names[0] diff --git a/edg/core/Ports.py b/edg/core/Ports.py index c7399f8c5..2ca9d25d3 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -210,7 +210,7 @@ def _convert(self, adapter: PortAdapter[ConvertTargetType]) -> ConvertTargetType adapter_inst = enclosing_block.Block(adapter) adapter_name_suffix = f"_{self._adapter_count}" if self._adapter_count > 0 else "" enclosing_block._namer.add_name( - adapter_inst, f"(adapter){enclosing_block._path_name_of(self)}{adapter_name_suffix}" + adapter_inst, lambda: f"(adapter){enclosing_block._path_name_of(self)}{adapter_name_suffix}" ) enclosing_block.connect(self, adapter_inst.src) # we don't name it to avoid explicit name conflicts self._adapter_count += 1