|
12 | 12 | ArrayFloatLike, ArrayRangeLike, ArrayStringLike |
13 | 13 | from .Array import BaseVector, Vector |
14 | 14 | from .Binding import InitParamBinding, AssignBinding |
15 | | -from .Blocks import BaseBlock, Connection, BlockElaborationState, AbstractBlockProperty |
| 15 | +from .Blocks import BaseBlock, Connection, BlockElaborationState, AbstractBlockProperty, BaseBlockMeta |
16 | 16 | from .ConstraintExpr import BoolLike, FloatLike, IntLike, RangeLike, StringLike |
17 | 17 | from .ConstraintExpr import ConstraintExpr, BoolExpr, FloatExpr, IntExpr, RangeExpr, StringExpr |
18 | | -from .Core import Refable, non_library, ElementMeta |
| 18 | +from .Core import Refable, non_library |
19 | 19 | from .HdlUserExceptions import * |
20 | 20 | from .IdentityDict import IdentityDict |
21 | 21 | from .IdentitySet import IdentitySet |
@@ -100,7 +100,41 @@ def __iter__(self): |
100 | 100 | return iter((tuple(self.blocks), self)) |
101 | 101 |
|
102 | 102 |
|
103 | | -class BlockMeta(ElementMeta): |
| 103 | +BlockPrototypeType = TypeVar('BlockPrototypeType', bound='Block') |
| 104 | +class BlockPrototype(Generic[BlockPrototypeType]): |
| 105 | + """A block prototype, that contains a type and arguments, but without constructing the entire block |
| 106 | + and running its (potentially quite expensive) __init__. |
| 107 | +
|
| 108 | + This class is automatically created on Block instantiations by the BlockMeta metaclass __init__ hook.""" |
| 109 | + def __init__(self, tpe: Type[BlockPrototypeType], args: Tuple[Any, ...], kwargs: Dict[str, Any]) -> None: |
| 110 | + self._tpe = tpe |
| 111 | + self._args = args |
| 112 | + self._kwargs = kwargs |
| 113 | + |
| 114 | + def __repr__(self) -> str: |
| 115 | + return f"{self.__class__.__name__}({self._tpe}, args={self._args}, kwargs={self._kwargs})" |
| 116 | + |
| 117 | + def _bind(self, parent: Union[BaseBlock, Port]) -> BlockPrototypeType: |
| 118 | + """Binds the prototype into an actual Block instance.""" |
| 119 | + Block._next_bind = self._tpe |
| 120 | + block = self._tpe(*self._args, **self._kwargs) # type: ignore |
| 121 | + block._bind_in_place(parent) |
| 122 | + return block |
| 123 | + |
| 124 | + def __getattribute__(self, item: str) -> Any: |
| 125 | + if item.startswith("_"): |
| 126 | + return super().__getattribute__(item) |
| 127 | + else: |
| 128 | + raise AttributeError(f"{self.__class__.__name__} has no attributes, must bind to get a concrete instance, tried to get {item}") |
| 129 | + |
| 130 | + def __setattr__(self, key: str, value: Any) -> None: |
| 131 | + if key.startswith("_"): |
| 132 | + super().__setattr__(key, value) |
| 133 | + else: |
| 134 | + raise AttributeError(f"{self.__class__.__name__} has no attributes, must bind to get a concrete instance, tried to set {key}") |
| 135 | + |
| 136 | + |
| 137 | +class BlockMeta(BaseBlockMeta): |
104 | 138 | """This provides a hook on __init__ that replaces argument values with empty ConstraintExpr |
105 | 139 | based on the type annotation and stores the supplied argument to the __init__ (if any) in the binding. |
106 | 140 |
|
@@ -233,6 +267,23 @@ class Block(BaseBlock[edgir.HierarchyBlock], metaclass=BlockMeta): |
233 | 267 | """Part with a statically-defined subcircuit. |
234 | 268 | Relations between contained parameters may only be expressed in the given constraint language. |
235 | 269 | """ |
| 270 | + _next_bind: Optional[Type[Block]] = None # set when binding, to avoid creating a prototype |
| 271 | + |
| 272 | + def __new__(cls, *args: Any, **kwargs: Any) -> Block: |
| 273 | + if Block._next_bind is not None: |
| 274 | + assert Block._next_bind is cls |
| 275 | + Block._next_bind = None |
| 276 | + return super().__new__(cls) |
| 277 | + elif builder.get_enclosing_block() is None: # always construct if top-level |
| 278 | + return super().__new__(cls) |
| 279 | + else: |
| 280 | + return BlockPrototype(cls, args, kwargs) # type: ignore |
| 281 | + |
| 282 | + SelfType = TypeVar('SelfType', bound='BaseBlock') |
| 283 | + def _bind(self: SelfType, parent: Union[BaseBlock, Port]) -> SelfType: |
| 284 | + # for type checking only |
| 285 | + raise TypeError("_bind should be called from BlockPrototype") |
| 286 | + |
236 | 287 | def __init__(self) -> None: |
237 | 288 | super().__init__() |
238 | 289 |
|
@@ -410,14 +461,19 @@ def _def_to_proto(self) -> edgir.HierarchyBlock: |
410 | 461 | def with_mixin(self, tpe: MixinType) -> MixinType: |
411 | 462 | """Adds an interface mixin for this Block. Mainly useful for abstract blocks, e.g. IoController with HasI2s.""" |
412 | 463 | from .BlockInterfaceMixin import BlockInterfaceMixin |
413 | | - if not (isinstance(tpe, BlockInterfaceMixin) and tpe._is_mixin()): |
| 464 | + if isinstance(tpe, BlockPrototype): |
| 465 | + tpe_cls = tpe._tpe |
| 466 | + else: |
| 467 | + tpe_cls = tpe.__class__ |
| 468 | + |
| 469 | + if not (issubclass(tpe_cls, BlockInterfaceMixin) and tpe_cls._is_mixin()): |
414 | 470 | raise TypeError("param to with_mixin must be a BlockInterfaceMixin") |
415 | 471 | if isinstance(self, BlockInterfaceMixin) and self._is_mixin(): |
416 | 472 | raise BlockDefinitionError(self, "mixins can not have with_mixin") |
417 | 473 | if (self.__class__, AbstractBlockProperty) not in self._elt_properties: |
418 | 474 | raise BlockDefinitionError(self, "mixins can only be added to abstract classes") |
419 | | - if not isinstance(self, tpe._get_mixin_base()): |
420 | | - raise TypeError(f"block {self.__class__.__name__} not an instance of mixin base {tpe._get_mixin_base().__name__}") |
| 475 | + if not isinstance(self, tpe_cls._get_mixin_base()): |
| 476 | + raise TypeError(f"block {self.__class__.__name__} not an instance of mixin base {tpe_cls._get_mixin_base().__name__}") |
421 | 477 | assert self._parent is not None |
422 | 478 |
|
423 | 479 | elt = tpe._bind(self._parent) |
@@ -569,16 +625,23 @@ def Export(self, port: ExportType, tags: Iterable[PortTag]=[], *, optional: bool |
569 | 625 | def Block(self, tpe: BlockType) -> BlockType: |
570 | 626 | from .BlockInterfaceMixin import BlockInterfaceMixin |
571 | 627 | from .DesignTop import DesignTop |
572 | | - if not isinstance(tpe, Block): |
573 | | - raise TypeError(f"param to Block(...) must be Block, got {tpe} of type {type(tpe)}") |
574 | | - if isinstance(tpe, BlockInterfaceMixin) and tpe._is_mixin(): |
575 | | - raise TypeError("param to Block(...) must not be BlockInterfaceMixin") |
576 | | - if isinstance(tpe, DesignTop): |
577 | | - raise TypeError(f"param to Block(...) may not be DesignTop") |
| 628 | + |
578 | 629 | if self._elaboration_state not in \ |
579 | | - [BlockElaborationState.init, BlockElaborationState.contents, BlockElaborationState.generate]: |
| 630 | + [BlockElaborationState.init, BlockElaborationState.contents, BlockElaborationState.generate]: |
580 | 631 | raise BlockDefinitionError(self, "can only define blocks in init, contents, or generate") |
581 | 632 |
|
| 633 | + if isinstance(tpe, BlockPrototype): |
| 634 | + tpe_cls = tpe._tpe |
| 635 | + else: |
| 636 | + tpe_cls = tpe.__class__ |
| 637 | + |
| 638 | + if not issubclass(tpe_cls, Block): |
| 639 | + raise TypeError(f"param to Block(...) must be Block, got {tpe_cls}") |
| 640 | + if issubclass(tpe_cls, BlockInterfaceMixin) and tpe_cls._is_mixin(): |
| 641 | + raise TypeError("param to Block(...) must not be BlockInterfaceMixin") |
| 642 | + if issubclass(tpe_cls, DesignTop): |
| 643 | + raise TypeError(f"param to Block(...) may not be DesignTop") |
| 644 | + |
582 | 645 | elt = tpe._bind(self) |
583 | 646 | self._blocks.register(elt) |
584 | 647 |
|
|
0 commit comments