Coverage for pyVHDLModel/Association.py: 93%
214 statements
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-25 10:05 +0000
« prev ^ index » next coverage.py v7.17.0, created at 2026-10-25 10:05 +0000
1# ==================================================================================================================== #
2# __ ___ _ ____ _ __ __ _ _ #
3# _ __ _ \ \ / / | | | _ \| | | \/ | ___ __| | ___| | #
4# | '_ \| | | \ \ / /| |_| | | | | | | |\/| |/ _ \ / _` |/ _ \ | #
5# | |_) | |_| |\ V / | _ | |_| | |___| | | | (_) | (_| | __/ | #
6# | .__/ \__, | \_/ |_| |_|____/|_____|_| |_|\___/ \__,_|\___|_| #
7# |_| |___/ #
8# ==================================================================================================================== #
9# Authors: #
10# Patrick Lehmann #
11# #
12# License: #
13# ==================================================================================================================== #
14# Copyright 2017-2026 Patrick Lehmann - Boetzingen, Germany #
15# Copyright 2016-2017 Patrick Lehmann - Dresden, Germany #
16# #
17# Licensed under the Apache License, Version 2.0 (the "License"); #
18# you may not use this file except in compliance with the License. #
19# You may obtain a copy of the License at #
20# #
21# http://www.apache.org/licenses/LICENSE-2.0 #
22# #
23# Unless required by applicable law or agreed to in writing, software #
24# distributed under the License is distributed on an "AS IS" BASIS, #
25# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
26# See the License for the specific language governing permissions and #
27# limitations under the License. #
28# #
29# SPDX-License-Identifier: Apache-2.0 #
30# ==================================================================================================================== #
31#
32"""
33This module contains parts of an abstract document language model for VHDL.
35Associations are used in generic maps, port maps and parameter maps.
36"""
37from __future__ import annotations
39from typing import TYPE_CHECKING, Iterable, Optional as Nullable, Union, Self
41from pyTooling.Common import getFullyQualifiedName, getTypeName
42from pyTooling.Decorators import export, readonly
43from pyTooling.MetaClasses import ExtendedType, abstractclass
45from pyVHDLModel.Base import ModelEntity
46from pyVHDLModel.Exception import AlreadyInDesignError, NotAChildError, AssociationOrderError, \
47 DuplicateGenericAssociationError, DuplicatePortAssociationError
48from pyVHDLModel.Name import Name, SimpleName
49from pyVHDLModel.Symbol import Symbol
50from pyVHDLModel.Expression import BaseExpression, QualifiedExpression, FunctionCall, TypeConversion, Literal
51if TYPE_CHECKING: 51 ↛ 52line 51 didn't jump to line 52 because the condition on line 51 was never true
52 from pyVHDLModel.Visitor import Visitor
55ExpressionUnion = Union[
56 BaseExpression,
57 QualifiedExpression,
58 FunctionCall,
59 TypeConversion,
60 # ConstantOrSymbol, TODO: ObjectSymbol
61 Literal,
62]
65@export
66@abstractclass
67class AssociationItem(ModelEntity):
68 """
69 A base-class for all association items.
71 The association item is the parent of its formal and its actual, if they are model entities: a
72 :class:`~pyVHDLModel.Symbol.Symbol` has no parent.
74 .. seealso::
76 * :class:`Generic association item <pyVHDLModel.Association.GenericAssociationItem>`
77 * :class:`Port association item <pyVHDLModel.Association.PortAssociationItem>`
78 * :class:`Parameter association item <pyVHDLModel.Association.ParameterAssociationItem>`
79 """
81 _formal: Nullable[Name | Symbol] #: Reference to the formal part, or ``None`` for a positional association.
82 _actual: ExpressionUnion #: The actual part of this association.
84 def __init__(self, formal: Nullable[Name | Symbol], actual: ExpressionUnion) -> None:
85 """
86 Initializes an association item.
88 :param formal: Reference to the formal part, or ``None`` for a positional association.
89 :param actual: The actual part of this association.
90 """
91 super().__init__()
93 self._formal = formal
94 if isinstance(formal, ModelEntity):
95 formal.Parent = self
97 self._actual = actual
98 if isinstance(actual, ModelEntity):
99 actual.Parent = self
101 @readonly
102 def Formal(self) -> Nullable[Name | Symbol]: # TODO: can also be a conversion function !!
103 """
104 Read-only property to access the formal (:attr:`_formal`).
106 :returns: The formal, or ``None`` if not set.
107 """
108 return self._formal
110 @readonly
111 def Actual(self) -> ExpressionUnion:
112 """
113 Read-only property to access the actual (:attr:`_actual`).
115 :returns: The actual.
116 """
117 return self._actual
119 def Copy(self) -> Self:
120 """
121 Create a copy of this association item.
123 The copy has no parent. Its formal and actual are copies of this item's formal and actual.
125 :returns: The new association item.
126 """
127 formal = None if self._formal is None else self._formal.Copy()
128 actual = None if self._actual is None else self._actual.Copy()
129 return self.__class__(formal, actual)
131 def Visit(self, visitor: Visitor) -> None:
132 """
133 Walk this association item, its formal and its actual with a visitor.
135 :param visitor: The visitor receiving the events.
136 """
137 visitor.VisitAssociationItem(self)
138 if self._formal is not None:
139 self._formal.Visit(visitor)
140 visitor.VisitArrow(self)
142 if self._actual is not None: 142 ↛ 145line 142 didn't jump to line 145 because the condition on line 142 was always true
143 self._actual.Visit(visitor)
145 visitor.DepartAssociationItem(self)
147 def __str__(self) -> str:
148 """
149 Formats the association item.
151 **Format:** ``formal => actual``, or ``actual`` alone when positional
153 :returns: Formatted association item.
154 """
155 if self._formal is None:
156 return str(self._actual)
157 else:
158 return f"{self._formal!s} => {self._actual!s}"
161@export
162class GenericAssociationItem(AssociationItem):
163 """
164 A base-class for all generic association items used in generic map aspects.
165 """
168@export
169class PortAssociationItem(AssociationItem):
170 """
171 A base-class for all port association items used in port map aspects.
172 """
175@export
176class ParameterAssociationItem(AssociationItem):
177 """
178 A base-class for all parameter association items used in parameter map aspects.
179 """
182@export
183class GenericMapAspectMixin(metaclass=ExtendedType, mixin=True):
184 """
185 A mixin-class for language constructs with a generic map aspect.
187 An association declares nothing: the mutators update neither a namespace nor a lookup table of the host class, so the
188 mixin expects nothing from its host.
190 .. seealso::
192 * :class:`Instantiation <pyVHDLModel.Concurrent.Instantiation>`
193 * :class:`Concurrent block statement <pyVHDLModel.Concurrent.ConcurrentBlockStatement>`
194 * :class:`Binding indication <pyVHDLModel.Configuration.BindingIndication>`
195 * :class:`Port map aspect <pyVHDLModel.Association.PortMapAspectMixin>`
196 """
198 _genericAssociationItems: list[GenericAssociationItem] #: List of all generic associations.
200 def __init__(self, genericAssociationItems: Nullable[Iterable[GenericAssociationItem]] = None) -> None:
201 """
202 Initializes a generic map aspect.
204 :param genericAssociationItems: List of all generic associations.
205 """
206 self._genericAssociationItems = genericAssociationItemList = []
207 if genericAssociationItems is not None:
208 for association in genericAssociationItems:
209 genericAssociationItemList.append(association)
210 association.Parent = self
212 @readonly
213 def GenericAssociationItems(self) -> list[GenericAssociationItem]:
214 """
215 Read-only property to access the generic associations (:attr:`_genericAssociationItems`).
217 :returns: List of generic association items.
218 """
219 return self._genericAssociationItems
221 def _VisitGenericMapAspect(self, visitor: Visitor) -> None:
222 """
223 Walk the generic map aspect, if there are generic associations, with a visitor.
225 The associations are separated by commas.
227 :param visitor: The visitor receiving the events.
228 """
229 if len(self._genericAssociationItems) == 0:
230 return
232 visitor.VisitGenericMapAspect(self)
233 for index, item in enumerate(self._genericAssociationItems):
234 if index > 0:
235 visitor.VisitComma(self)
237 item.Visit(visitor)
239 visitor.DepartGenericMapAspect(self)
241 def AddGenericAssociationItem(self, genericAssociationItem: GenericAssociationItem) -> None:
242 """
243 Add a generic association at the end of the generic map aspect.
245 The association's parent is set to this language construct.
247 :param genericAssociationItem: The generic association to add.
248 :raises ValueError: If parameter 'genericAssociationItem' is ``None``.
249 :raises TypeError: If parameter 'genericAssociationItem' is not of type
250 :class:`~pyVHDLModel.Association.GenericAssociationItem`.
251 :raises AlreadyInDesignError: If parameter 'genericAssociationItem' already has a parent.
252 :raises DuplicateGenericAssociationError: If the association is named, and another named association of the generic
253 map aspect has the same formal.
254 :raises AssociationOrderError: If the association is positional, and the generic map aspect has a named
255 association.
257 .. seealso::
259 :meth:`InsertGenericAssociationItem`
260 |rarr| Insert a generic association at a position of the generic map aspect.
261 :meth:`RemoveGenericAssociationItem`
262 |rarr| Remove a generic association from the generic map aspect.
263 """
264 self.InsertGenericAssociationItem(len(self._genericAssociationItems), genericAssociationItem)
266 def InsertGenericAssociationItem(self, index: int, genericAssociationItem: GenericAssociationItem) -> None:
267 """
268 Insert a generic association at a position of the generic map aspect.
270 The association's parent is set to this language construct.
272 A named association (``formal => actual``) can't repeat the formal of another named association of the generic map
273 aspect. Only formals given as a simple name are compared: a partial association (``a(0) => x``) or a conversion
274 function (``to_integer(a) => x``) is accepted. All positional associations precede the named associations.
276 :param index: Position, from ``0`` (first) to the number of generic associations
277 (append). A negative index counts from the end, as for
278 :meth:`list.insert`: ``-1`` inserts before the last generic association.
279 :param genericAssociationItem: The generic association to insert.
280 :raises ValueError: If parameter 'index' is ``None``.
281 :raises TypeError: If parameter 'index' is not of type :class:`int`.
282 :raises ValueError: If parameter 'index' is out of range.
283 :raises ValueError: If parameter 'genericAssociationItem' is ``None``.
284 :raises TypeError: If parameter 'genericAssociationItem' is not of type
285 :class:`~pyVHDLModel.Association.GenericAssociationItem`.
286 :raises AlreadyInDesignError: If parameter 'genericAssociationItem' already has a parent.
287 :raises DuplicateGenericAssociationError: If the association is named, and another named association of the generic
288 map aspect has the same formal.
289 :raises AssociationOrderError: If the association is positional, and a named association precedes the
290 position.
291 :raises AssociationOrderError: If the association is named, and a positional association follows the
292 position.
294 .. seealso::
296 :meth:`AddGenericAssociationItem`
297 |rarr| Add a generic association at the end of the generic map aspect.
298 :meth:`RemoveGenericAssociationItem`
299 |rarr| Remove a generic association from the generic map aspect.
300 """
301 count = len(self._genericAssociationItems)
302 if index is None:
303 raise ValueError("Parameter 'index' is None.")
304 elif not isinstance(index, int):
305 ex = TypeError("Parameter 'index' is not of type 'int'.")
306 ex.add_note(f"Got type '{getFullyQualifiedName(index)}'.")
307 raise ex
308 elif not (-count <= index <= count):
309 ex = ValueError(f"Parameter 'index' is out of range {-count}..{count}.")
310 ex.add_note(f"Got value '{index}'.")
311 raise ex
313 if genericAssociationItem is None:
314 raise ValueError("Parameter 'genericAssociationItem' is None.")
315 elif not isinstance(genericAssociationItem, GenericAssociationItem):
316 ex = TypeError("Parameter 'genericAssociationItem' is not of type 'GenericAssociationItem'.")
317 ex.add_note(f"Got type '{getFullyQualifiedName(genericAssociationItem)}'.")
318 raise ex
319 elif genericAssociationItem._parent is not None:
320 ex = AlreadyInDesignError(
321 f"{getTypeName(genericAssociationItem)} already has a parent.",
322 element=genericAssociationItem,
323 parent=genericAssociationItem._parent
324 )
325 ex.add_note(f"Remove it from its {getTypeName(genericAssociationItem._parent)} first.")
326 raise ex
328 formal = genericAssociationItem._formal
329 if isinstance(formal, SimpleName):
330 normalizedFormal = formal._normalizedIdentifier
331 for existing in self._genericAssociationItems:
332 if isinstance(existing._formal, SimpleName) and existing._formal._normalizedIdentifier == normalizedFormal:
333 ex = DuplicateGenericAssociationError(
334 f"{getTypeName(self)} already associates generic '{formal._identifier}'.",
335 identifier=formal._identifier,
336 parent=self,
337 element=genericAssociationItem,
338 existing=existing
339 )
340 ex.add_note(f"Existing association is '{existing!s}'.")
341 raise ex
343 position = index if index >= 0 else count + index
344 if formal is None:
345 for existing in self._genericAssociationItems[:position]:
346 if existing._formal is not None:
347 ex = AssociationOrderError(
348 f"Positional generic association can't follow named association '{existing!s}'.",
349 parent=self,
350 element=genericAssociationItem,
351 existing=existing
352 )
353 ex.add_note("Insert it before the first named association, or name its formal.")
354 raise ex
355 else:
356 for existing in self._genericAssociationItems[position:]:
357 if existing._formal is None:
358 ex = AssociationOrderError(
359 f"Named generic association can't precede positional association '{existing!s}'.",
360 parent=self,
361 element=genericAssociationItem,
362 existing=existing
363 )
364 ex.add_note("Insert it after the last positional association.")
365 raise ex
367 self._genericAssociationItems.insert(index, genericAssociationItem)
368 genericAssociationItem.Parent = self
370 def RemoveGenericAssociationItem(self, genericAssociationItem: GenericAssociationItem) -> None:
371 """
372 Remove a generic association from the generic map aspect.
374 The association is detached: its parent is cleared. The association itself is unchanged and can be added to another
375 language construct, or inserted at another position.
377 :param genericAssociationItem: The generic association to remove.
378 :raises ValueError: If parameter 'genericAssociationItem' is ``None``.
379 :raises TypeError: If parameter 'genericAssociationItem' is not of type
380 :class:`~pyVHDLModel.Association.GenericAssociationItem`.
381 :raises NotAChildError: If parameter 'genericAssociationItem' is not a generic association of this language
382 construct.
384 .. seealso::
386 :meth:`AddGenericAssociationItem`
387 |rarr| Add a generic association at the end of the generic map aspect.
388 :meth:`InsertGenericAssociationItem`
389 |rarr| Insert a generic association at a position of the generic map aspect.
390 """
391 if genericAssociationItem is None:
392 raise ValueError("Parameter 'genericAssociationItem' is None.")
393 elif not isinstance(genericAssociationItem, GenericAssociationItem):
394 ex = TypeError("Parameter 'genericAssociationItem' is not of type 'GenericAssociationItem'.")
395 ex.add_note(f"Got type '{getFullyQualifiedName(genericAssociationItem)}'.")
396 raise ex
398 for index, item in enumerate(self._genericAssociationItems):
399 if item is genericAssociationItem: 399 ↛ 398line 399 didn't jump to line 398 because the condition on line 399 was always true
400 break
401 else:
402 raise NotAChildError(
403 f"{getTypeName(genericAssociationItem)} is not a generic association of this {getTypeName(self)}.",
404 element=genericAssociationItem,
405 parent=self
406 )
408 del self._genericAssociationItems[index]
409 genericAssociationItem._parent = None
412@export
413class PortMapAspectMixin(metaclass=ExtendedType, mixin=True):
414 """
415 A mixin-class for language constructs with a port map aspect.
417 An association declares nothing: the mutators update neither a namespace nor a lookup table of the host class, so the
418 mixin expects nothing from its host.
420 .. seealso::
422 * :class:`Instantiation <pyVHDLModel.Concurrent.Instantiation>`
423 * :class:`Concurrent block statement <pyVHDLModel.Concurrent.ConcurrentBlockStatement>`
424 * :class:`Binding indication <pyVHDLModel.Configuration.BindingIndication>`
425 * :class:`Generic map aspect <pyVHDLModel.Association.GenericMapAspectMixin>`
426 """
428 _portAssociationItems: list[PortAssociationItem] #: List of all port associations.
430 def __init__(self, portAssociationItems: Nullable[Iterable[PortAssociationItem]] = None) -> None:
431 """
432 Initializes a port map aspect.
434 :param portAssociationItems: List of all port associations.
435 """
436 self._portAssociationItems = portAssociationItemList = []
437 if portAssociationItems is not None:
438 for association in portAssociationItems:
439 portAssociationItemList.append(association)
440 association.Parent = self
442 @readonly
443 def PortAssociationItems(self) -> list[PortAssociationItem]:
444 """
445 Read-only property to access the port associations (:attr:`_portAssociationItems`).
447 :returns: List of port association items.
448 """
449 return self._portAssociationItems
451 def _VisitPortMapAspect(self, visitor: Visitor) -> None:
452 """
453 Walk the port map aspect, if there are port associations, with a visitor.
455 The associations are separated by commas.
457 :param visitor: The visitor receiving the events.
458 """
459 if len(self._portAssociationItems) == 0:
460 return
462 visitor.VisitPortMapAspect(self)
463 for index, item in enumerate(self._portAssociationItems):
464 if index > 0:
465 visitor.VisitComma(self)
467 item.Visit(visitor)
469 visitor.DepartPortMapAspect(self)
471 def AddPortAssociationItem(self, portAssociationItem: PortAssociationItem) -> None:
472 """
473 Add a port association at the end of the port map aspect.
475 The association's parent is set to this language construct.
477 :param portAssociationItem: The port association to add.
478 :raises ValueError: If parameter 'portAssociationItem' is ``None``.
479 :raises TypeError: If parameter 'portAssociationItem' is not of type
480 :class:`~pyVHDLModel.Association.PortAssociationItem`.
481 :raises AlreadyInDesignError: If parameter 'portAssociationItem' already has a parent.
482 :raises DuplicatePortAssociationError: If the association is named, and another named association of the port map
483 aspect has the same formal.
484 :raises AssociationOrderError: If the association is positional, and the port map aspect has a named
485 association.
487 .. seealso::
489 :meth:`InsertPortAssociationItem`
490 |rarr| Insert a port association at a position of the port map aspect.
491 :meth:`RemovePortAssociationItem`
492 |rarr| Remove a port association from the port map aspect.
493 """
494 self.InsertPortAssociationItem(len(self._portAssociationItems), portAssociationItem)
496 def InsertPortAssociationItem(self, index: int, portAssociationItem: PortAssociationItem) -> None:
497 """
498 Insert a port association at a position of the port map aspect.
500 The association's parent is set to this language construct.
502 A named association (``formal => actual``) can't repeat the formal of another named association of the port map
503 aspect. Only formals given as a simple name are compared: a partial association (``a(0) => x``) or a conversion
504 function (``to_integer(a) => x``) is accepted. All positional associations precede the named associations.
506 :param index: Position, from ``0`` (first) to the number of port associations (append). A
507 negative index counts from the end, as for :meth:`list.insert`: ``-1``
508 inserts before the last port association.
509 :param portAssociationItem: The port association to insert.
510 :raises ValueError: If parameter 'index' is ``None``.
511 :raises TypeError: If parameter 'index' is not of type :class:`int`.
512 :raises ValueError: If parameter 'index' is out of range.
513 :raises ValueError: If parameter 'portAssociationItem' is ``None``.
514 :raises TypeError: If parameter 'portAssociationItem' is not of type
515 :class:`~pyVHDLModel.Association.PortAssociationItem`.
516 :raises AlreadyInDesignError: If parameter 'portAssociationItem' already has a parent.
517 :raises DuplicatePortAssociationError: If the association is named, and another named association of the port map
518 aspect has the same formal.
519 :raises AssociationOrderError: If the association is positional, and a named association precedes the
520 position.
521 :raises AssociationOrderError: If the association is named, and a positional association follows the
522 position.
524 .. seealso::
526 :meth:`AddPortAssociationItem`
527 |rarr| Add a port association at the end of the port map aspect.
528 :meth:`RemovePortAssociationItem`
529 |rarr| Remove a port association from the port map aspect.
530 """
531 count = len(self._portAssociationItems)
532 if index is None: 532 ↛ 533line 532 didn't jump to line 533 because the condition on line 532 was never true
533 raise ValueError("Parameter 'index' is None.")
534 elif not isinstance(index, int): 534 ↛ 535line 534 didn't jump to line 535 because the condition on line 534 was never true
535 ex = TypeError("Parameter 'index' is not of type 'int'.")
536 ex.add_note(f"Got type '{getFullyQualifiedName(index)}'.")
537 raise ex
538 elif not (-count <= index <= count): 538 ↛ 539line 538 didn't jump to line 539 because the condition on line 538 was never true
539 ex = ValueError(f"Parameter 'index' is out of range {-count}..{count}.")
540 ex.add_note(f"Got value '{index}'.")
541 raise ex
543 if portAssociationItem is None:
544 raise ValueError("Parameter 'portAssociationItem' is None.")
545 elif not isinstance(portAssociationItem, PortAssociationItem):
546 ex = TypeError("Parameter 'portAssociationItem' is not of type 'PortAssociationItem'.")
547 ex.add_note(f"Got type '{getFullyQualifiedName(portAssociationItem)}'.")
548 raise ex
549 elif portAssociationItem._parent is not None:
550 ex = AlreadyInDesignError(
551 f"{getTypeName(portAssociationItem)} already has a parent.",
552 element=portAssociationItem,
553 parent=portAssociationItem._parent
554 )
555 ex.add_note(f"Remove it from its {getTypeName(portAssociationItem._parent)} first.")
556 raise ex
558 formal = portAssociationItem._formal
559 if isinstance(formal, SimpleName):
560 normalizedFormal = formal._normalizedIdentifier
561 for existing in self._portAssociationItems:
562 if isinstance(existing._formal, SimpleName) and existing._formal._normalizedIdentifier == normalizedFormal:
563 ex = DuplicatePortAssociationError(
564 f"{getTypeName(self)} already associates port '{formal._identifier}'.",
565 identifier=formal._identifier,
566 parent=self,
567 element=portAssociationItem,
568 existing=existing
569 )
570 ex.add_note(f"Existing association is '{existing!s}'.")
571 raise ex
573 position = index if index >= 0 else count + index
574 if formal is None:
575 for existing in self._portAssociationItems[:position]:
576 if existing._formal is not None: 576 ↛ 575line 576 didn't jump to line 575 because the condition on line 576 was always true
577 ex = AssociationOrderError(
578 f"Positional port association can't follow named association '{existing!s}'.",
579 parent=self,
580 element=portAssociationItem,
581 existing=existing
582 )
583 ex.add_note("Insert it before the first named association, or name its formal.")
584 raise ex
585 else:
586 for existing in self._portAssociationItems[position:]:
587 if existing._formal is None:
588 ex = AssociationOrderError(
589 f"Named port association can't precede positional association '{existing!s}'.",
590 parent=self,
591 element=portAssociationItem,
592 existing=existing
593 )
594 ex.add_note("Insert it after the last positional association.")
595 raise ex
597 self._portAssociationItems.insert(index, portAssociationItem)
598 portAssociationItem.Parent = self
600 def RemovePortAssociationItem(self, portAssociationItem: PortAssociationItem) -> None:
601 """
602 Remove a port association from the port map aspect.
604 The association is detached: its parent is cleared. The association itself is unchanged and can be added to another
605 language construct, or inserted at another position.
607 :param portAssociationItem: The port association to remove.
608 :raises ValueError: If parameter 'portAssociationItem' is ``None``.
609 :raises TypeError: If parameter 'portAssociationItem' is not of type
610 :class:`~pyVHDLModel.Association.PortAssociationItem`.
611 :raises NotAChildError: If parameter 'portAssociationItem' is not a port association of this language construct.
613 .. seealso::
615 :meth:`AddPortAssociationItem`
616 |rarr| Add a port association at the end of the port map aspect.
617 :meth:`InsertPortAssociationItem`
618 |rarr| Insert a port association at a position of the port map aspect.
619 """
620 if portAssociationItem is None: 620 ↛ 621line 620 didn't jump to line 621 because the condition on line 620 was never true
621 raise ValueError("Parameter 'portAssociationItem' is None.")
622 elif not isinstance(portAssociationItem, PortAssociationItem): 622 ↛ 623line 622 didn't jump to line 623 because the condition on line 622 was never true
623 ex = TypeError("Parameter 'portAssociationItem' is not of type 'PortAssociationItem'.")
624 ex.add_note(f"Got type '{getFullyQualifiedName(portAssociationItem)}'.")
625 raise ex
627 for index, item in enumerate(self._portAssociationItems):
628 if item is portAssociationItem: 628 ↛ 627line 628 didn't jump to line 627 because the condition on line 628 was always true
629 break
630 else:
631 raise NotAChildError(
632 f"{getTypeName(portAssociationItem)} is not a port association of this {getTypeName(self)}.",
633 element=portAssociationItem,
634 parent=self
635 )
637 del self._portAssociationItems[index]
638 portAssociationItem._parent = None