Reference Guide  2.5.0
symbol_table.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2017-2024, Science and Technology Facilities Council.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 #
17 # * Neither the name of the copyright holder nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 # -----------------------------------------------------------------------------
34 # Authors R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab
35 # I. Kavcic, Met Office
36 # J. Henrichs, Bureau of Meteorology
37 # Modified A. B. G. Chalk, STFC Daresbury Lab
38 # -----------------------------------------------------------------------------
39 
40 ''' This module contains the SymbolTable implementation. '''
41 
42 # pylint: disable=too-many-lines
43 
44 from collections import OrderedDict
45 from collections.abc import Iterable
46 import inspect
47 import copy
48 
49 from psyclone.configuration import Config
50 from psyclone.errors import InternalError
51 from psyclone.psyir.symbols import (
52  DataSymbol, ContainerSymbol, DataTypeSymbol, GenericInterfaceSymbol,
53  ImportInterface, RoutineSymbol, Symbol, SymbolError, UnresolvedInterface)
54 from psyclone.psyir.symbols.intrinsic_symbol import IntrinsicSymbol
55 from psyclone.psyir.symbols.typed_symbol import TypedSymbol
56 
57 
58 class SymbolTable():
59  # pylint: disable=too-many-public-methods
60  '''Encapsulates the symbol table and provides methods to add new
61  symbols and look up existing symbols. Nested scopes are supported
62  and, by default, the add and lookup methods take any ancestor
63  symbol tables into consideration (ones attached to nodes that are
64  ancestors of the node that this symbol table is attached to). If the
65  default visibility is not specified then it defaults to
66  Symbol.Visbility.PUBLIC.
67 
68  :param node: reference to the Schedule or Container to which this \
69  symbol table belongs.
70  :type node: :py:class:`psyclone.psyir.nodes.Schedule`, \
71  :py:class:`psyclone.psyir.nodes.Container` or NoneType
72  :param default_visibility: optional default visibility value for this \
73  symbol table, if not provided it defaults to PUBLIC visibility.
74  :type default_visibillity: \
75  :py:class:`psyclone.psyir.symbols.Symbol.Visibility`
76 
77  :raises TypeError: if node argument is not a Schedule or a Container.
78 
79  '''
80  def __init__(self, node=None, default_visibility=Symbol.Visibility.PUBLIC):
81  # Dict of Symbol objects with the symbol names as keys. Make
82  # this ordered so that different versions of Python always
83  # produce code with declarations in the same order.
84  self._symbols_symbols = OrderedDict()
85  # Ordered list of the arguments.
86  self._argument_list_argument_list = []
87  # Dict of tags. Some symbols can be identified with a tag.
88  self._tags_tags = {}
89 
90  # Reference to the node to which this symbol table belongs.
91  self._node_node = None
92  if node:
93  self.attachattach(node)
94 
95  # The default visibility of symbols in this symbol table. The
96  # setter does validation of the supplied quantity.
97  self._default_visibility_default_visibility = None
98  self.default_visibilitydefault_visibilitydefault_visibilitydefault_visibility = default_visibility
99 
100  @property
101  def default_visibility(self):
102  '''
103  :returns: the default visibility of symbols in this table.
104  :rtype: :py:class:`psyclone.psyir.symbols.Symbol.Visibility`
105  '''
106  return self._default_visibility_default_visibility
107 
108  @default_visibility.setter
109  def default_visibility(self, vis):
110  '''
111  Sets the default visibility of symbols in this table.
112 
113  :param vis: the default visibility.
114  :type vis: :py:class:`psyclone.psyir.symbols.Symbol.Visibility`
115 
116  :raises TypeError: if the supplied value is of the wrong type.
117 
118  '''
119  if not isinstance(vis, Symbol.Visibility):
120  raise TypeError(
121  f"Default visibility must be an instance of psyir.symbols."
122  f"Symbol.Visibility but got '{type(vis).__name__}'")
123  self._default_visibility_default_visibility = vis
124 
125  @property
126  def node(self):
127  '''
128  :returns: the Schedule or Container to which this symbol table belongs.
129  :rtype: :py:class:`psyclone.psyir.nodes.Schedule`, \
130  :py:class:`psyclone.psyir.nodes.Container` or NoneType
131 
132  '''
133  return self._node_node
134 
135  def is_empty(self):
136  '''
137  :returns: True if the symbol table is empty, and False otherwise.
138  :rtype: bool
139 
140  '''
141  return len(self._symbols_symbols) == 0
142 
143  def parent_symbol_table(self, scope_limit=None):
144  '''If this symbol table is enclosed in another scope, return the
145  symbol table of the next outer scope. Otherwise return None.
146 
147  :param scope_limit: optional Node which limits the symbol \
148  search space to the symbol tables of the nodes within the \
149  given scope. If it is None (the default), the whole \
150  scope (all symbol tables in ancestor nodes) is searched \
151  otherwise ancestors of the scope_limit node are not \
152  searched.
153  :type scope_limit: :py:class:`psyclone.psyir.nodes.Node` or \
154  `NoneType`
155 
156  :returns: the 'parent' SymbolTable of the current SymbolTable (i.e.
157  the one that encloses this one in the PSyIR hierarchy).
158  :rtype: :py:class:`psyclone.psyir.symbols.SymbolTable` or NoneType
159 
160  '''
161  # Validate the supplied scope_limit
162  if scope_limit is not None:
163  # pylint: disable=import-outside-toplevel
164  from psyclone.psyir.nodes import Node
165  if not isinstance(scope_limit, Node):
166  raise TypeError(
167  f"The scope_limit argument '{scope_limit}', is not of "
168  f"type `Node`.")
169 
170  # We use the Node with which this table is associated in order to
171  # move up the Node hierarchy
172  if self.nodenode:
173  search_next = self.nodenode
174  while search_next is not scope_limit and search_next.parent:
175  search_next = search_next.parent
176  if hasattr(search_next, 'symbol_table'):
177  return search_next.symbol_table
178  return None
179 
180  def get_symbols(self, scope_limit=None):
181  '''Return symbols from this symbol table and all symbol tables
182  associated with ancestors of the node that this symbol table
183  is attached to. If there are name duplicates we only return the
184  one from the closest ancestor including self. It accepts an
185  optional scope_limit argument.
186 
187  :param scope_limit: optional Node which limits the symbol \
188  search space to the symbol tables of the nodes within the \
189  given scope. If it is None (the default), the whole \
190  scope (all symbol tables in ancestor nodes) is searched \
191  otherwise ancestors of the scope_limit node are not \
192  searched.
193  :type scope_limit: Optional[:py:class:`psyclone.psyir.nodes.Node`]
194 
195  :returns: ordered dictionary of symbols indexed by symbol name.
196  :rtype: OrderedDict[str] = :py:class:`psyclone.psyir.symbols.Symbol`
197 
198  '''
199  all_symbols = OrderedDict()
200  current = self
201  while current:
202  for symbol_name, symbol in current.symbols_dict.items():
203  if symbol_name not in all_symbols:
204  all_symbols[symbol_name] = symbol
205  current = current.parent_symbol_table(scope_limit)
206  return all_symbols
207 
208  def get_tags(self, scope_limit=None):
209  '''Return tags from this symbol table and all symbol tables associated
210  with ancestors of the node that this symbol table is attached
211  to. If there are tag duplicates we only return the one from the closest
212  ancestor including self. It accepts an optional scope_limit argument.
213 
214  :param scope_limit: optional Node which limits the symbol \
215  search space to the symbol tables of the nodes within the \
216  given scope. If it is None (the default), the whole \
217  scope (all symbol tables in ancestor nodes) is searched \
218  otherwise ancestors of the scope_limit node are not \
219  searched.
220  :type scope_limit: :py:class:`psyclone.psyir.nodes.Node` or \
221  `NoneType`
222 
223  :returns: ordered dictionary of symbols indexed by tag.
224  :rtype: OrderedDict[str] = :py:class:`psyclone.psyir.symbols.Symbol`
225 
226  '''
227  all_tags = OrderedDict()
228  current = self
229  while current:
230  for tag, symbol in current.tags_dict.items():
231  if tag not in all_tags:
232  all_tags[tag] = symbol
233  current = current.parent_symbol_table(scope_limit)
234  return all_tags
235 
236  def shallow_copy(self):
237  '''Create a copy of the symbol table with new instances of the
238  top-level data structures but keeping the same existing symbol
239  objects. Symbols added to the new symbol table will not be added
240  in the original but the existing objects are still the same.
241 
242  :returns: a shallow copy of this symbol table.
243  :rtype: :py:class:`psyclone.psyir.symbols.SymbolTable`
244 
245  '''
246  # pylint: disable=protected-access
247  new_st = type(self)()
248  new_st._symbols = copy.copy(self._symbols_symbols)
249  new_st._argument_list = copy.copy(self._argument_list_argument_list)
250  new_st._tags = copy.copy(self._tags_tags)
251  new_st._node = self.nodenode
252  new_st._default_visibility = self.default_visibilitydefault_visibilitydefault_visibilitydefault_visibility
253  return new_st
254 
255  def deep_copy(self):
256  '''Create a copy of the symbol table with new instances of the
257  top-level data structures and also new instances of the symbols
258  contained in these data structures. Modifying a symbol attribute
259  will not affect the equivalent named symbol in the original symbol
260  table.
261 
262  The only attribute not copied is the _node reference to the scope,
263  since that scope can only have one symbol table associated to it.
264 
265  :returns: a deep copy of this symbol table.
266  :rtype: :py:class:`psyclone.psyir.symbols.SymbolTable`
267 
268  '''
269  # pylint: disable=protected-access
270  new_st = type(self)()
271 
272  # Make a copy of each symbol in the symbol table
273  for symbol in self.symbolssymbols:
274  new_st.add(symbol.copy())
275 
276  # Prepare the new argument list
277  new_arguments = []
278  for name in [arg.name for arg in self.argument_listargument_list]:
279  new_arguments.append(new_st.lookup(name))
280  new_st.specify_argument_list(new_arguments)
281 
282  # Prepare the new tag dict
283  for tag, symbol in self._tags_tags.items():
284  new_st._tags[tag] = new_st.lookup(symbol.name)
285 
286  # Fix the container links for imported symbols
287  for symbol in new_st.imported_symbols:
288  name = symbol.interface.container_symbol.name
289  orig_name = symbol.interface.orig_name
290  new_container = new_st.lookup(name)
291  symbol.interface = ImportInterface(new_container,
292  orig_name=orig_name)
293 
294  # Fix the references to RoutineSymbols within any
295  # GenericInterfaceSymbols.
296  for symbol in new_st.symbols:
297  if not isinstance(symbol, GenericInterfaceSymbol):
298  continue
299  new_routines = []
300  for routine in symbol.routines:
301  new_routines.append((new_st.lookup(routine.symbol.name),
302  routine.from_container))
303  symbol.routines = new_routines
304 
305  # Set the default visibility
306  new_st._default_visibility = self.default_visibilitydefault_visibilitydefault_visibilitydefault_visibility
307 
308  return new_st
309 
310  @staticmethod
311  def _normalize(key):
312  '''Normalises the symboltable key strings.
313 
314  :param str key: an input key.
315 
316  :returns: the normalized key.
317  :rtype: str
318 
319  '''
320  # The symbol table is currently case insensitive
321  new_key = key.lower()
322  return new_key
323 
324  @classmethod
325  def _has_same_name(cls, first, second):
326  ''' Compare if two symbols have the same normalized name. For
327  convenience it accepts symbols and strings.
328 
329  :param first: first item of the comparison.
330  :type first: str | :py:class:`psyclone.psyir.symbols.Symbol`
331  :param second: second item of the comparison.
332  :type second: str | :py:class:`psyclone.psyir.symbols.Symbol`
333 
334  :returns: whether the two symbols names are the same.
335  :rtype: bool
336 
337  '''
338  string1 = first if isinstance(first, str) else first.name
339  string2 = second if isinstance(second, str) else second.name
340  return cls._normalize_normalize(string1) == cls._normalize_normalize(string2)
341 
342  def new_symbol(self, root_name=None, tag=None, shadowing=False,
343  symbol_type=None, allow_renaming=True, **symbol_init_args):
344  ''' Create a new symbol. Optional root_name and shadowing
345  arguments can be given to choose the name following the rules of
346  next_available_name(). An optional tag can also be given.
347  By default it creates a generic symbol but a symbol_type argument
348  and any additional initialization keyword arguments of this
349  symbol_type can be provided to refine the created Symbol.
350 
351  :param root_name: optional name to use when creating a
352  new symbol name. This will be appended
353  with an integer if the name
354  clashes with an existing symbol name.
355  :type root_name: Optional[str]
356  :param str tag: optional tag identifier for the new symbol.
357  :param bool shadowing: optional logical flag indicating whether the
358  name can be overlapping with a symbol in any of the ancestors
359  symbol tables. Defaults to False.
360  :param symbol_type: class type of the new symbol.
361  :type symbol_type: type object of class (or subclasses) of
362  :py:class:`psyclone.psyir.symbols.Symbol`
363  :param bool allow_renaming: whether to allow the newly created
364  symbol to be renamed from root_name.
365  Defaults to True.
366  :param symbol_init_args: arguments to create a new symbol.
367  :type symbol_init_args: unwrapped dict[str, Any]
368 
369  :raises TypeError: if the type_symbol argument is not the type of a
370  Symbol object class or one of its subclasses.
371  :raises SymbolError: if the the symbol needs to be created but would
372  need to be renamed to be created and
373  allow_renaming is False.
374 
375  '''
376  # Only type-check symbol_type, the other arguments are just passed down
377  # and type checked inside each relevant method.
378  if symbol_type is not None:
379  if not (isinstance(symbol_type, type) and
380  Symbol in inspect.getmro(symbol_type)):
381  raise TypeError(
382  f"The symbol_type parameter should be a type class of "
383  f"Symbol or one of its sub-classes but found "
384  f"'{type(symbol_type).__name__}' instead.")
385  else:
386  symbol_type = Symbol
387 
388  # If no visibility parameter has been provided use this symbol table's
389  # default visibility
390  if "visibility" not in symbol_init_args:
391  symbol_init_args["visibility"] = self.default_visibilitydefault_visibilitydefault_visibilitydefault_visibility
392 
393  available_name = self.next_available_namenext_available_name(root_name, shadowing)
394  # TODO #2546 - we should disallow renaming of UnsupportedFortranTypes
395  if (not allow_renaming and available_name != root_name):
396  raise SymbolError(
397  f"Cannot create symbol '{root_name}' as a symbol with that "
398  f"name already exists in this scope, and renaming is "
399  f"disallowed."
400  )
401  if "interface" in symbol_init_args and available_name != root_name:
402  interface = symbol_init_args["interface"]
403  if isinstance(interface, ImportInterface):
404  symbol_init_args["interface"] = ImportInterface(
405  symbol_init_args["interface"].container_symbol,
406  root_name
407  )
408  symbol = symbol_type(available_name, **symbol_init_args)
409  self.addadd(symbol, tag)
410  return symbol
411 
412  def find_or_create(self, name, **new_symbol_args):
413  ''' Lookup a symbol by its name, if it doesn't exist create a new
414  symbol with the given properties.
415 
416  :param str name: name of the symbol to lookup or create.
417  :param new_symbol_args: arguments to create a new symbol.
418  :type new_symbol_args: unwrapped Dict[str, object]
419 
420  :raises SymbolError: if the symbol already exists but the type_symbol \
421  argument does not match the type of the symbol \
422  found.
423 
424  '''
425  try:
426  symbol = self.lookuplookup(name)
427  # Check that the symbol found matches the requested description
428  if 'symbol_type' in new_symbol_args:
429  symbol_type = new_symbol_args['symbol_type']
430  if not isinstance(symbol, new_symbol_args['symbol_type']):
431  raise SymbolError(
432  f"Expected symbol with name '{name}' to be of type "
433  f"'{symbol_type.__name__}' but found type "
434  f"'{type(symbol).__name__}'.")
435  # TODO #1057: If the symbol is found and some unmatching arguments
436  # were given it should also fail here.
437  return symbol
438  except KeyError:
439  return self.new_symbolnew_symbol(name, **new_symbol_args)
440 
441  def find_or_create_tag(self, tag, root_name=None,
442  **new_symbol_args):
443  ''' Lookup a tag, if it doesn't exist create a new symbol with the
444  given tag. By default it creates a generic Symbol with the tag as the
445  root of the symbol name. Optionally, a different root_name or any of
446  the arguments available in the new_symbol() method can be given to
447  refine the name and the type of the created Symbol.
448 
449  :param str tag: tag identifier.
450  :param str root_name: optional name of the new symbol if it needs \
451  to be created. Otherwise it is ignored.
452  :param bool shadowing: optional logical flag indicating whether the
453  name can be overlapping with a symbol in any of the ancestors
454  symbol tables. Defaults to False.
455  :param symbol_type: class type of the new symbol.
456  :type symbol_type: type object of class (or subclasses) of
457  :py:class:`psyclone.psyir.symbols.Symbol`
458  :param bool allow_renaming: whether to allow the newly created
459  :param bool allow_renaming: whether to allow the newly created
460  symbol to be renamed from root_name.
461  Defaults to True.
462  :param new_symbol_args: arguments to create a new symbol.
463  :type new_symbol_args: unwrapped dict[str, Any]
464 
465  :returns: symbol associated with the given tag.
466  :rtype: :py:class:`psyclone.psyir.symbols.Symbol`
467 
468  :raises SymbolError: if the symbol already exists but the type_symbol \
469  argument does not match the type of the symbol \
470  found.
471 
472  '''
473  try:
474  symbol = self.lookup_with_taglookup_with_tag(tag)
475  # Check that the symbol found matches the requested description
476  if 'symbol_type' in new_symbol_args:
477  symbol_type = new_symbol_args['symbol_type']
478  if not isinstance(symbol, new_symbol_args['symbol_type']):
479  raise SymbolError(
480  f"Expected symbol with tag '{tag}' to be of type "
481  f"'{symbol_type.__name__}' but found type "
482  f"'{type(symbol).__name__}'.")
483  # TODO #1057: If the symbol is found and some unmatching arguments
484  # were given it should also fail here.
485  return symbol
486  except KeyError:
487  if not root_name:
488  root_name = tag
489  return self.new_symbolnew_symbol(root_name, tag, **new_symbol_args)
490 
491  def next_available_name(self, root_name=None, shadowing=False,
492  other_table=None):
493  '''Return a name that is not in the symbol table and therefore can
494  be used to declare a new symbol.
495  If the `root_name` argument is not supplied or if it is
496  an empty string then the name is generated internally,
497  otherwise the `root_name` is used. If required, an additional
498  integer is appended to avoid clashes.
499  If the shadowing argument is True (is False by default), the names
500  in parent symbol tables will not be considered.
501  If `other_table` is supplied, the new name is constructed so as not
502  to clash with any entries in that table.
503 
504  :param str root_name: optional name to use when creating a new \
505  symbol name. This will be appended with an integer if the name \
506  clashes with an existing symbol name.
507  :param bool shadowing: optional logical flag indicating whether the \
508  name can be overlapping with a symbol in any of the ancestors \
509  symbol tables. Defaults to False.
510  :param other_table: an optional, second symbol table to take into \
511  account when constructing a new name.
512  :type other_table: :py:class`psyclone.psyir.symbols.SymbolTable`
513 
514  :returns: the new unique symbol name.
515  :rtype: str
516 
517  :raises TypeError: if any of the arguments are of the wrong type.
518 
519  '''
520  if not isinstance(shadowing, bool):
521  raise TypeError(
522  f"Argument 'shadowing' should be of type bool"
523  f" but found '{type(shadowing).__name__}'.")
524 
525  if other_table and not isinstance(other_table, SymbolTable):
526  raise TypeError(
527  f"If supplied, argument 'other_table' should be of type "
528  f"SymbolTable but found '{type(other_table).__name__}'.")
529 
530  if shadowing:
531  symbols = self._symbols_symbols
532  else:
533  # If symbol shadowing is not permitted, the list of symbols names
534  # that can't be used includes all the symbols from all the ancestor
535  # symbol tables.
536  symbols = self.get_symbolsget_symbols()
537 
538  # Construct the set of existing names.
539  existing_names = set(symbols.keys())
540 
541  if other_table:
542  # If a second symbol table has been supplied, include its entries
543  # in the list of names to exclude.
544  other_names = set(other_table.symbols_dict.keys())
545  existing_names = existing_names.union(other_names)
546 
547  if root_name is not None:
548  if not isinstance(root_name, str):
549  raise TypeError(
550  f"Argument root_name should be of type str or NoneType "
551  f"but found '{type(root_name).__name__}'.")
552  if not root_name:
553  root_name = Config.get().psyir_root_name
554  candidate_name = root_name
555  idx = 1
556  while self._normalize_normalize(candidate_name) in existing_names:
557  candidate_name = f"{root_name}_{idx}"
558  idx += 1
559  return candidate_name
560 
561  def add(self, new_symbol, tag=None):
562  '''Add a new symbol to the symbol table if the symbol name is not
563  already in use.
564 
565  :param new_symbol: the symbol to add to the symbol table.
566  :type new_symbol: :py:class:`psyclone.psyir.symbols.Symbol`
567  :param str tag: a tag identifier for the new symbol, by default no \
568  tag is given.
569 
570  :raises InternalError: if the new_symbol argument is not a \
571  symbol.
572  :raises KeyError: if the symbol name is already in use.
573  :raises KeyError: if a tag is supplied and it is already in \
574  use.
575 
576  '''
577  if not isinstance(new_symbol, Symbol):
578  raise InternalError(f"Symbol '{new_symbol}' is not a symbol, but "
579  f"'{type(new_symbol).__name__}'.'")
580 
581  key = self._normalize_normalize(new_symbol.name)
582  if key in self._symbols_symbols:
583  raise KeyError(f"Symbol table already contains a symbol with "
584  f"name '{new_symbol.name}'.")
585 
586  if tag:
587  if tag in self.get_tagsget_tags():
588  raise KeyError(
589  f"This symbol table, or an outer scope ancestor symbol "
590  f"table, already contains the tag '{tag}' for the symbol"
591  f" '{self.lookup_with_tag(tag).name}', so it can not be "
592  f"associated with symbol '{new_symbol.name}'.")
593  self._tags_tags[tag] = new_symbol
594 
595  self._symbols_symbols[key] = new_symbol
596 
597  def check_for_clashes(self, other_table, symbols_to_skip=()):
598  '''
599  Checks the symbols in the supplied table against those in
600  this table. If there is a name clash that cannot be resolved by
601  renaming then a SymbolError is raised. Any symbols appearing
602  in `symbols_to_skip` are excluded from the checks.
603 
604  :param other_table: the table for which to check for clashes.
605  :type other_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
606  :param symbols_to_skip: an optional list of symbols to exclude from
607  the check.
608  :type symbols_to_skip: Iterable[
609  :py:class:`psyclone.psyir.symbols.Symbol`]
610 
611  :raises TypeError: if symbols_to_skip is supplied but is not an
612  instance of Iterable.
613  :raises SymbolError: if there would be an unresolvable name clash
614  when importing symbols from `other_table` into this table.
615 
616  '''
617  # pylint: disable-next=import-outside-toplevel
618  from psyclone.psyir.nodes import IntrinsicCall
619 
620  if not isinstance(symbols_to_skip, Iterable):
621  raise TypeError(
622  f"check_for_clashes: 'symbols_to_skip' must be an instance of "
623  f"Iterable but got '{type(symbols_to_skip).__name__}'")
624 
625  # Check whether there are any wildcard imports common to both tables.
626  self_imports = self.wildcard_importswildcard_imports()
627  other_imports = other_table.wildcard_imports()
628  shared_wildcard_imports = self_imports & other_imports
629  # Any wildcard imports that appear in one table but not in the other.
630  unique_wildcard_imports = self_imports ^ other_imports
631 
632  for other_sym in other_table.symbols:
633  if other_sym.name not in self or other_sym in symbols_to_skip:
634  continue
635  # We have a name clash.
636  this_sym = self.lookuplookup(other_sym.name)
637 
638  # If they are both ContainerSymbols then that's OK as they refer to
639  # the same Container.
640  if (isinstance(this_sym, ContainerSymbol) and
641  isinstance(other_sym, ContainerSymbol)):
642  continue
643 
644  # If they are both IntrinsicSymbol then that's fine.
645  if (isinstance(this_sym, IntrinsicSymbol) and
646  isinstance(other_sym, IntrinsicSymbol)):
647  continue
648 
649  if other_sym.is_import and this_sym.is_import:
650  # Both symbols are imported. That's fine as long as they have
651  # the same import interface (are imported from the same
652  # Container and refer to the same Symbol in that Container).
653  if this_sym.interface != other_sym.interface:
654  raise SymbolError(
655  f"This table has an import of '{this_sym.name}' via "
656  f"interface '{this_sym.interface}' but the supplied "
657  f"table imports it via '{other_sym.interface}'.")
658  continue
659 
660  if other_sym.is_unresolved and this_sym.is_unresolved:
661  # Both Symbols are unresolved.
662  if shared_wildcard_imports and not unique_wildcard_imports:
663  # The tables have one or more wildcard imports in common
664  # and no wildcard imports unique to one table. Therefore
665  # the two symbols represent the same memory location.
666  continue
667  if not (self_imports or other_imports):
668  # Neither table has any wildcard imports.
669  try:
670  # An unresolved symbol representing an intrinisc is OK
671  _ = IntrinsicCall.Intrinsic[this_sym.name.upper()]
672  # Take this opportunity to specialise the symbol(s).
673  if not isinstance(this_sym, IntrinsicSymbol):
674  this_sym.specialise(IntrinsicSymbol)
675  if not isinstance(other_sym, IntrinsicSymbol):
676  other_sym.specialise(IntrinsicSymbol)
677  continue
678  except KeyError:
679  pass
680  # We can't rename a symbol if we don't know its origin.
681  raise SymbolError(
682  f"A symbol named '{this_sym.name}' is present but "
683  f"unresolved in one or both tables.")
684 
685  # Can either of them be renamed?
686  try:
687  self.rename_symbolrename_symbol(this_sym, "", dry_run=True)
688  except SymbolError as err1:
689  try:
690  other_table.rename_symbol(other_sym, "", dry_run=True)
691  except SymbolError as err2:
692  # pylint: disable=raise-missing-from
693  raise SymbolError(
694  f"There is a name clash for symbol '{this_sym.name}' "
695  f"that cannot be resolved by renaming "
696  f"one of the instances because:\n- {err1}\n- {err2}")
697 
698  def _add_container_symbols_from_table(self, other_table):
699  '''
700  Takes container symbols from the supplied symbol table and adds them to
701  this table. All references to each container symbol are also updated.
702  (This is a preliminary step to adding all symbols from other_table to
703  this table.)
704 
705  :param other_table: the symbol table from which to take container
706  symbols.
707  :type other_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
708 
709  '''
710  for csym in other_table.containersymbols:
711  if csym.name in self:
712  # We have a clash with another symbol in this table.
713  self_csym = self.lookuplookup(csym.name)
714  if not isinstance(self_csym, ContainerSymbol):
715  # The symbol in *this* table is not a Container so we
716  # may be able to rename it.
717  self.rename_symbolrename_symbol(
718  self_csym,
719  self.next_available_namenext_available_name(
720  csym.name, other_table=other_table))
721  # We can then add an import from the Container.
722  self.addadd(csym)
723  else:
724  # The symbol in *this* table is also a ContainerSymbol so
725  # must refer to the same Container. If there is a wildcard
726  # import from this Container then we'll need that in this
727  # Table too.
728  if csym.wildcard_import:
729  self_csym.wildcard_import = True
730  else:
731  self.addadd(csym)
732  # We must update all references to this ContainerSymbol
733  # so that they point to the one in this table instead.
734  imported_syms = other_table.symbols_imported_from(csym)
735  for isym in imported_syms:
736  if isym.name in self:
737  # We have a potential clash with a symbol imported
738  # into the other table.
739  other_sym = self.lookuplookup(isym.name)
740  if not other_sym.is_import:
741  # The calling merge() method has already checked that
742  # we don't have a clash between symbols of the same
743  # name imported from different containers. We don't
744  # support renaming an imported symbol but the
745  # symbol in this table can be renamed so we do that.
746  self.rename_symbolrename_symbol(
747  other_sym,
748  self.next_available_namenext_available_name(
749  other_sym.name, other_table=other_table))
750  isym.interface = ImportInterface(
751  self.lookuplookup(csym.name),
752  orig_name=isym.interface.orig_name)
753 
754  def _add_symbols_from_table(self, other_table, symbols_to_skip=()):
755  '''
756  Takes symbols from the supplied symbol table and adds them to this
757  table (unless they appear in `symbols_to_skip`).
758  _add_container_symbols_from_table() MUST have been called
759  before this method in order to handle any Container Symbols and update
760  those Symbols imported from them.
761 
762  :param other_table: the symbol table from which to add symbols.
763  :type other_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
764  :param symbols_to_skip: an optional list of symbols to exclude from
765  the merge.
766  :type symbols_to_skip: Iterable[
767  :py:class:`psyclone.psyir.symbols.Symbol`]
768 
769  :raises InternalError: if an imported symbol is found that has not
770  already been updated to refer to a Container in this table.
771 
772  '''
773  for old_sym in other_table.symbols:
774 
775  if old_sym in symbols_to_skip or isinstance(old_sym,
776  ContainerSymbol):
777  # We've dealt with Container symbols in _add_container_symbols.
778  continue
779 
780  try:
781  self.addadd(old_sym)
782 
783  except KeyError:
784  # We have a clash with a symbol in this table.
785  self._handle_symbol_clash_handle_symbol_clash(old_sym, other_table)
786 
787  def _handle_symbol_clash(self, old_sym, other_table):
788  '''
789  Adds the supplied Symbol to the current table in the presence
790  of a name clash. `check_for_clashes` MUST have been called
791  prior to this method in order to check for any unresolvable cases.
792 
793  :param old_sym: the Symbol to be added to self.
794  :type old_sym: :py:class:`psyclone.psyir.symbols.Symbol`
795  :param other_table: the other table containing the symbol that we are
796  trying to add to self.
797  :type other_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
798 
799  :raises InternalError: if the supplied symbol is imported from a
800  Container that is not represented in this table.
801 
802  '''
803  if old_sym.is_import:
804  # This symbol is imported from a Container so should
805  # already have been updated so as to be imported from the
806  # corresponding container in this table.
807  self_csym = self.lookuplookup(old_sym.interface.container_symbol.name)
808  if old_sym.interface.container_symbol is self_csym:
809  return
810  raise InternalError(
811  f"Symbol '{old_sym.name}' imported from '{self_csym.name}' "
812  f"has not been updated to refer to the corresponding "
813  f"container in the current table.")
814 
815  self_sym = self.lookuplookup(old_sym.name)
816  if old_sym.is_unresolved and self_sym.is_unresolved:
817  # The clashing symbols are both unresolved so we ASSUME that
818  # check_for_clashes has previously determined that they must
819  # refer to the same thing and we don't have to do anything.
820  return
821 
822  # A Symbol with the same name already exists so we attempt to rename
823  # first the one that we are adding and failing that, the existing
824  # symbol in this table.
825  new_name = self.next_available_namenext_available_name(
826  old_sym.name, other_table=other_table)
827  try:
828  other_table.rename_symbol(old_sym, new_name)
829  self.addadd(old_sym)
830  except SymbolError:
831  self.rename_symbolrename_symbol(self_sym, new_name)
832  self.addadd(old_sym)
833 
834  def merge(self, other_table, symbols_to_skip=()):
835  '''Merges all of the symbols found in `other_table` into this
836  table. Symbol objects in *either* table may be renamed in the
837  event of clashes.
838 
839  Any Symbols appearing in `symbols_to_skip` are excluded.
840 
841  :param other_table: the symbol table from which to add symbols.
842  :type other_table: :py:class:`psyclone.psyir.symbols.SymbolTable`
843  :param symbols_to_skip: an optional list of Symbols to exclude from
844  the merge.
845  :type symbols_to_skip: Iterable[
846  :py:class:`psyclone.psyir.symbols.Symbol`]
847 
848  :raises TypeError: if `other_table` is not a SymbolTable.
849  :raises TypeError: if `symbols_to_skip` is not an Iterable.
850  :raises SymbolError: if name clashes prevent the merge.
851 
852  '''
853  if not isinstance(other_table, SymbolTable):
854  raise TypeError(f"SymbolTable.merge() expects a SymbolTable "
855  f"instance but got '{type(other_table).__name__}'")
856  if not isinstance(symbols_to_skip, Iterable):
857  raise TypeError(
858  f"SymbolTable.merge() expects 'symbols_to_skip' to be an "
859  f"Iterable but got '{type(symbols_to_skip).__name__}'")
860 
861  try:
862  self.check_for_clashescheck_for_clashes(other_table,
863  symbols_to_skip=symbols_to_skip)
864  except SymbolError as err:
865  raise SymbolError(
866  f"Cannot merge {other_table.view()} with {self.view()} due to "
867  f"unresolvable name clashes.") from err
868 
869  # Deal with any Container symbols first.
870  self._add_container_symbols_from_table_add_container_symbols_from_table(other_table)
871 
872  # Copy each Symbol from the supplied table into this one, excluding
873  # ContainerSymbols and any listed in `symbols_to_skip`.
874  self._add_symbols_from_table_add_symbols_from_table(other_table,
875  symbols_to_skip=symbols_to_skip)
876 
877  def swap_symbol_properties(self, symbol1, symbol2):
878  '''Swaps the properties of symbol1 and symbol2 apart from the symbol
879  name. Argument list positions are also updated appropriately.
880 
881  :param symbol1: the first symbol.
882  :type symbol1: :py:class:`psyclone.psyir.symbols.Symbol`
883  :param symbol2: the second symbol.
884  :type symbol2: :py:class:`psyclone.psyir.symbols.Symbol`
885 
886  :raises KeyError: if either of the supplied symbols are not in \
887  the symbol table.
888  :raises TypeError: if the supplied arguments are not symbols, \
889  or the names of the symbols are the same in the SymbolTable \
890  instance.
891 
892  '''
893  for symbol in [symbol1, symbol2]:
894  if not isinstance(symbol, Symbol):
895  raise TypeError(f"Arguments should be of type 'Symbol' but "
896  f"found '{type(symbol).__name__}'.")
897  if symbol.name not in self._symbols_symbols:
898  raise KeyError(f"Symbol '{symbol.name}' is not in the symbol "
899  f"table.")
900  if self._has_same_name_has_same_name(symbol1.name, symbol2.name):
901  raise ValueError(f"The symbols should have different names, but "
902  f"found '{symbol1.name}' for both.")
903 
904  tmp_symbol = symbol1.copy()
905  symbol1.copy_properties(symbol2)
906  symbol2.copy_properties(tmp_symbol)
907 
908  # Update argument list if necessary
909  index1 = None
910  if symbol1 in self._argument_list_argument_list:
911  index1 = self._argument_list_argument_list.index(symbol1)
912  index2 = None
913  if symbol2 in self._argument_list_argument_list:
914  index2 = self._argument_list_argument_list.index(symbol2)
915  if index1 is not None:
916  self._argument_list_argument_list[index1] = symbol2
917  if index2 is not None:
918  self._argument_list_argument_list[index2] = symbol1
919 
920  def specify_argument_list(self, argument_symbols):
921  '''
922  Sets-up the internal list storing the order of the arguments to this
923  kernel.
924 
925  :param list argument_symbols: ordered list of the DataSymbols \
926  representing the kernel arguments.
927 
928  :raises ValueError: if the new argument_list is not consistent with \
929  the existing entries in the SymbolTable.
930 
931  '''
932  self._validate_arg_list_validate_arg_list(argument_symbols)
933  self._argument_list_argument_list = argument_symbols[:]
934 
935  def lookup(self, name, visibility=None, scope_limit=None):
936  '''Look up a symbol in the symbol table. The lookup can be limited
937  by visibility (e.g. just show public methods) or by scope_limit (e.g.
938  just show symbols up to a certain scope).
939 
940  :param str name: name of the symbol.
941  :param visibilty: the visibility or list of visibilities that the \
942  symbol must have.
943  :type visibility: [list of] :py:class:`psyclone.symbols.Visibility`
944  :param scope_limit: optional Node which limits the symbol \
945  search space to the symbol tables of the nodes within the \
946  given scope. If it is None (the default), the whole \
947  scope (all symbol tables in ancestor nodes) is searched \
948  otherwise ancestors of the scope_limit node are not \
949  searched.
950  :type scope_limit: :py:class:`psyclone.psyir.nodes.Node` or \
951  `NoneType`
952 
953  :returns: the symbol with the given name and, if specified, visibility.
954  :rtype: :py:class:`psyclone.psyir.symbols.Symbol`
955 
956  :raises TypeError: if the name argument is not a string.
957  :raises SymbolError: if the name exists in the Symbol Table but does \
958  not have the specified visibility.
959  :raises TypeError: if the visibility argument has the wrong type.
960  :raises KeyError: if the given name is not in the Symbol Table.
961 
962  '''
963  if not isinstance(name, str):
964  raise TypeError(
965  f"Expected the name argument to the lookup() method to be "
966  f"a str but found '{type(name).__name__}'.")
967 
968  try:
969  symbol = self.get_symbolsget_symbols(scope_limit)[self._normalize_normalize(name)]
970  if visibility:
971  if not isinstance(visibility, list):
972  vis_list = [visibility]
973  else:
974  vis_list = visibility
975  if symbol.visibility not in vis_list:
976  vis_names = []
977  # Take care here in case the 'visibility' argument
978  # is of the wrong type
979  for vis in vis_list:
980  if not isinstance(vis, Symbol.Visibility):
981  raise TypeError(
982  f"the 'visibility' argument to lookup() must "
983  f"be an instance (or list of instances) of "
984  f"Symbol.Visibility but got "
985  f"'{type(vis).__name__}' when searching for "
986  f"symbol '{name}'")
987  vis_names.append(vis.name)
988  raise SymbolError(
989  f"Symbol '{name}' exists in the Symbol Table but has "
990  f"visibility '{symbol.visibility.name}' which does not"
991  f" match with the requested visibility: {vis_names}")
992  return symbol
993  except KeyError as err:
994  raise KeyError(f"Could not find '{name}' in the Symbol Table.") \
995  from err
996 
997  def lookup_with_tag(self, tag, scope_limit=None):
998  '''Look up a symbol by its tag. The lookup can be limited by
999  scope_limit (e.g. just show symbols up to a certain scope).
1000 
1001  :param str tag: tag identifier.
1002  :param scope_limit: optional Node which limits the symbol \
1003  search space to the symbol tables of the nodes within the \
1004  given scope. If it is None (the default), the whole \
1005  scope (all symbol tables in ancestor nodes) is searched \
1006  otherwise ancestors of the scope_limit node are not \
1007  searched.
1008 
1009  :returns: symbol with the given tag.
1010  :rtype: :py:class:`psyclone.psyir.symbols.Symbol`
1011 
1012  :raises TypeError: if the tag argument is not a string.
1013  :raises KeyError: if the given tag is not in the Symbol Table.
1014 
1015  '''
1016  if not isinstance(tag, str):
1017  raise TypeError(
1018  f"Expected the tag argument to the lookup_with_tag() method "
1019  f"to be a str but found '{type(tag).__name__}'.")
1020 
1021  try:
1022  return self.get_tagsget_tags(scope_limit)[tag]
1023  except KeyError as err:
1024  raise KeyError(f"Could not find the tag '{tag}' in the Symbol "
1025  f"Table.") from err
1026 
1027  def __contains__(self, key):
1028  '''Check if the given key is part of the Symbol Table.
1029 
1030  :param str key: key to check for existance.
1031 
1032  :returns: whether the Symbol Table contains the given key.
1033  :rtype: bool
1034  '''
1035  return self._normalize_normalize(key.lower()) in self._symbols_symbols
1036 
1037  def symbols_imported_from(self, csymbol):
1038  '''
1039  Examines the contents of this symbol table to see which DataSymbols
1040  (if any) are imported from the supplied ContainerSymbol (which must
1041  be present in the SymbolTable).
1042 
1043  :param csymbol: the ContainerSymbol to search for imports from.
1044  :type csymbol: :py:class:`psyclone.psyir.symbols.ContainerSymbol`
1045 
1046  :returns: list of DataSymbols that are imported from the supplied \
1047  ContainerSymbol. If none are found then the list is empty.
1048  :rtype: list of :py:class:`psyclone.psyir.symbols.DataSymbol`
1049 
1050  :raises TypeError: if the supplied object is not a ContainerSymbol.
1051  :raises KeyError: if the supplied object is not in this SymbolTable.
1052 
1053  '''
1054  if not isinstance(csymbol, ContainerSymbol):
1055  raise TypeError(
1056  f"symbols_imported_from() expects a ContainerSymbol but got "
1057  f"an object of type '{type(csymbol).__name__}'")
1058  # self.lookup(name) will raise a KeyError if there is no symbol with
1059  # that name in the table.
1060  if self.lookuplookup(csymbol.name) is not csymbol:
1061  raise KeyError(f"The '{csymbol.name}' entry in this SymbolTable "
1062  f"is not the supplied ContainerSymbol.")
1063 
1064  return [symbol for symbol in self.imported_symbolsimported_symbols if
1065  symbol.interface.container_symbol is csymbol]
1066 
1067  def swap(self, old_symbol, new_symbol):
1068  '''
1069  Remove the `old_symbol` from the table and replace it with the
1070  `new_symbol`.
1071 
1072  :param old_symbol: the symbol to remove from the table.
1073  :type old_symbol: :py:class:`psyclone.psyir.symbols.Symbol`
1074  :param new_symbol: the symbol to add to the table.
1075  :type new_symbol: :py:class:`psyclone.psyir.symbols.Symbol`
1076 
1077  :raises TypeError: if either old/new_symbol are not Symbols.
1078  :raises SymbolError: if `old_symbol` and `new_symbol` don't have
1079  the same name (after normalising).
1080  '''
1081  if not isinstance(old_symbol, Symbol):
1082  raise TypeError(f"Symbol to remove must be of type Symbol but "
1083  f"got '{type(old_symbol).__name__}'")
1084  if not isinstance(new_symbol, Symbol):
1085  raise TypeError(f"Symbol to add must be of type Symbol but "
1086  f"got '{type(new_symbol).__name__}'")
1087  if not self._has_same_name_has_same_name(old_symbol, new_symbol):
1088  raise SymbolError(
1089  f"Cannot swap symbols that have different names, got: "
1090  f"'{old_symbol.name}' and '{new_symbol.name}'")
1091  # TODO #898 remove() does not currently check for any uses of
1092  # old_symbol.
1093  self.removeremove(old_symbol)
1094  self.addadd(new_symbol)
1095 
1096  def _validate_remove_routinesymbol(self, symbol):
1097  '''
1098  Checks whether the supplied RoutineSymbol can be removed from this
1099  table.
1100 
1101  If the symbol is the target of a Call or is a member of an interface
1102  definition then removal is not possible.
1103 
1104  :param symbol: the Symbol to validate for removal.
1105  :type symbol: :py:class:`psyclone.psyir.symbols.RoutineSymbol`
1106 
1107  :raises ValueError: if the Symbol cannot be removed.
1108 
1109  '''
1110  # Check for Calls or GenericInterfaceSymbols that reference it.
1111  # TODO #2271 - this walk will fail to find some symbols (e.g. in
1112  # variable initialisation expressions or CodeBlocks).
1113  # pylint: disable=import-outside-toplevel
1114  from psyclone.psyir.nodes import Call
1115  all_calls = self.nodenode.walk(Call) if self.nodenode else []
1116  for call in all_calls:
1117  if call.routine.symbol is symbol:
1118  raise ValueError(
1119  f"Cannot remove RoutineSymbol '{symbol.name}' "
1120  f"because it is referenced by '{call.debug_string()}'")
1121  # Check for any references to it within interfaces.
1122  for sym in self._symbols_symbols.values():
1123  if not isinstance(sym, GenericInterfaceSymbol):
1124  continue
1125  for rt_info in sym.routines:
1126  if rt_info.symbol is symbol:
1127  raise ValueError(
1128  f"Cannot remove RoutineSymbol '{symbol.name}' "
1129  f"because it is referenced in interface '{sym.name}'")
1130 
1131  def remove(self, symbol):
1132  '''
1133  Remove the supplied symbol from the Symbol Table. This has a high
1134  potential to leave broken links, so this method checks for some
1135  references to the removed symbol depending on the symbol type.
1136 
1137  Currently, generic Symbols, ContainerSymbols and RoutineSymbols are
1138  supported. Support for removing other types of Symbol will be added
1139  as required.
1140 
1141  TODO #898. This method should check for any references/uses of
1142  the target symbol.
1143 
1144  :param symbol: the symbol to remove.
1145  :type symbol: :py:class:`psyclone.psyir.symbols.Symbol`
1146 
1147  :raises TypeError: if the supplied parameter is not of type Symbol.
1148  :raises NotImplementedError: the removal of this symbol type is not
1149  supported yet.
1150  :raises KeyError: if the supplied symbol is not in the symbol table.
1151  :raises ValueError: if the supplied container symbol is referenced
1152  by one or more DataSymbols.
1153  :raises InternalError: if the supplied symbol is not the same as the
1154  entry with that name in this SymbolTable.
1155  '''
1156  if not isinstance(symbol, Symbol):
1157  raise TypeError(f"remove() expects a Symbol argument but found: "
1158  f"'{type(symbol).__name__}'.")
1159 
1160  # pylint: disable=unidiomatic-typecheck
1161  if not (isinstance(symbol, (ContainerSymbol, RoutineSymbol)) or
1162  type(symbol) is Symbol):
1163  raise NotImplementedError(
1164  f"remove() currently only supports generic Symbol, "
1165  f"ContainerSymbol and RoutineSymbol types but got: "
1166  f"'{type(symbol).__name__}'")
1167  # pylint: enable=unidiomatic-typecheck
1168 
1169  # Since we are manipulating the _symbols dict directly we must use
1170  # the normalised name of the symbol.
1171  norm_name = self._normalize_normalize(symbol.name)
1172 
1173  if norm_name not in self._symbols_symbols:
1174  raise KeyError(f"Cannot remove Symbol '{symbol.name}' from symbol "
1175  f"table because it does not exist.")
1176  # Sanity-check that the entry in the table is the symbol we've
1177  # been passed.
1178  if self._symbols_symbols[norm_name] is not symbol:
1179  raise InternalError(
1180  f"The Symbol with name '{symbol.name}' in this symbol table "
1181  f"is not the same Symbol object as the one that has been "
1182  f"supplied to the remove() method.")
1183 
1184  # We can only remove a ContainerSymbol if no DataSymbols are
1185  # being imported from it
1186  if (isinstance(symbol, ContainerSymbol) and
1187  self.symbols_imported_fromsymbols_imported_from(symbol)):
1188  raise ValueError(
1189  f"Cannot remove ContainerSymbol '{symbol.name}' since symbols"
1190  f" {[sym.name for sym in self.symbols_imported_from(symbol)]} "
1191  f"are imported from it - remove them first.")
1192 
1193  # RoutineSymbols require special consideration as they may be the
1194  # target of a Call or a member of a GenericInterfaceSymbol.
1195  if isinstance(symbol, RoutineSymbol):
1196  self._validate_remove_routinesymbol_validate_remove_routinesymbol(symbol)
1197 
1198  # If the symbol had a tag, it should be disassociated
1199  for tag, tagged_symbol in list(self._tags_tags.items()):
1200  if symbol is tagged_symbol:
1201  del self._tags_tags[tag]
1202 
1203  self._symbols_symbols.pop(norm_name)
1204 
1205  @property
1206  def argument_list(self):
1207  '''
1208  Checks that the contents of the SymbolTable are self-consistent
1209  and then returns the list of kernel arguments.
1210 
1211  :returns: ordered list of arguments.
1212  :rtype: list of :py:class:`psyclone.psyir.symbols.DataSymbol`
1213 
1214  :raises InternalError: if the entries of the SymbolTable are not \
1215  self-consistent.
1216 
1217  '''
1218  try:
1219  self._validate_arg_list_validate_arg_list(self._argument_list_argument_list)
1220  self._validate_non_args_validate_non_args()
1221  except ValueError as err:
1222  # If the SymbolTable is inconsistent at this point then
1223  # we have an InternalError.
1224  raise InternalError(str(err.args)) from err
1225  return self._argument_list_argument_list
1226 
1227  @staticmethod
1228  def _validate_arg_list(arg_list):
1229  '''
1230  Checks that the supplied list of Symbols are valid kernel arguments.
1231 
1232  :param arg_list: the proposed kernel arguments.
1233  :type param_list: list of :py:class:`psyclone.psyir.symbols.DataSymbol`
1234 
1235  :raises TypeError: if any item in the supplied list is not a \
1236  DataSymbol.
1237  :raises ValueError: if any of the symbols does not have an argument \
1238  interface.
1239 
1240  '''
1241  for symbol in arg_list:
1242  if not isinstance(symbol, DataSymbol):
1243  raise TypeError(f"Expected a list of DataSymbols but found an "
1244  f"object of type '{type(symbol)}'.")
1245  if not symbol.is_argument:
1246  raise ValueError(
1247  f"DataSymbol '{symbol}' is listed as a kernel argument "
1248  f"but has an interface of type '{type(symbol.interface)}' "
1249  f"rather than ArgumentInterface")
1250 
1251  def _validate_non_args(self):
1252  '''
1253  Performs internal consistency checks on the current entries in the
1254  SymbolTable that do not represent kernel arguments.
1255 
1256  :raises ValueError: if a symbol that is not in the argument list \
1257  has an argument interface.
1258 
1259  '''
1260  for symbol in self.datasymbolsdatasymbols:
1261  if symbol not in self._argument_list_argument_list:
1262  # DataSymbols not in the argument list must not have a
1263  # Symbol.Argument interface
1264  if symbol.is_argument:
1265  raise ValueError(
1266  f"Symbol '{symbol}' is not listed as a kernel argument"
1267  f" and yet has an ArgumentInterface interface.")
1268 
1269  @property
1270  def symbols_dict(self):
1271  '''
1272  :returns: ordered dictionary of symbols indexed by symbol name.
1273  :rtype: OrderedDict[str] = :py:class:`psyclone.psyir.symbols.Symbol`
1274 
1275  '''
1276  return self._symbols_symbols
1277 
1278  @property
1279  def tags_dict(self):
1280  '''
1281  :returns: ordered dictionary of symbols indexed by tag.
1282  :rtype: OrderedDict[str] = :py:class:`psyclone.psyir.symbols.Symbol`
1283 
1284  '''
1285  return self._tags_tags
1286 
1288  '''
1289  Constructs and returns a reverse of the map returned by tags_dict
1290  method.
1291 
1292  :returns: ordered dictionary of tags indexed by symbol.
1293  :rtype: OrderedDict[:py:class:`psyclone.psyir.symbols.Symbol`, str]
1294 
1295  '''
1296  tags_dict_reversed = OrderedDict()
1297  # TODO #1654. This assumes that there is only ever one tag associated
1298  # with a particular Symbol. At present this is guaranteed by the
1299  # SymbolTable interface but this restriction may be lifted in future.
1300  for tag, sym in self._tags_tags.items():
1301  tags_dict_reversed[sym] = tag
1302  return tags_dict_reversed
1303 
1304  @property
1305  def symbols(self):
1306  '''
1307  :returns: list of symbols.
1308  :rtype: List[:py:class:`psyclone.psyir.symbols.Symbol`]
1309  '''
1310  return list(self._symbols_symbols.values())
1311 
1312  @property
1313  def datasymbols(self):
1314  '''
1315  :returns: list of symbols representing data variables.
1316  :rtype: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
1317  '''
1318  return [sym for sym in self._symbols_symbols.values() if
1319  isinstance(sym, DataSymbol)]
1320 
1321  @property
1323  '''
1324  :returns: list of symbols representing automatic variables.
1325  :rtype: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
1326  '''
1327  return [sym for sym in self.datasymbolsdatasymbols if sym.is_automatic]
1328 
1329  @property
1331  '''
1332  :returns: list of symbols representing arguments.
1333  :rtype: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
1334  '''
1335  return [sym for sym in self.datasymbolsdatasymbols if sym.is_argument]
1336 
1337  @property
1338  def imported_symbols(self):
1339  '''
1340  :returns: list of symbols that have an imported interface (are \
1341  associated with data that exists outside the current scope).
1342  :rtype: List[:py:class:`psyclone.psyir.symbols.Symbol`]
1343 
1344  '''
1345  return [sym for sym in self.symbolssymbols if sym.is_import]
1346 
1347  @property
1349  '''
1350  :returns: list of symbols representing unresolved variables.
1351  :rtype: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
1352  '''
1353  return [sym for sym in self.datasymbolsdatasymbols if sym.is_unresolved]
1354 
1355  @property
1357  '''
1358  :returns: list of all symbols used to define the precision of \
1359  other symbols within the table.
1360  :rtype: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
1361 
1362  '''
1363  # Accumulate into a set so as to remove any duplicates
1364  precision_symbols = set()
1365  for sym in self.datasymbolsdatasymbols:
1366  # Not all types have the 'precision' attribute (e.g.
1367  # UnresolvedType)
1368  if (hasattr(sym.datatype, "precision") and
1369  isinstance(sym.datatype.precision, DataSymbol)):
1370  precision_symbols.add(sym.datatype.precision)
1371  return list(precision_symbols)
1372 
1373  @property
1374  def containersymbols(self):
1375  '''
1376  :returns: a list of the ContainerSymbols present in the Symbol Table.
1377  :rtype: List[:py:class:`psyclone.psyir.symbols.ContainerSymbol`]
1378  '''
1379  return [sym for sym in self.symbolssymbols if isinstance(sym,
1380  ContainerSymbol)]
1381 
1382  @property
1383  def datatypesymbols(self):
1384  '''
1385  :returns: the DataTypeSymbols present in the Symbol Table.
1386  :rtype: List[:py:class:`psyclone.psyir.symbols.DataTypeSymbol`]
1387  '''
1388  return [sym for sym in self.symbolssymbols if isinstance(sym, DataTypeSymbol)]
1389 
1390  @property
1392  '''
1393  :returns: list of symbols representing kernel iteration indices.
1394  :rtype: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
1395 
1396  :raises NotImplementedError: this method is abstract.
1397  '''
1398  raise NotImplementedError(
1399  "Abstract property. Which symbols are iteration indices is"
1400  " API-specific.")
1401 
1402  @property
1403  def data_arguments(self):
1404  '''
1405  :returns: list of symbols representing kernel data arguments.
1406  :rtype: List[:py:class:`psyclone.psyir.symbols.DataSymbol`]
1407 
1408  :raises NotImplementedError: this method is abstract.
1409  '''
1410  raise NotImplementedError(
1411  "Abstract property. Which symbols are data arguments is"
1412  " API-specific.")
1413 
1414  def copy_external_import(self, imported_var, tag=None):
1415  '''
1416  Copy the given imported variable (and its referenced ContainerSymbol if
1417  needed) into the SymbolTable.
1418 
1419  :param imported_var: the variable to be copied in.
1420  :type imported_var: :py:class:`psyclone.psyir.symbols.DataSymbol`
1421  :param str tag: a tag identifier for the new copy, by default no tag \
1422  is given.
1423 
1424  :raises TypeError: if the given variable is not an imported variable.
1425  :raises KeyError: if the given variable name already exists in the \
1426  symbol table.
1427 
1428  '''
1429  if not isinstance(imported_var, DataSymbol):
1430  raise TypeError(
1431  f"The imported_var argument of "
1432  f"SymbolTable.copy_external_import method should be a "
1433  f"DataSymbol, but found '{type(imported_var).__name__}'.")
1434 
1435  if not imported_var.is_import:
1436  raise TypeError(
1437  f"The imported_var argument of SymbolTable.copy_external_"
1438  f"import method should have an ImportInterface interface, "
1439  f"but found '{type(imported_var.interface).__name__}'.")
1440 
1441  external_container_name = imported_var.interface.container_symbol.name
1442 
1443  # If the Container is not yet in the SymbolTable we need to
1444  # create one and add it.
1445  if external_container_name not in self:
1446  self.addadd(ContainerSymbol(external_container_name))
1447  container_ref = self.lookuplookup(external_container_name)
1448 
1449  # Copy the variable into the SymbolTable with the appropriate interface
1450  if imported_var.name not in self:
1451  new_symbol = imported_var.copy()
1452  # Update the interface of this new symbol
1453  new_symbol.interface = ImportInterface(container_ref)
1454  self.addadd(new_symbol, tag)
1455  else:
1456  # If it already exists it must refer to the same Container and have
1457  # the same tag.
1458  local_instance = self.lookuplookup(imported_var.name)
1459  if not (local_instance.is_import and
1460  self._has_same_name_has_same_name(
1461  local_instance.interface.container_symbol,
1462  external_container_name)):
1463  raise KeyError(
1464  f"Couldn't copy '{imported_var}' into the SymbolTable. The"
1465  f" name '{imported_var.name}' is already used by another "
1466  f"symbol.")
1467  if tag:
1468  # If the symbol already exists and a tag is provided
1469  try:
1470  self.lookup_with_taglookup_with_tag(tag)
1471  except KeyError:
1472  # If the tag was not used, it will now be attached
1473  # to the symbol.
1474  self._tags_tags[tag] = self.lookuplookup(imported_var.name)
1475 
1476  # The tag should not refer to a different symbol
1477  if self.lookuplookup(imported_var.name) != self.lookup_with_taglookup_with_tag(tag):
1478  raise KeyError(
1479  f"Couldn't copy '{imported_var}' into the SymbolTable."
1480  f" The tag '{tag}' is already used by another symbol.")
1481 
1482  def resolve_imports(self, container_symbols=None, symbol_target=None):
1483  ''' Try to resolve deferred and unknown information from imported
1484  symbols in this symbol table by searching for their definitions in
1485  referred external container. A single symbol to resolve can be
1486  specified for a more targeted import.
1487 
1488  :param container_symbols: list of container symbols to search in
1489  order to resolve imported symbols. Defaults to all container
1490  symbols in the symbol table.
1491  :type container_symbols: list[
1492  :py:class:`psyclone.psyir.symbols.ContainerSymbol`]
1493  :param symbol_target: If a symbol is given, this method will just
1494  resolve information for the given symbol. Otherwise it will
1495  resolve all possible symbols information. Defaults to None.
1496  :type symbol_target: Optional[
1497  :py:class:`psyclone.psyir.symbols.Symbol`]
1498 
1499  :raises SymbolError: if a symbol name clash is found between multiple \
1500  imports or an import and a local symbol.
1501  :raises TypeError: if the provided container_symbols is not a list of \
1502  ContainerSymbols.
1503  :raises TypeError: if the provided symbol_target is not a Symbol.
1504  :raises KeyError: if a symbol_target has been specified but this has \
1505  not been found in any of the searched containers.
1506 
1507  '''
1508  if container_symbols is not None:
1509  if not isinstance(container_symbols, list):
1510  raise TypeError(
1511  f"The resolve_imports container_symbols argument must be a"
1512  f" list but found '{type(container_symbols).__name__}' "
1513  f"instead.")
1514  for item in container_symbols:
1515  if not isinstance(item, ContainerSymbol):
1516  raise TypeError(
1517  f"The resolve_imports container_symbols argument list "
1518  f"elements must be ContainerSymbols, but found a "
1519  f"'{type(item).__name__}' instead.")
1520  else:
1521  # If no container_symbol is given, search in all the containers
1522  container_symbols = self.containersymbolscontainersymbols
1523 
1524  if symbol_target and not isinstance(symbol_target, Symbol):
1525  raise TypeError(
1526  f"The resolve_imports symbol_target argument must be a Symbol "
1527  f"but found '{type(symbol_target).__name__}' instead.")
1528 
1529  for c_symbol in container_symbols:
1530  try:
1531  external_container = c_symbol.container
1532  # pylint: disable=broad-except
1533  except Exception:
1534  # Ignore this container if the associated module file has not
1535  # been found in the given include_path or any issue has arisen
1536  # during parsing.
1537  # TODO #11: It would be useful to log this.
1538  continue
1539 
1540  # Examine all Symbols defined within this external container
1541  for symbol in external_container.symbol_table.symbols:
1542  if symbol.visibility == Symbol.Visibility.PRIVATE:
1543  continue # We must ignore this symbol
1544 
1545  if isinstance(symbol, ContainerSymbol):
1546  # TODO #1540: We also skip other ContainerSymbols but in
1547  # reality if this is a wildcard import we would have to
1548  # process the nested external container.
1549  continue
1550 
1551  # If we are just resolving a single specific symbol we don't
1552  # need to process this symbol unless the name matches.
1553  if symbol_target and not self._has_same_name_has_same_name(
1554  symbol, symbol_target):
1555  continue
1556 
1557  # Determine if there is an Unresolved Symbol in a
1558  # descendent symbol table that matches the name of the
1559  # symbol we are importing and if so, move it to this
1560  # symbol table if a symbol with the same name does not
1561  # already exist in this symbol table.
1562 
1563  # There are potential issues with this approach and
1564  # with the routine in general which are captured in
1565  # issue #2331. Issue #2271 may also help/fix some or
1566  # all of the problems too.
1567 
1568  # Import here to avoid circular dependencies
1569  # pylint: disable=import-outside-toplevel
1570  from psyclone.psyir.nodes import ScopingNode, Reference
1571  for scoping_node in self.nodenode.walk(ScopingNode):
1572  symbol_table = scoping_node.symbol_table
1573  if symbol.name in symbol_table:
1574  test_symbol = symbol_table.lookup(symbol.name)
1575  # pylint: disable=unidiomatic-typecheck
1576  if (type(test_symbol) is Symbol
1577  and test_symbol.is_unresolved):
1578  # No wildcard imports in this symbol table
1579  if not [sym for sym in
1580  symbol_table.containersymbols if
1581  sym.wildcard_import]:
1582  symbol_table.remove(test_symbol)
1583  if test_symbol.name not in self:
1584  self.addadd(test_symbol)
1585  else:
1586  for ref in symbol_table.node.walk(
1587  Reference):
1588  if SymbolTable._has_same_name(
1589  ref.symbol, symbol):
1590  mod_symbol = self.lookuplookup(
1591  symbol.name)
1592  ref.symbol = mod_symbol
1593 
1594  # This Symbol matches the name of a symbol in the current table
1595  if symbol.name in self:
1596 
1597  symbol_match = self.lookuplookup(symbol.name)
1598  interface = symbol_match.interface
1599  visibility = symbol_match.visibility
1600 
1601  # If the import statement is not a wildcard import, the
1602  # matching symbol must have the appropriate interface
1603  # referring to this c_symbol
1604  if not c_symbol.wildcard_import:
1605  if not isinstance(interface, ImportInterface) or \
1606  interface.container_symbol is not c_symbol:
1607  continue # It doesn't come from this import
1608 
1609  # Found a match, update the interface if necessary or raise
1610  # an error if it is an ambiguous match
1611  if isinstance(interface, UnresolvedInterface):
1612  # Now we know where the symbol is coming from
1613  interface = ImportInterface(c_symbol)
1614  elif isinstance(interface, ImportInterface):
1615  # If it is already an ImportInterface we don't need
1616  # to update the interface information
1617  pass
1618  else:
1619  raise SymbolError(
1620  f"Found a name clash with symbol '{symbol.name}' "
1621  f"when importing symbols from container "
1622  f"'{c_symbol.name}'.")
1623 
1624  # If the external symbol is a subclass of the local
1625  # symbol_match, copy the external symbol properties,
1626  # otherwise ignore this step.
1627  if isinstance(symbol, type(symbol_match)):
1628  # pylint: disable=unidiomatic-typecheck
1629  if type(symbol) is not type(symbol_match):
1630  if isinstance(symbol, TypedSymbol):
1631  # All TypedSymbols have a mandatory datatype
1632  # argument
1633  symbol_match.specialise(
1634  type(symbol), datatype=symbol.datatype)
1635  else:
1636  symbol_match.specialise(type(symbol))
1637 
1638  symbol_match.copy_properties(symbol)
1639  # Restore the interface and visibility as these are
1640  # local (not imported) properties
1641  symbol_match.interface = interface
1642  symbol_match.visibility = visibility
1643  if symbol_target:
1644  # If we were looking just for this symbol we don't need
1645  # to continue searching
1646  return
1647  else:
1648  if c_symbol.wildcard_import:
1649  # This symbol is PUBLIC and inside a wildcard import,
1650  # so it needs to be declared in the symbol table.
1651  new_symbol = symbol.copy()
1652  new_symbol.interface = ImportInterface(c_symbol)
1653  new_symbol.visibility = self.default_visibilitydefault_visibilitydefault_visibilitydefault_visibility
1654  self.addadd(new_symbol)
1655  if symbol_target:
1656  # If we were looking just for this symbol then
1657  # we're done.
1658  return
1659 
1660  if symbol_target:
1661  raise KeyError(
1662  f"The target symbol '{symbol_target.name}' was not found in "
1663  f"any of the searched containers: "
1664  f"{[cont.name for cont in container_symbols]}.")
1665 
1666  def rename_symbol(self, symbol, name, dry_run=False):
1667  '''
1668  Rename the given symbol which should belong to this symbol table
1669  with the new name provided.
1670 
1671  :param symbol: the symbol to be renamed.
1672  :type symbol: :py:class:`psyclone.psyir.symbols.Symbol`
1673  :param str name: the new name.
1674  :param bool dry_run: if True then only the validation checks are
1675  performed.
1676 
1677  :raises TypeError: if the symbol is not a Symbol.
1678  :raises TypeError: if the name is not a str.
1679  :raises ValueError: if the given symbol does not belong to this
1680  symbol table.
1681  :raises KeyError: if the given variable name already exists in the
1682  symbol table.
1683  :raises SymbolError: if the specified Symbol is a ContainerSymbol, is
1684  imported/unresolved or is a formal routine argument.
1685  :raises SymbolError: if the specified Symbol is accessed within a
1686  CodeBlock in the scope of this table.
1687  :raises SymbolError: if the symbol has a common block interface.
1688 
1689  '''
1690  if not isinstance(symbol, Symbol):
1691  raise TypeError(
1692  f"The symbol argument of rename_symbol() must be a Symbol, but"
1693  f" found: '{type(symbol).__name__}'.")
1694 
1695  if symbol not in self.symbolssymbols:
1696  raise ValueError(
1697  f"The symbol argument of rename_symbol() must belong to this "
1698  f"symbol_table instance, but '{symbol}' does not.")
1699 
1700  if isinstance(symbol, ContainerSymbol):
1701  raise SymbolError(f"Cannot rename symbol '{symbol.name}' because "
1702  f"it is a ContainerSymbol.")
1703 
1704  if symbol.is_import:
1705  raise SymbolError(
1706  f"Cannot rename symbol '{symbol.name}' because it is imported "
1707  f"(from Container '{symbol.interface.container_symbol.name}')."
1708  )
1709 
1710  if symbol.is_unresolved:
1711  raise SymbolError(
1712  f"Cannot rename symbol '{symbol.name}' because it is "
1713  f"unresolved.")
1714 
1715  if symbol.is_argument:
1716  raise SymbolError(
1717  f"Cannot rename symbol '{symbol.name}' because it is a routine"
1718  f" argument and as such may be named in a Call.")
1719 
1720  if symbol.is_commonblock:
1721  raise SymbolError(
1722  f"Cannot rename symbol '{symbol.name}' because it has a "
1723  f"CommonBlock interface.")
1724 
1725  if not isinstance(name, str):
1726  raise TypeError(
1727  f"The name argument of rename_symbol() must be a str, but"
1728  f" found: '{type(symbol).__name__}'.")
1729 
1730  if self._normalize_normalize(name) in self._symbols_symbols:
1731  raise KeyError(
1732  f"The name argument of rename_symbol() must not already exist "
1733  f"in this symbol_table instance, but '{name}' does.")
1734 
1735  old_name = self._normalize_normalize(symbol.name)
1736  if self.nodenode:
1737  # pylint: disable=import-outside-toplevel
1738  from psyclone.psyir.nodes import CodeBlock
1739  cblocks = self.nodenode.walk(CodeBlock)
1740  for cblock in cblocks:
1741  sym_names = [self._normalize_normalize(sname) for sname in
1742  cblock.get_symbol_names()]
1743  if old_name in sym_names:
1744  cblk_txt = "\n".join(str(anode) for anode in
1745  cblock.get_ast_nodes)
1746  raise SymbolError(
1747  f"Cannot rename Symbol '{symbol.name}' because it is "
1748  f"accessed in a CodeBlock:\n"
1749  f"{cblk_txt}")
1750 
1751  if dry_run:
1752  return
1753 
1754  # Delete current dictionary entry
1755  del self._symbols_symbols[old_name]
1756 
1757  # Rename symbol using protected access as the Symbol class should not
1758  # expose a name attribute setter.
1759  # pylint: disable=protected-access
1760  symbol._name = name
1761 
1762  # Re-insert modified symbol
1763  self.addadd(symbol)
1764 
1765  def wildcard_imports(self):
1766  '''
1767  Searches this symbol table and then up through any parent symbol
1768  tables for a ContainerSymbol that has a wildcard import.
1769 
1770  :returns: the name(s) of containers which have wildcard imports
1771  into the current scope.
1772  :rtype: set[str]
1773 
1774  '''
1775  wildcards = set()
1776  current_table = self
1777  while current_table:
1778  for sym in current_table.containersymbols:
1779  if sym.wildcard_import:
1780  wildcards.add(sym.name)
1781  current_table = current_table.parent_symbol_table()
1782  return wildcards
1783 
1784  def view(self):
1785  '''
1786  :returns: a representation of this Symbol Table.
1787  :rtype: str
1788 
1789  '''
1790  return str(self)
1791 
1792  def __str__(self):
1793  header = "Symbol Table"
1794  if self.nodenode:
1795  header += f" of {self.node.coloured_name(False)}"
1796  if hasattr(self.nodenode, 'name'):
1797  header += f" '{self.node.name}'"
1798  header += ":"
1799  header += "\n" + "-" * len(header) + "\n"
1800 
1801  return header + "\n".join(map(str, self._symbols_symbols.values())) + "\n"
1802 
1803  @property
1804  def scope(self):
1805  '''
1806  :returns: the scope associated to this symbol table.
1807  :rtype: :py:class:`psyclone.psyir.nodes.ScopingNode`
1808 
1809  '''
1810  return self._node_node
1811 
1812  def detach(self):
1813  ''' Detach this symbol table from the associated scope and return self.
1814 
1815  :returns: this symbol table.
1816  :rtype: py:class:`psyclone.psyir.symbols.SymbolTable`
1817 
1818  '''
1819  if self._node_node:
1820  # pylint: disable=protected-access
1821  self._node_node._symbol_table = None
1822  self._node_node = None
1823  return self
1824 
1825  def attach(self, node):
1826  ''' Attach this symbol table to the provided scope.
1827 
1828  :param node: the scoped node this symbol table will attach to.
1829  :type node: py:class:`psyclone.psyir.nodes.ScopingNode`
1830 
1831  '''
1832  # pylint: disable=import-outside-toplevel
1833  from psyclone.psyir.nodes import ScopingNode
1834  if not isinstance(node, ScopingNode):
1835  raise TypeError(
1836  f"A SymbolTable must be attached to a ScopingNode"
1837  f" but found '{type(node).__name__}'.")
1838 
1839  if node.symbol_table is not None:
1840  raise ValueError(
1841  "The provided scope already has a symbol table attached "
1842  "to it. You may need to detach that one first.")
1843 
1844  if self._node_node is not None:
1845  raise ValueError(
1846  f"The symbol table is already bound to another "
1847  f"scope ({self.node.node_str(False)}). Consider "
1848  f"detaching or deepcopying the symbol table first.")
1849 
1850  self._node_node = node
1851  # pylint: disable=protected-access
1852  node._symbol_table = self
1853 
1854  def __eq__(self, other):
1855  '''
1856  Checks whether two SymbolTables are equal.
1857 
1858  # TODO 1698: Improve. Currently it uses a quick implementation
1859  # that only checks that the view() lines of each symbol_table
1860  # are exactly the same.
1861  # The current implementation does not check tags, order
1862  # of arguments or visibilities.
1863 
1864  :param object other: the object to check equality to.
1865 
1866  :returns: whether other is equal to self.
1867  :rtype: bool
1868  '''
1869  # pylint: disable=unidiomatic-typecheck
1870  if type(self) is not type(other):
1871  return False
1872  this_lines = self.viewview().split('\n')
1873  other_lines = other.view().split('\n')
1874  for line in other_lines:
1875  if line not in this_lines:
1876  return False
1877  this_lines.remove(line)
1878  return len(this_lines) == 0
def _handle_symbol_clash(self, old_sym, other_table)
def swap(self, old_symbol, new_symbol)
def resolve_imports(self, container_symbols=None, symbol_target=None)
def next_available_name(self, root_name=None, shadowing=False, other_table=None)
def rename_symbol(self, symbol, name, dry_run=False)
def add(self, new_symbol, tag=None)
def find_or_create_tag(self, tag, root_name=None, **new_symbol_args)
def check_for_clashes(self, other_table, symbols_to_skip=())
def merge(self, other_table, symbols_to_skip=())
def _add_container_symbols_from_table(self, other_table)
def lookup(self, name, visibility=None, scope_limit=None)
def swap_symbol_properties(self, symbol1, symbol2)
def _add_symbols_from_table(self, other_table, symbols_to_skip=())
def copy_external_import(self, imported_var, tag=None)
def specify_argument_list(self, argument_symbols)
def parent_symbol_table(self, scope_limit=None)
def find_or_create(self, name, **new_symbol_args)
def new_symbol(self, root_name=None, tag=None, shadowing=False, symbol_type=None, allow_renaming=True, **symbol_init_args)
def lookup_with_tag(self, tag, scope_limit=None)