Coverage for pyVHDLModel/Namespace.py: 93%

139 statements  

« 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. 

34 

35A helper class to implement namespaces and scopes. 

36""" 

37from typing import TYPE_CHECKING, TypeVar, Generic, Dict, Optional as Nullable, Any, Tuple 

38 

39from pyTooling.Common import getFullyQualifiedName 

40from pyTooling.Decorators import readonly 

41from pyTooling.Warning import WarningCollector 

42 

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 

48 

49K = TypeVar("K") 

50O = TypeVar("O") 

51 

52 

53class ExtendedKeyError(KeyError): 

54 """ 

55 A :exc:`KeyError` reporting which namespaces were searched. 

56 

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. 

62 

63 def __init__(self, key: str, searchedNamespaces: Tuple["Namespace", ...], message: str) -> None: 

64 """ 

65 Initializes an extended key error. 

66 

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) 

72 

73 self.key = key 

74 self.searchedNamespaces = searchedNamespaces 

75 

76 

77class Namespace(Generic[K, O]): 

78 """ 

79 Represents a namespace: the declared items visible in one declarative region. 

80 

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. 

84 

85 .. seealso:: 

86 

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. 

95 

96 def __init__( 

97 self, 

98 name: str, 

99 parentNamespace: Nullable["Namespace"] = None, 

100 sharesRegionWithParent: bool = False 

101 ) -> None: 

102 """ 

103 Initializes a namespace. 

104 

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 

113 

114 @readonly 

115 def Name(self) -> str: 

116 """ 

117 Read-only property to access the name (:attr:`_name`). 

118 

119 :returns: The name. 

120 """ 

121 return self._name 

122 

123 @property 

124 def ParentNamespace(self) -> 'Namespace': 

125 """ 

126 Property to access the parent namespace (:attr:`_parentNamespace`). 

127 

128 :returns: The parent namespace. 

129 """ 

130 return self._parentNamespace 

131 

132 @ParentNamespace.setter 

133 def ParentNamespace(self, value: 'Namespace') -> None: 

134 self._parentNamespace = value 

135 value._subNamespaces[self._name] = self 

136 

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`). 

142 

143 .. hint:: 

144 

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. 

148 

149 :returns: ``True`` if the parent namespace is the same declarative region. 

150 """ 

151 return self._sharesRegionWithParent 

152 

153 @readonly 

154 def SubNamespaces(self) -> Dict[str, 'Namespace']: 

155 """ 

156 Read-only property to access the sub namespaces (:attr:`_subNamespaces`). 

157 

158 :returns: Dictionary of sub namespaces. 

159 """ 

160 return self._subNamespaces 

161 

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. 

165 

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. 

169 

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). 

173 

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 

179 

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 

191 

192 namespace = namespace._parentNamespace if namespace._sharesRegionWithParent else None 

193 

194 self._elements[normalizedIdentifier] = element 

195 

196 def Elements(self) -> Dict[K, O]: 

197 return self._elements 

198 

199 def FindComponent(self, componentSymbol: ComponentInstantiationSymbol) -> 'Component': 

200 from pyVHDLModel.DesignUnit import Component 

201 

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 

212 

213 if (parentNamespace := self._parentNamespace) is None: 

214 raise ExtendedKeyError(key, (self, ), f"Component '{key}' not found in '{self._name}'.") 

215 

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 

221 

222 def FindSubtype(self, subtypeSymbol: Symbol) -> 'BaseType': 

223 from pyVHDLModel.Type import Subtype, FullType 

224 

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 

249 

250 if (parentNamespace := self._parentNamespace) is None: 

251 raise ExtendedKeyError(key, (self, ), f"Subtype '{key}' not found in '{self._name}'.") 

252 

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 

258 

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 

294 

295 if (parentNamespace := self._parentNamespace) is None: 

296 raise ExtendedKeyError(key, (self, ), f"Object '{key}' not found in '{self._name}'.") 

297 

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