Coverage for pyVHDLModel/Namespace.py: 93%
139 statements
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-11 23:50 +0000
« prev ^ index » next coverage.py v7.16.0, created at 2026-09-11 23:50 +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.
35A helper class to implement namespaces and scopes.
36"""
37from typing import TYPE_CHECKING, TypeVar, Generic, Dict, Optional as Nullable, Any, Tuple
39from pyTooling.Common import getFullyQualifiedName
40from pyTooling.Decorators import readonly
41from pyTooling.Warning import WarningCollector
43from pyVHDLModel.Object import Obj, Signal, Constant, Variable
44from pyVHDLModel.Symbol import ComponentInstantiationSymbol, Symbol, PossibleReference
45from pyVHDLModel.Exception import DuplicateDeclarationWarning
46if TYPE_CHECKING: 46 ↛ 47line 46 didn't jump to line 47 because the condition on line 46 was never true
47 from pyVHDLModel.Type import Subtype, FullType, BaseType
49K = TypeVar("K")
50O = TypeVar("O")
53class ExtendedKeyError(KeyError):
54 """
55 A :exc:`KeyError` reporting which namespaces were searched.
57 Raised when a name cannot be resolved. Besides the key (:data:`key`), it carries every namespace
58 visited while walking outwards (:data:`searchedNamespaces`).
59 """
60 key: str #: The key that was not found.
61 searchedNamespaces: Tuple["Namespace", ...] #: The namespaces that were searched for the key.
63 def __init__(self, key: str, searchedNamespaces: Tuple["Namespace", ...], message: str) -> None:
64 """
65 Initializes an extended key error.
67 :param key: The key that was not found.
68 :param searchedNamespaces: The namespaces that were searched for the key.
69 :param message: The error message.
70 """
71 super().__init__(message)
73 self.key = key
74 self.searchedNamespaces = searchedNamespaces
77class Namespace(Generic[K, O]):
78 """
79 Represents a namespace: the declared items visible in one declarative region.
81 Namespaces nest, so a lookup that misses locally continues in the parent namespace
82 (:data:`ParentNamespace`). That is what makes an entity's ports visible inside its architecture,
83 and lets a process variable hide an outer signal.
85 .. seealso::
87 * :class:`Concurrent declaration region <pyVHDLModel.Regions.ConcurrentDeclarationRegionMixin>`
88 * :class:`Sequential declaration region <pyVHDLModel.Regions.SequentialDeclarationRegionMixin>`
89 """
90 _name: str #: The namespace's name.
91 _parentNamespace: "Namespace" #: Reference to the enclosing namespace, ``None`` if outermost.
92 _subNamespaces: Dict[str, "Namespace"] #: Dictionary of all nested namespaces, indexed by name.
93 _elements: Dict[K, O] #: All elements declared in this namespace, indexed by name.
94 _sharesRegionWithParent: bool #: ``True`` if the parent namespace is the same declarative region.
96 def __init__(
97 self,
98 name: str,
99 parentNamespace: Nullable["Namespace"] = None,
100 sharesRegionWithParent: bool = False
101 ) -> None:
102 """
103 Initializes a namespace.
105 :param name: The namespace's name.
106 :param parentNamespace: Reference to the enclosing namespace, or ``None`` for the outermost one.
107 """
108 self._name = name
109 self._parentNamespace = parentNamespace
110 self._subNamespaces = {}
111 self._elements = {}
112 self._sharesRegionWithParent = sharesRegionWithParent
114 @readonly
115 def Name(self) -> str:
116 """
117 Read-only property to access the name (:attr:`_name`).
119 :returns: The name.
120 """
121 return self._name
123 @property
124 def ParentNamespace(self) -> 'Namespace':
125 """
126 Property to access the parent namespace (:attr:`_parentNamespace`).
128 :returns: The parent namespace.
129 """
130 return self._parentNamespace
132 @ParentNamespace.setter
133 def ParentNamespace(self, value: 'Namespace') -> None:
134 self._parentNamespace = value
135 value._subNamespaces[self._name] = self
137 @readonly
138 def SharesRegionWithParent(self) -> bool:
139 """
140 Read-only property to access whether this namespace continues its parent's declarative region
141 (:attr:`_sharesRegionWithParent`).
143 .. hint::
145 An entity and its architecture form one VHDL declarative region, as do a package and its body,
146 even though each owns a namespace. A duplicate declaration is reported across such a link, while
147 a genuinely nested region - a process, a block - hides instead.
149 :returns: ``True`` if the parent namespace is the same declarative region.
150 """
151 return self._sharesRegionWithParent
153 @readonly
154 def SubNamespaces(self) -> Dict[str, 'Namespace']:
155 """
156 Read-only property to access the sub namespaces (:attr:`_subNamespaces`).
158 :returns: Dictionary of sub namespaces.
159 """
160 return self._subNamespaces
162 def AddElement(self, normalizedIdentifier: K, element: O, overloadable: bool = False) -> None:
163 """
164 Add a declared item to this namespace, reporting a duplicate declaration.
166 VHDL rejects two declarations sharing an identifier in one declarative region. A region can span
167 more than one namespace - an entity and its architecture form one, as do a package and its body -
168 so enclosing namespaces are searched too, but only while they share this one's region.
170 Overloadable declarations are exempt: several subprograms may share a name as long as their
171 signatures differ. Signatures are not compared yet, so two subprograms sharing a name are always
172 accepted (see the overload-resolution finding).
174 :param normalizedIdentifier: The normalized (lower case) identifier being declared.
175 :param element: The declared item.
176 :param overloadable: ``True`` if this declaration may legally share its name.
177 """
178 from pyVHDLModel.Subprogram import Function, Procedure
180 namespace = self
181 while namespace is not None:
182 existing = namespace._elements.get(normalizedIdentifier)
183 # `existing is element` means the region is being re-indexed, not that the name is declared twice.
184 isReindex = existing is element
185 isOverload = overloadable and isinstance(existing, (Function, Procedure))
186 if existing is not None and not isReindex and not isOverload:
187 WarningCollector.Raise(DuplicateDeclarationWarning(
188 f"Identifier '{normalizedIdentifier}' is already used for a declaration in '{namespace._name}'."
189 ))
190 break
192 namespace = namespace._parentNamespace if namespace._sharesRegionWithParent else None
194 self._elements[normalizedIdentifier] = element
196 def Elements(self) -> Dict[K, O]:
197 return self._elements
199 def FindComponent(self, componentSymbol: ComponentInstantiationSymbol) -> 'Component':
200 from pyVHDLModel.DesignUnit import Component
202 try:
203 element = self._elements[componentSymbol._name._normalizedIdentifier]
204 if isinstance(element, Component):
205 return element
206 else:
207 ex = TypeError(f"Found element '{componentSymbol._name._identifier}', but it is not a component.")
208 ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.")
209 raise ex
210 except KeyError:
211 key = componentSymbol._name._identifier
213 if (parentNamespace := self._parentNamespace) is None:
214 raise ExtendedKeyError(key, (self, ), f"Component '{key}' not found in '{self._name}'.")
216 try:
217 return parentNamespace.FindComponent(componentSymbol)
218 except ExtendedKeyError as ex:
219 searchedNamespaces = (self, *ex.searchedNamespaces)
220 raise ExtendedKeyError(key, searchedNamespaces, f"Component '{key}' not found in: {', '.join(ns._name for ns in searchedNamespaces)}.") from ex
222 def FindSubtype(self, subtypeSymbol: Symbol) -> 'BaseType':
223 from pyVHDLModel.Type import Subtype, FullType
225 try:
226 element = self._elements[subtypeSymbol._name._normalizedIdentifier]
227 if isinstance(element, Subtype):
228 if PossibleReference.Subtype in subtypeSymbol._possibleReferences:
229 return element
230 else:
231 ex = TypeError(f"Found subtype '{subtypeSymbol._name._identifier}', but it was not expected.")
232 ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.")
233 ex.add_note(f"Expected one of: {subtypeSymbol._possibleReferences}.")
234 raise ex
235 elif isinstance(element, FullType):
236 if PossibleReference.Type in subtypeSymbol._possibleReferences:
237 return element
238 else:
239 ex = TypeError(f"Found type '{subtypeSymbol._name._identifier}', but it was not expected.")
240 ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.")
241 ex.add_note(f"Expected one of: {subtypeSymbol._possibleReferences}.")
242 raise ex
243 else:
244 ex = TypeError(f"Found element '{subtypeSymbol._name._identifier}', but it is not a type or subtype.")
245 ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.")
246 raise ex
247 except KeyError:
248 key = subtypeSymbol._name._identifier
250 if (parentNamespace := self._parentNamespace) is None:
251 raise ExtendedKeyError(key, (self, ), f"Subtype '{key}' not found in '{self._name}'.")
253 try:
254 return parentNamespace.FindSubtype(subtypeSymbol)
255 except ExtendedKeyError as ex:
256 searchedNamespaces = (self, *ex.searchedNamespaces)
257 raise ExtendedKeyError(key, searchedNamespaces, f"Subtype '{key}' not found in: {', '.join(ns._name for ns in searchedNamespaces)}.") from ex
259 def FindObject(self, objectSymbol: Symbol) -> Obj:
260 try:
261 element = self._elements[objectSymbol._name._normalizedIdentifier]
262 if isinstance(element, Signal):
263 if PossibleReference.Signal in objectSymbol._possibleReferences:
264 return element
265 elif PossibleReference.SignalAttribute in objectSymbol._possibleReferences:
266 return element
267 else:
268 ex = TypeError(f"Found signal '{objectSymbol._name._identifier}', but it was not expected.")
269 ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.")
270 ex.add_note(f"Expected one of: {objectSymbol._possibleReferences}.")
271 raise ex
272 elif isinstance(element, Constant):
273 if PossibleReference.Constant in objectSymbol._possibleReferences: 273 ↛ 276line 273 didn't jump to line 276 because the condition on line 273 was always true
274 return element
275 else:
276 ex = TypeError(f"Found constant '{objectSymbol._name._identifier}', but it was not expected.")
277 ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.")
278 ex.add_note(f"Expected one of: {objectSymbol._possibleReferences}.")
279 raise ex
280 elif isinstance(element, Variable):
281 if PossibleReference.Variable in objectSymbol._possibleReferences: 281 ↛ 284line 281 didn't jump to line 284 because the condition on line 281 was always true
282 return element
283 else:
284 ex = TypeError(f"Found variable '{objectSymbol._name._identifier}', but it was not expected.")
285 ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.")
286 ex.add_note(f"Expected one of: {objectSymbol._possibleReferences}.")
287 raise ex
288 else:
289 ex = TypeError(f"Found element '{objectSymbol._name._identifier}', but it is not an object.")
290 ex.add_note(f"Got type '{getFullyQualifiedName(element)}'.")
291 raise ex
292 except KeyError:
293 key = objectSymbol._name._identifier
295 if (parentNamespace := self._parentNamespace) is None:
296 raise ExtendedKeyError(key, (self, ), f"Object '{key}' not found in '{self._name}'.")
298 try:
299 return parentNamespace.FindObject(objectSymbol)
300 except ExtendedKeyError as ex:
301 searchedNamespaces = (self, *ex.searchedNamespaces)
302 raise ExtendedKeyError(key, searchedNamespaces, f"Object '{key}' not found in: {', '.join(ns._name for ns in searchedNamespaces)}.") from ex