From c2e1161f56d3c2cdf1037aeb144afac02ed046ce Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 11 Nov 2025 23:10:21 -0800 Subject: [PATCH 01/10] dont instantiate parts --- edg/jlcparts/JlcPartsDiode.py | 2 +- edg/jlcparts/JlcPartsFet.py | 2 +- edg/parts/JlcFet.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/edg/jlcparts/JlcPartsDiode.py b/edg/jlcparts/JlcPartsDiode.py index af7d2a65c..b06c17458 100644 --- a/edg/jlcparts/JlcPartsDiode.py +++ b/edg/jlcparts/JlcPartsDiode.py @@ -70,4 +70,4 @@ def _entry_to_table_row(cls, row_dict: Dict[PartsTableColumn, Any], filename: st return None -lambda: JlcPartsDiode(), JlcPartsZenerDiode() # ensure class is instantiable (non-abstract) +lambda: (JlcPartsDiode(), JlcPartsZenerDiode()) # ensure class is instantiable (non-abstract) diff --git a/edg/jlcparts/JlcPartsFet.py b/edg/jlcparts/JlcPartsFet.py index e3284c90d..b5eb67f7e 100644 --- a/edg/jlcparts/JlcPartsFet.py +++ b/edg/jlcparts/JlcPartsFet.py @@ -68,4 +68,4 @@ class JlcPartsSwitchFet(PartsTableSelectorFootprint, JlcPartsBaseFet, TableSwitc pass -lambda: JlcPartsFet(), JlcPartsSwitchFet() # ensure class is instantiable (non-abstract) +lambda: (JlcPartsFet(), JlcPartsSwitchFet()) # ensure class is instantiable (non-abstract) diff --git a/edg/parts/JlcFet.py b/edg/parts/JlcFet.py index 5e9baebaa..7ddff80e3 100644 --- a/edg/parts/JlcFet.py +++ b/edg/parts/JlcFet.py @@ -196,4 +196,4 @@ class JlcSwitchFet(PartsTableSelectorFootprint, JlcBaseFet, FetFallbackGateCharg pass -lambda: JlcFet, JlcSwitchFet() # ensure class is instantiable (non-abstract) +lambda: (JlcFet(), JlcSwitchFet()) # ensure class is instantiable (non-abstract) From bacca7a9104512f2f0b9448ba8c9b118a63991e1 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 11 Nov 2025 23:10:33 -0800 Subject: [PATCH 02/10] unconditionally passthrough args/kwargs --- edg/core/HierarchyBlock.py | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index e39d6c531..42acaa4fe 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -147,12 +147,11 @@ def __new__(cls, *args: Any, **kwargs: Any) -> Any: arg_data: List[Tuple[str, inspect.Parameter, Type[ConstraintExpr]]] = [] # discard param 0 (self) for arg_name, arg_param in list(inspect.signature(orig_init).parameters.items())[1:]: - if arg_param.kind not in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD): - param_expr_type = BlockMeta._ANNOTATION_EXPR_MAP.get(arg_param.annotation, None) - if param_expr_type is None: - raise BlockDefinitionError(new_cls, f"in {new_cls}.__init__, unknown annotation type for {arg_name}: {arg_param.annotation}") - else: - param_expr_type = ConstraintExpr # dummy placeholder + if arg_param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD): + continue # pass-through *kwargs, handled at lower level + param_expr_type = BlockMeta._ANNOTATION_EXPR_MAP.get(arg_param.annotation, None) + if param_expr_type is None: + raise BlockDefinitionError(new_cls, f"in {new_cls}.__init__, unknown annotation type for {arg_name}: {arg_param.annotation}") arg_data.append((arg_name, arg_param, param_expr_type)) @@ -190,13 +189,7 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) -> new_args: List[Any] = [] new_kwargs: Dict[str, Any] = {} for arg_pos, (arg_name, arg_param, param_expr_type) in enumerate(arg_data): - if arg_param.kind == inspect.Parameter.VAR_POSITIONAL: # pass-through *args, handled at lower level - new_args.extend(args[arg_pos:]) - elif arg_param.kind == inspect.Parameter.VAR_KEYWORD: # pass-through *kwargs, handled at lower level - for arg_name in kwargs: - if arg_name not in new_kwargs: - new_kwargs[arg_name] = kwargs[arg_name] - elif arg_pos < len(args) and arg_param.kind in (inspect.Parameter.POSITIONAL_ONLY, + if arg_pos < len(args) and arg_param.kind in (inspect.Parameter.POSITIONAL_ONLY, inspect.Parameter.POSITIONAL_OR_KEYWORD): # present positional arg new_arg = remap_arg(arg_name, param_expr_type, args[arg_pos]) new_args.append(new_arg) @@ -221,11 +214,17 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) -> new_arg = remap_arg(arg_name, param_expr_type, None) new_kwargs[arg_name] = new_arg self._init_params[arg_name] = new_arg + + # unconditioally pass through all args and kwargs + new_args.extend(args[len(new_args):]) + for arg_name in kwargs: + if arg_name not in new_kwargs: + new_kwargs[arg_name] = kwargs[arg_name] + + orig_init(self, *new_args, **new_kwargs) finally: builder.pop_to(builder_prev) - orig_init(self, *new_args, **new_kwargs) - new_cls.__init__ = functools.update_wrapper(wrapped_init, orig_init) return new_cls From c8ddbc69a16d88ab623f896699b06b23f1b4a7ef Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 11 Nov 2025 23:10:39 -0800 Subject: [PATCH 03/10] *boom* --- edg/core/Core.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/edg/core/Core.py b/edg/core/Core.py index 5be80f322..0f3648561 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -202,8 +202,6 @@ def __init__(self) -> None: self._parent: Optional[LibraryElement] = None # set by binding, None means not bound self._initializer_args: Tuple[Tuple[Any, ...], Dict[str, Any]] # set by metaclass - builder.push_element(self) - self.manager = SubElementManager() self.manager_ignored: Set[str] = set(['_parent']) From 069fa68361975d28d3945f666392e41a6aacce28 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Tue, 11 Nov 2025 23:46:05 -0800 Subject: [PATCH 04/10] refactor --- edg/core/Blocks.py | 20 ++++++-------------- edg/core/Builder.py | 21 +++++++++------------ edg/core/ConstraintExpr.py | 5 +++-- edg/core/HierarchyBlock.py | 14 ++++++++++---- edg/core/Link.py | 32 ++++++++++++++++++++++++++++++-- edg/core/MultipackBlock.py | 4 ++-- 6 files changed, 60 insertions(+), 36 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index eae7d1174..3b559a6c3 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -415,27 +415,19 @@ def _bind(self: SelfType, parent: Union[BaseBlock, Port]) -> SelfType: def _check_constraint(self, constraint: ConstraintExpr) -> None: def check_subexpr(expr: Union[ConstraintExpr, BasePort]) -> None: # TODO rewrite this whole method if isinstance(expr, ConstraintExpr) and isinstance(expr.binding, ParamBinding): - if isinstance(expr.parent, BaseBlock): - block_parent = expr.parent - elif isinstance(expr.parent, BasePort): - block_parent = cast(BaseBlock, expr.parent._block_parent()) # TODO make less ugly - assert block_parent is not None + if isinstance(expr._context, BaseBlock): + block_parent = expr._context else: - raise ValueError(f"unknown parent {expr.parent} of {expr}") + raise ValueError(f"unknown parent {expr._context} of {expr}") - if isinstance(block_parent._parent, BasePort): - block_parent_parent: Any = block_parent._parent._block_parent() - else: - block_parent_parent = block_parent._parent - - if not (block_parent is self or block_parent_parent is self): - raise UnreachableParameterError(f"In {type(self)}, constraint references unreachable parameter {expr}. " + if not (block_parent is self or block_parent._parent is self): + raise UnreachableParameterError(f"In {self}, constraint references unreachable parameter {expr}. " "Only own parameters, or immediate contained blocks' parameters can be accessed.") elif isinstance(expr, BasePort): block_parent = cast(BaseBlock, expr._block_parent()) assert block_parent is not None if not block_parent is self or block_parent._parent is self: - raise UnreachableParameterError(f"In {type(self)}, constraint references unreachable port {expr}. " + raise UnreachableParameterError(f"In {self}, constraint references unreachable port {expr}. " "Only own ports, or immediate contained blocks' ports can be accessed.") for subexpr in constraint._get_exprs(): diff --git a/edg/core/Builder.py b/edg/core/Builder.py index dece5a7c8..4413251fa 100644 --- a/edg/core/Builder.py +++ b/edg/core/Builder.py @@ -2,33 +2,30 @@ from typing import * +from deprecated import deprecated + from .. import edgir if TYPE_CHECKING: - from .Core import Refable from .Blocks import BaseBlock class Builder: def __init__(self) -> None: - self.stack: List[Refable] = [] + self.stack: List[BaseBlock] = [] - def push_element(self, elt: Refable) -> None: + def push_element(self, elt: BaseBlock) -> None: self.stack.append(elt) - def pop_to(self, elt: Optional[Refable]) -> None: + def pop_to(self, elt: Optional[BaseBlock]) -> None: self.stack.pop() assert self.get_curr_context() is elt + @deprecated("use get_curr_context() instead, context frames can only be blocks now") def get_enclosing_block(self) -> Optional[BaseBlock]: - from .Blocks import BaseBlock - # traverse down the stack and get the first BaseBlock, ignoring things like ports - for elt in reversed(self.stack): - if isinstance(elt, BaseBlock): - return elt - return None - - def get_curr_context(self) -> Optional[Refable]: + return self.get_curr_context() + + def get_curr_context(self) -> Optional[BaseBlock]: if not self.stack: return None else: diff --git a/edg/core/ConstraintExpr.py b/edg/core/ConstraintExpr.py index 03511f6c1..edebcfc78 100644 --- a/edg/core/ConstraintExpr.py +++ b/edg/core/ConstraintExpr.py @@ -17,6 +17,7 @@ if TYPE_CHECKING: from .Ports import BasePort + from .Blocks import BaseBlock SelfType = TypeVar('SelfType', bound='ConstraintExpr') @@ -64,7 +65,7 @@ def __init__(self: SelfType, initializer: Optional[Union[SelfType, WrappedType]] self.initializer = None else: self.initializer = self._to_expr_type(initializer) - self.parent = builder.get_curr_context() + self._context: Optional[BaseBlock] = builder.get_curr_context() def _get_exprs(self) -> Iterable[Union[ConstraintExpr, BasePort]]: assert self.binding is not None @@ -80,7 +81,7 @@ def _new_bind(cls: Type[SelfType], binding: Binding) -> SelfType: def _bind(self: SelfType, binding: Binding) -> SelfType: """Returns a clone of this object with the specified binding. This object must be unbound.""" assert not self._is_bound() - assert builder.get_curr_context() is self.parent, f"can't clone in original context {self.parent} to different new context {builder.get_curr_context()}" + assert builder.get_curr_context() is self._context, f"can't clone in original context {self._context} to different new context {builder.get_curr_context()}" if not isinstance(binding, ParamBinding): assert self.initializer is None, "Only Parameters may have initializers" clone: SelfType = type(self)(self.initializer) diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 42acaa4fe..da97d1a46 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -181,9 +181,14 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) -> return arg_type()._bind(InitParamBinding(self, typed_arg_value)) - # create wrapper ConstraintExpr in new object scope - builder_prev = builder.get_curr_context() - builder.push_element(self) + if builder.get_curr_context() is not self: + # test needed to make sure we don't double-push in nested super().__init__ calls + # create wrapper ConstraintExpr in new object scope + builder_prev = (True, builder.get_curr_context()) + builder.push_element(self) + else: + builder_prev = (False, None) # dummy value for try/finally + try: # rebuild args and kwargs by traversing the args list new_args: List[Any] = [] @@ -223,7 +228,8 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) -> orig_init(self, *new_args, **new_kwargs) finally: - builder.pop_to(builder_prev) + if builder_prev[0]: + builder.pop_to(builder_prev[1]) new_cls.__init__ = functools.update_wrapper(wrapped_init, orig_init) diff --git a/edg/core/Link.py b/edg/core/Link.py index bb9341e0a..86cce475d 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -1,18 +1,46 @@ from __future__ import annotations +import functools from typing import * from .. import edgir from .Array import BaseVector, DerivedVector from .Blocks import BaseBlock, Connection -from .Core import Refable, non_library +from .Builder import builder +from .Core import Refable, non_library, ElementMeta from .HdlUserExceptions import UnconnectableError from .IdentityDict import IdentityDict from .Ports import Port +class LinkMeta(ElementMeta): + def __new__(cls, *args: Any, **kwargs: Any) -> Any: + new_cls = super().__new__(cls, *args, **kwargs) + + if '__init__' in new_cls.__dict__: + orig_init = new_cls.__dict__['__init__'] + def wrapped_init(self, *args, **kwargs) -> None: + if builder.get_curr_context() is not self: + # test needed to make sure we don't double-push in nested super().__init__ calls + # create wrapper ConstraintExpr in new object scope + builder_prev = (True, builder.get_curr_context()) + builder.push_element(self) + else: + builder_prev = (False, None) # dummy value for try/finally + + try: + orig_init(self, *args, **kwargs) + finally: + if builder_prev[0]: + builder.pop_to(builder_prev[1]) + + new_cls.__init__ = functools.update_wrapper(wrapped_init, orig_init) + + return new_cls + + @non_library -class Link(BaseBlock[edgir.Link]): +class Link(BaseBlock[edgir.Link], metaclass=LinkMeta): def __init__(self) -> None: super().__init__() self.parent: Optional[Port] = None diff --git a/edg/core/MultipackBlock.py b/edg/core/MultipackBlock.py index 30e88ee60..c55aeb3e6 100644 --- a/edg/core/MultipackBlock.py +++ b/edg/core/MultipackBlock.py @@ -188,7 +188,7 @@ def packed_assign(self, self_param: ConstraintExpr, packed_param: PackedParamTyp raise BlockDefinitionError(self, "can only define multipack in init") if isinstance(packed_param, ConstraintExpr): assert type(self_param) == type(packed_param), "packed_assign parameters must be of the same type" - block_parent = packed_param.parent + block_parent = packed_param._context assert isinstance(block_parent, Block) self._packed_assigns_by_packed_block[block_parent][self_param] = packed_param elif isinstance(packed_param, PackedBlockParamArray): @@ -234,7 +234,7 @@ def unpacked_assign(self, packed_param: UnpackedParamTypes, self_param: Constrai raise BlockDefinitionError(self, "can only define multipack in init") if isinstance(packed_param, ConstraintExpr): assert type(packed_param) == type(self_param), "unpacked_assign parameters must be of the same type" - block_parent = packed_param.parent + block_parent = packed_param._context assert isinstance(block_parent, Block) self._unpacked_assigns_by_packed_block[block_parent][self_param] = packed_param elif isinstance(packed_param, PackedBlockParam): From efb58f752bd9702ec76759e6401f8b3cc0c6224a Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 12 Nov 2025 00:00:42 -0800 Subject: [PATCH 05/10] Update Blocks.py --- edg/core/Blocks.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 3b559a6c3..b2f4c7350 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -415,12 +415,14 @@ def _bind(self: SelfType, parent: Union[BaseBlock, Port]) -> SelfType: def _check_constraint(self, constraint: ConstraintExpr) -> None: def check_subexpr(expr: Union[ConstraintExpr, BasePort]) -> None: # TODO rewrite this whole method if isinstance(expr, ConstraintExpr) and isinstance(expr.binding, ParamBinding): - if isinstance(expr._context, BaseBlock): - block_parent = expr._context + if isinstance(expr.binding.parent, BaseBlock): + expr_parent = expr.binding.parent + elif isinstance(expr.binding.parent, BasePort): + expr_parent = expr.binding.parent._block_parent() else: - raise ValueError(f"unknown parent {expr._context} of {expr}") + raise ValueError("unexpected parent type") - if not (block_parent is self or block_parent._parent is self): + if not (expr_parent is self or expr_parent._parent is self): raise UnreachableParameterError(f"In {self}, constraint references unreachable parameter {expr}. " "Only own parameters, or immediate contained blocks' parameters can be accessed.") elif isinstance(expr, BasePort): From 34b98f26ee2fc7e12ff8931c1831da96b4047e37 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 12 Nov 2025 00:03:58 -0800 Subject: [PATCH 06/10] Update Blocks.py --- edg/core/Blocks.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index b2f4c7350..2768df507 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -415,14 +415,17 @@ def _bind(self: SelfType, parent: Union[BaseBlock, Port]) -> SelfType: def _check_constraint(self, constraint: ConstraintExpr) -> None: def check_subexpr(expr: Union[ConstraintExpr, BasePort]) -> None: # TODO rewrite this whole method if isinstance(expr, ConstraintExpr) and isinstance(expr.binding, ParamBinding): - if isinstance(expr.binding.parent, BaseBlock): - expr_parent = expr.binding.parent - elif isinstance(expr.binding.parent, BasePort): - expr_parent = expr.binding.parent._block_parent() - else: - raise ValueError("unexpected parent type") + expr_parent = expr.binding.parent + if isinstance(expr_parent, BasePort): + expr_block_parent = expr_parent._block_parent() + assert expr_block_parent is not None + expr_parent = expr_block_parent + + expr_parent_parent = expr_parent._parent # may be None for top-level + if isinstance(expr_parent_parent, BasePort): # resolve Link parent port to parent block + expr_parent_parent = expr_parent_parent._block_parent() - if not (expr_parent is self or expr_parent._parent is self): + if not (expr_parent is self or expr_parent_parent is self): raise UnreachableParameterError(f"In {self}, constraint references unreachable parameter {expr}. " "Only own parameters, or immediate contained blocks' parameters can be accessed.") elif isinstance(expr, BasePort): From afc358f53bb61a409ff78402901c9e2918eb46d4 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 12 Nov 2025 00:09:25 -0800 Subject: [PATCH 07/10] wip --- edg/core/Blocks.py | 2 +- edg/core/Builder.py | 12 ++++++------ edg/core/ConstraintExpr.py | 4 ++-- edg/core/Core.py | 17 ++++++----------- edg/core/HierarchyBlock.py | 4 ++-- edg/core/Link.py | 4 ++-- edg/core/Ports.py | 2 -- 7 files changed, 19 insertions(+), 26 deletions(-) diff --git a/edg/core/Blocks.py b/edg/core/Blocks.py index 2768df507..fe7850fad 100644 --- a/edg/core/Blocks.py +++ b/edg/core/Blocks.py @@ -407,7 +407,7 @@ def _bind_in_place(self, parent: Union[BaseBlock, Port]): def _bind(self: SelfType, parent: Union[BaseBlock, Port]) -> SelfType: """Returns a clone of this object with the specified binding. This object must be unbound.""" assert self._parent is None, "can't clone bound block" - assert builder.get_curr_context() is self._lexical_parent, "can't clone to different context" + assert builder.get_enclosing_block() is self._block_context, "can't clone to different context" clone = type(self)(*self._initializer_args[0], **self._initializer_args[1]) # type: ignore clone._bind_in_place(parent) return clone diff --git a/edg/core/Builder.py b/edg/core/Builder.py index 4413251fa..fd70e8266 100644 --- a/edg/core/Builder.py +++ b/edg/core/Builder.py @@ -19,22 +19,22 @@ def push_element(self, elt: BaseBlock) -> None: def pop_to(self, elt: Optional[BaseBlock]) -> None: self.stack.pop() - assert self.get_curr_context() is elt + assert self.get_enclosing_block() is elt - @deprecated("use get_curr_context() instead, context frames can only be blocks now") def get_enclosing_block(self) -> Optional[BaseBlock]: - return self.get_curr_context() - - def get_curr_context(self) -> Optional[BaseBlock]: if not self.stack: return None else: return self.stack[-1] + @deprecated("use get_enclosing_block() instead, context frames can only be blocks now") + def get_curr_context(self) -> Optional[BaseBlock]: + return self.get_enclosing_block() + def elaborate_toplevel(self, block: BaseBlock, *, is_generator: bool = False, generate_values: Iterable[Tuple[edgir.LocalPath, edgir.ValueLit]] = []) -> edgir.HierarchyBlock: - assert self.get_curr_context() is None + assert self.get_enclosing_block() is None self.push_element(block) try: if is_generator: # TODO this is kind of nasty =( diff --git a/edg/core/ConstraintExpr.py b/edg/core/ConstraintExpr.py index edebcfc78..09b8fecdf 100644 --- a/edg/core/ConstraintExpr.py +++ b/edg/core/ConstraintExpr.py @@ -65,7 +65,7 @@ def __init__(self: SelfType, initializer: Optional[Union[SelfType, WrappedType]] self.initializer = None else: self.initializer = self._to_expr_type(initializer) - self._context: Optional[BaseBlock] = builder.get_curr_context() + self._context: Optional[BaseBlock] = builder.get_enclosing_block() def _get_exprs(self) -> Iterable[Union[ConstraintExpr, BasePort]]: assert self.binding is not None @@ -81,7 +81,7 @@ def _new_bind(cls: Type[SelfType], binding: Binding) -> SelfType: def _bind(self: SelfType, binding: Binding) -> SelfType: """Returns a clone of this object with the specified binding. This object must be unbound.""" assert not self._is_bound() - assert builder.get_curr_context() is self._context, f"can't clone in original context {self._context} to different new context {builder.get_curr_context()}" + assert builder.get_enclosing_block() is self._context, f"can't clone in original context {self._context} to different new context {builder.get_curr_context()}" if not isinstance(binding, ParamBinding): assert self.initializer is None, "Only Parameters may have initializers" clone: SelfType = type(self)(self.initializer) diff --git a/edg/core/Core.py b/edg/core/Core.py index 0f3648561..d428aea93 100644 --- a/edg/core/Core.py +++ b/edg/core/Core.py @@ -146,17 +146,12 @@ class ElementMeta(type): """Hook on construction to store some metadata about its creation. This hooks the top-level __init__ only.""" def __call__(cls, *args, **kwargs): - parent = builder.get_curr_context() block_context = builder.get_enclosing_block() - try: - obj = type.__call__(cls, *args, **kwargs) - obj._initializer_args = (args, kwargs) # stores args so it is clone-able - obj._lexical_parent = parent # stores context for error checking - obj._block_context = block_context - obj._post_init() - finally: - if builder.get_curr_context() is not parent: # in case the constructor skipped internal element init - builder.pop_to(parent) + + obj = type.__call__(cls, *args, **kwargs) + obj._initializer_args = (args, kwargs) # stores args so it is clone-able + obj._block_context = block_context + obj._post_init() return obj @@ -198,7 +193,7 @@ def __repr__(self) -> str: return "%s@%02x" % (self._get_def_name(), (id(self) // 4) & 0xff) def __init__(self) -> None: - self._lexical_parent: Optional[LibraryElement] # set by metaclass + self._block_context: Optional["Refable"] # set by metaclass, as lexical scope available pre-binding self._parent: Optional[LibraryElement] = None # set by binding, None means not bound self._initializer_args: Tuple[Tuple[Any, ...], Dict[str, Any]] # set by metaclass diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index da97d1a46..657d9d439 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -181,10 +181,10 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) -> return arg_type()._bind(InitParamBinding(self, typed_arg_value)) - if builder.get_curr_context() is not self: + if builder.get_enclosing_block() is not self: # test needed to make sure we don't double-push in nested super().__init__ calls # create wrapper ConstraintExpr in new object scope - builder_prev = (True, builder.get_curr_context()) + builder_prev = (True, builder.get_enclosing_block()) builder.push_element(self) else: builder_prev = (False, None) # dummy value for try/finally diff --git a/edg/core/Link.py b/edg/core/Link.py index 86cce475d..7e54ffbfa 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -20,10 +20,10 @@ def __new__(cls, *args: Any, **kwargs: Any) -> Any: if '__init__' in new_cls.__dict__: orig_init = new_cls.__dict__['__init__'] def wrapped_init(self, *args, **kwargs) -> None: - if builder.get_curr_context() is not self: + if builder.get_enclosing_block() is not self: # test needed to make sure we don't double-push in nested super().__init__ calls # create wrapper ConstraintExpr in new object scope - builder_prev = (True, builder.get_curr_context()) + builder_prev = (True, builder.get_enclosing_block()) builder.push_element(self) else: builder_prev = (False, None) # dummy value for try/finally diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 570086fc7..3c487b3d7 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -14,7 +14,6 @@ if TYPE_CHECKING: from .Blocks import BaseBlock - from .Link import Link from .PortBlocks import PortBridge, PortAdapter @@ -26,7 +25,6 @@ class BasePort(HasMetadata): def __init__(self) -> None: """Abstract Base Class for ports""" self._parent: Optional[PortParentTypes] # refined from Optional[Refable] in base LibraryElement - self._block_context: Optional[BaseBlock] # set by metaclass, as lexical scope available pre-binding self._initializer_args: Tuple[Tuple[Any, ...], Dict[str, Any]] # set by metaclass super().__init__() From 7ffb357a35f1a855355179fa02ae1e0067223ef1 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 12 Nov 2025 00:16:51 -0800 Subject: [PATCH 08/10] cleaning --- edg/core/HierarchyBlock.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 657d9d439..b163c3a39 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -148,7 +148,7 @@ def __new__(cls, *args: Any, **kwargs: Any) -> Any: # discard param 0 (self) for arg_name, arg_param in list(inspect.signature(orig_init).parameters.items())[1:]: if arg_param.kind in (inspect.Parameter.VAR_POSITIONAL, inspect.Parameter.VAR_KEYWORD): - continue # pass-through *kwargs, handled at lower level + continue # pass through of *args, **kwargs handled later param_expr_type = BlockMeta._ANNOTATION_EXPR_MAP.get(arg_param.annotation, None) if param_expr_type is None: raise BlockDefinitionError(new_cls, f"in {new_cls}.__init__, unknown annotation type for {arg_name}: {arg_param.annotation}") From ed4e04e78ee90146aad81d276ef2c575b558baae Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 12 Nov 2025 00:25:09 -0800 Subject: [PATCH 09/10] cleanup --- edg/core/Builder.py | 13 +++++++++---- edg/core/HierarchyBlock.py | 12 ++---------- edg/core/Link.py | 12 ++---------- edg/core/Ports.py | 1 + 4 files changed, 14 insertions(+), 24 deletions(-) diff --git a/edg/core/Builder.py b/edg/core/Builder.py index fd70e8266..45bdce6a5 100644 --- a/edg/core/Builder.py +++ b/edg/core/Builder.py @@ -14,12 +14,17 @@ class Builder: def __init__(self) -> None: self.stack: List[BaseBlock] = [] - def push_element(self, elt: BaseBlock) -> None: - self.stack.append(elt) + def push_element(self, elt: BaseBlock) -> Optional[BaseBlock]: + """Pushes a new element onto the context stack, returning the previous top element. + Ignores if the element is already on top of the stack.""" + prev_elt = self.get_enclosing_block() + if not self.stack or self.stack[-1] is not elt: # prevent double-pushing + self.stack.append(elt) + return prev_elt def pop_to(self, elt: Optional[BaseBlock]) -> None: - self.stack.pop() - assert self.get_enclosing_block() is elt + while (elt is None and self.stack) or (elt is not None and self.stack[-1] is not elt): + self.stack.pop() def get_enclosing_block(self) -> Optional[BaseBlock]: if not self.stack: diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index b163c3a39..48afc6409 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -181,14 +181,7 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) -> return arg_type()._bind(InitParamBinding(self, typed_arg_value)) - if builder.get_enclosing_block() is not self: - # test needed to make sure we don't double-push in nested super().__init__ calls - # create wrapper ConstraintExpr in new object scope - builder_prev = (True, builder.get_enclosing_block()) - builder.push_element(self) - else: - builder_prev = (False, None) # dummy value for try/finally - + builder_prev = builder.push_element(self) try: # rebuild args and kwargs by traversing the args list new_args: List[Any] = [] @@ -228,8 +221,7 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) -> orig_init(self, *new_args, **new_kwargs) finally: - if builder_prev[0]: - builder.pop_to(builder_prev[1]) + builder.pop_to(builder_prev) new_cls.__init__ = functools.update_wrapper(wrapped_init, orig_init) diff --git a/edg/core/Link.py b/edg/core/Link.py index 7e54ffbfa..d16223c5c 100644 --- a/edg/core/Link.py +++ b/edg/core/Link.py @@ -20,19 +20,11 @@ def __new__(cls, *args: Any, **kwargs: Any) -> Any: if '__init__' in new_cls.__dict__: orig_init = new_cls.__dict__['__init__'] def wrapped_init(self, *args, **kwargs) -> None: - if builder.get_enclosing_block() is not self: - # test needed to make sure we don't double-push in nested super().__init__ calls - # create wrapper ConstraintExpr in new object scope - builder_prev = (True, builder.get_enclosing_block()) - builder.push_element(self) - else: - builder_prev = (False, None) # dummy value for try/finally - + builder_prev = builder.push_element(self) try: orig_init(self, *args, **kwargs) finally: - if builder_prev[0]: - builder.pop_to(builder_prev[1]) + builder.pop_to(builder_prev) new_cls.__init__ = functools.update_wrapper(wrapped_init, orig_init) diff --git a/edg/core/Ports.py b/edg/core/Ports.py index 3c487b3d7..3cc74a5b8 100644 --- a/edg/core/Ports.py +++ b/edg/core/Ports.py @@ -14,6 +14,7 @@ if TYPE_CHECKING: from .Blocks import BaseBlock + from .Link import Link from .PortBlocks import PortBridge, PortAdapter From bb827539e260678483e0cc26eb332c1e76450673 Mon Sep 17 00:00:00 2001 From: Richard Lin Date: Wed, 12 Nov 2025 00:28:23 -0800 Subject: [PATCH 10/10] fix --- edg/core/ConstraintExpr.py | 2 +- edg/core/HierarchyBlock.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/edg/core/ConstraintExpr.py b/edg/core/ConstraintExpr.py index 09b8fecdf..0a0c75e41 100644 --- a/edg/core/ConstraintExpr.py +++ b/edg/core/ConstraintExpr.py @@ -81,7 +81,7 @@ def _new_bind(cls: Type[SelfType], binding: Binding) -> SelfType: def _bind(self: SelfType, binding: Binding) -> SelfType: """Returns a clone of this object with the specified binding. This object must be unbound.""" assert not self._is_bound() - assert builder.get_enclosing_block() is self._context, f"can't clone in original context {self._context} to different new context {builder.get_curr_context()}" + assert builder.get_enclosing_block() is self._context, f"can't clone in original context {self._context} to different new context {builder.get_enclosing_block()}" if not isinstance(binding, ParamBinding): assert self.initializer is None, "Only Parameters may have initializers" clone: SelfType = type(self)(self.initializer) diff --git a/edg/core/HierarchyBlock.py b/edg/core/HierarchyBlock.py index 48afc6409..8cf539016 100644 --- a/edg/core/HierarchyBlock.py +++ b/edg/core/HierarchyBlock.py @@ -213,7 +213,7 @@ def remap_arg(arg_name: str, arg_type: Type[ConstraintExpr], arg_value: Any) -> new_kwargs[arg_name] = new_arg self._init_params[arg_name] = new_arg - # unconditioally pass through all args and kwargs + # unconditionally pass through all args and kwargs new_args.extend(args[len(new_args):]) for arg_name in kwargs: if arg_name not in new_kwargs: