Reference Guide  2.5.0
symbol.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 # -----------------------------------------------------------------------------
38 
39 '''This module contains the SymbolError class and the generic Symbol class.
40 '''
41 
42 from enum import Enum
43 from psyclone.errors import PSycloneError, InternalError
45  AutomaticInterface, SymbolInterface, ArgumentInterface,
46  UnresolvedInterface, ImportInterface, UnknownInterface,
47  CommonBlockInterface, DefaultModuleInterface, StaticInterface)
48 
49 
51  '''
52  PSyclone-specific exception for use with errors relating to the Symbol and
53  SymbolTable in the PSyIR.
54 
55  :param str value: the message associated with the error.
56  '''
57  def __init__(self, value):
58  PSycloneError.__init__(self, value)
59  self.valuevaluevalue = "PSyclone SymbolTable error: "+str(value)
60 
61 
62 class Symbol():
63  '''Generic Symbol item for the Symbol Table and PSyIR References.
64  It has an immutable name label because it must always match with the
65  key in the SymbolTable. If the symbol is private then it is only visible
66  to those nodes that are descendants of the Node to which its containing
67  Symbol Table belongs.
68 
69  :param str name: name of the symbol.
70  :param visibility: the visibility of the symbol.
71  :type visibility: :py:class:`psyclone.psyir.symbols.Symbol.Visibility`
72  :param interface: optional object describing the interface to this \
73  symbol (i.e. whether it is passed as a routine argument or \
74  accessed in some other way). Defaults to \
75  :py:class:`psyclone.psyir.symbols.AutomaticInterface`
76  :type interface: Optional[ \
77  :py:class:`psyclone.psyir.symbols.symbol.SymbolInterface`]
78 
79  :raises TypeError: if the name is not a str.
80 
81  '''
82 
83  class Visibility(Enum):
84  ''' Enumeration of the different visibility attributes supported in
85  the PSyIR. If no visibility information is supplied for a Symbol then
86  it is given the DEFAULT_VISIBILITY value.
87 
88  PUBLIC: the symbol is visible in any scoping region that has access to
89  the SymbolTable containing it.
90  PRIVATE: the symbol is only visibile inside the scoping region that
91  contains the SymbolTable to which it belongs.
92  '''
93  PUBLIC = 1
94  PRIVATE = 2
95 
96  # The visibility given to any PSyIR symbols that are created without being
97  # given an explicit visibility.
98  DEFAULT_VISIBILITY = Visibility.PUBLIC
99 
100  def __init__(self, name, visibility=DEFAULT_VISIBILITY, interface=None):
101 
102  if not isinstance(name, str):
103  raise TypeError(
104  f"{type(self).__name__} 'name' attribute should be of type "
105  f"'str' but '{type(name).__name__}' found.")
106 
107  self._name = name
108 
109  # The following attributes have a setter method (with error checking)
110  self._visibility = None
111  self._interface = None
112 
113  self._process_arguments(visibility=visibility, interface=interface)
114 
115  def _process_arguments(self, visibility=None, interface=None):
116  ''' Process the visibility and interface arguments of the constructor
117  and the specialise methods.
118 
119  :param visibility: the visibility of the symbol.
120  :type visibility: :py:class:`psyclone.psyir.symbols.Symbol.Visibility`
121  :param interface: optional object describing the interface to this \
122  symbol (i.e. whether it is passed as a routine argument or \
123  accessed in some other way). Defaults to \
124  :py:class:`psyclone.psyir.symbols.AutomaticInterface`
125  :type interface: Optional[ \
126  :py:class:`psyclone.psyir.symbols.symbol.SymbolInterface`]
127 
128  '''
129  if interface:
130  self.interface = interface
131  elif not self.interface:
132  self.interface = AutomaticInterface()
133 
134  if visibility:
135  self.visibility = visibility
136 
137  def copy(self):
138  '''Create and return a copy of this object. Any references to the
139  original will not be affected so the copy will not be referred
140  to by any other object.
141 
142  :returns: A symbol object with the same properties as this \
143  symbol object.
144  :rtype: :py:class:`psyclone.psyir.symbols.Symbol`
145 
146  '''
147  # The constructors for all Symbol-based classes have 'name' as the
148  # first positional argument.
149  return type(self)(self.namename, visibility=self.visibilityvisibilityvisibilityvisibility,
150  interface=self.interfaceinterfaceinterfaceinterface.copy())
151 
152  def copy_properties(self, symbol_in):
153  '''Replace all properties in this object with the properties from
154  symbol_in, apart from the name (which is immutable) and visibility.
155 
156  :param symbol_in: the symbol from which the properties are copied.
157  :type symbol_in: :py:class:`psyclone.psyir.symbols.Symbol`
158 
159  :raises TypeError: if the argument is not the expected type.
160 
161  '''
162  if not isinstance(symbol_in, Symbol):
163  raise TypeError(f"Argument should be of type 'Symbol' but "
164  f"found '{type(symbol_in).__name__}'.")
165  self._interface_interface = symbol_in.interface
166 
167  def specialise(self, subclass, **kwargs):
168  '''Specialise this symbol so that it becomes an instance of the class
169  provided in the subclass argument. This allows this instance
170  to become a subclass without any references to it becoming
171  invalid.
172 
173  :param subclass: the class that this symbol will become.
174  :type subclass: type of sub-class of \
175  :py:class:`psyclone.psyir.symbols.Symbol`
176 
177  :raises TypeError: if subclass is not a sub-class of Symbol.
178 
179  '''
180  try:
181  is_subclass = issubclass(subclass, self.__class____class__)
182  except TypeError as info:
183  raise TypeError(
184  f"The specialise method in '{self.name}' expects the "
185  f"subclass argument to be a class.") from info
186  # pylint: disable = unidiomatic-typecheck
187  if not is_subclass or type(self) is subclass:
188  raise TypeError(
189  f"The specialise method in '{self.name}', an instance of "
190  f"'{type(self).__name__}', expects the subclass argument to "
191  f"be a subclass of '{type(self).__name__}', but found "
192  f"'{subclass.__name__}'.")
193  self.__class____class__ = subclass
194  self._process_arguments_process_arguments(**kwargs)
195 
196  # pylint: disable=inconsistent-return-statements
198  '''
199  Looks-up and returns the Symbol referred to by this Symbol's
200  Import Interface.
201 
202  :raises SymbolError: if the module pointed to by the symbol interface \
203  does not contain the symbol (or the symbol is \
204  not public).
205  :raises NotImplementedError: if this symbol does not have an \
206  ImportInterface.
207  '''
208  if not self.is_importis_import:
209  raise NotImplementedError(
210  f"Error trying to resolve symbol '{self.name}' properties, "
211  f"the lazy evaluation of '{self.interface}' interfaces is "
212  f"not supported.")
213 
214  module = self.interfaceinterfaceinterfaceinterface.container_symbol
215  try:
216  return module.container.symbol_table.lookup(
217  self.namename, visibility=self.VisibilityVisibility.PUBLIC)
218  except KeyError as kerr:
219  raise SymbolError(
220  f"Error trying to resolve the properties of symbol "
221  f"'{self.name}'. The interface points to module "
222  f"'{module.name}' but could not find the definition of "
223  f"'{self.name}' in that module.") from kerr
224  except SymbolError as err:
225  raise SymbolError(
226  f"Error trying to resolve the properties of symbol "
227  f"'{self.name}' in module '{module.name}': {err.value}") from err
228 
229  def resolve_type(self):
230  '''
231  Update the properties of this Symbol by using the definition imported
232  from the external Container. If this symbol does not have an
233  ImportInterface then there is no lookup needed and we just return this
234  symbol.
235 
236  :returns: a symbol object with the class and type determined by
237  examining the Container from which it is imported.
238  :rtype: subclass of :py:class:`psyclone.psyir.symbols.Symbol`
239 
240  '''
241  if self.is_importis_import:
242  extern_symbol = self.get_external_symbolget_external_symbol()
243  init_value = None
244  if extern_symbol.initial_value:
245  init_value = extern_symbol.initial_value.copy()
246  # Specialise the existing Symbol in-place so that all References
247  # to it remain valid.
248  self.specialisespecialise(type(extern_symbol),
249  datatype=extern_symbol.datatype,
250  is_constant=extern_symbol.is_constant,
251  initial_value=init_value)
252  return self
253 
254  @property
255  def name(self):
256  '''
257  :returns: name of the Symbol.
258  :rtype: str
259  '''
260  return self._name_name
261 
262  @property
263  def visibility(self):
264  '''
265  :returns: the visibility of this Symbol.
266  :rtype: :py:class:`psyclone.psyir.symbol.Symbol.Visibility`
267  '''
268  return self._visibility_visibility
269 
270  @visibility.setter
271  def visibility(self, value):
272  '''
273  Setter for the visibility attribute.
274 
275  :raises TypeError: if the supplied value is not an instance of \
276  Symbol.Visibility.
277  '''
278  if not isinstance(value, Symbol.Visibility):
279  raise TypeError(
280  f"{type(self).__name__} 'visibility' attribute should be of "
281  f"type psyir.symbols.Symbol.Visibility but got "
282  f"'{type(value).__name__}'.")
283  self._visibility_visibility = value
284 
285  @property
286  def interface(self):
287  '''
288  :returns: the an object describing the interface to this Symbol.
289  :rtype: Sub-class of \
290  :py:class:`psyclone.psyir.symbols.symbol.SymbolInterface`
291  '''
292  return self._interface_interface
293 
294  @interface.setter
295  def interface(self, value):
296  '''
297  Setter for the Interface associated with this Symbol.
298 
299  :param value: an Interface object describing how the Symbol is \
300  accessed.
301  :type value: Sub-class of \
302  :py:class:`psyclone.psyir.symbols.symbol.SymbolInterface`
303 
304  :raises TypeError: if the supplied `value` is of the wrong type.
305  '''
306  if not isinstance(value, SymbolInterface):
307  raise TypeError(f"The interface to a Symbol must be a "
308  f"SymbolInterface but got "
309  f"'{type(value).__name__}'")
310  self._interface_interface = value
311 
312  @property
313  def is_automatic(self):
314  '''
315  :returns: whether the Symbol has an AutomaticInterface.
316  :rtype: bool
317 
318  '''
319  return isinstance(self._interface_interface, AutomaticInterface)
320 
321  @property
322  def is_modulevar(self):
323  '''
324  :returns: whether the Symbol has a DefaultModuleInterface.
325  :rtype: bool
326 
327  '''
328  return isinstance(self._interface_interface, DefaultModuleInterface)
329 
330  @property
331  def is_import(self):
332  '''
333  :returns: whether the Symbol has an ImportInterface.
334  :rtype: bool
335 
336  '''
337  return isinstance(self._interface_interface, ImportInterface)
338 
339  @property
340  def is_argument(self):
341  '''
342  :returns: whether the Symbol has an ArgumentInterface.
343  :rtype: bool
344 
345  '''
346  return isinstance(self._interface_interface, ArgumentInterface)
347 
348  @property
349  def is_commonblock(self):
350  '''
351  :returns: whether the Symbol has a CommonBlockInterface.
352  :rtype: bool
353 
354  '''
355  return isinstance(self._interface_interface, CommonBlockInterface)
356 
357  @property
358  def is_static(self):
359  '''
360  :returns: whether the Symbol has a StaticInterface.
361  :rtype: bool
362 
363  '''
364  return isinstance(self._interface_interface, StaticInterface)
365 
366  @property
367  def is_unresolved(self):
368  '''
369  :returns: whether the Symbol has an UnresolvedInterface.
370  :rtype: bool
371 
372  '''
373  return isinstance(self._interface_interface, UnresolvedInterface)
374 
375  @property
377  '''
378  :returns: whether the Symbol has an UnknownInterface.
379  :rtype: bool
380 
381  '''
382  return isinstance(self._interface_interface, UnknownInterface)
383 
384  def find_symbol_table(self, node):
385  '''
386  Searches back up the PSyIR tree for the SymbolTable that contains
387  this Symbol.
388 
389  :param node: the PSyIR node from which to search.
390  :type node: :py:class:`psyclone.psyir.nodes.Node`
391 
392  :returns: the SymbolTable containing this Symbol or None.
393  :rtype: :py:class:`psyclone.psyir.symbols.SymbolTable` or NoneType
394 
395  :raises TypeError: if the supplied `node` argument is not a PSyIR Node.
396 
397  '''
398  # This import has to be local to this method to avoid circular
399  # dependencies.
400  # pylint: disable=import-outside-toplevel
401  from psyclone.psyir.nodes import Node
402  if not isinstance(node, Node):
403  raise TypeError(
404  f"find_symbol_table: expected to be passed an instance of "
405  f"psyir.nodes.Node but got '{type(node).__name__}'")
406 
407  try:
408  current = node.scope.symbol_table
409  while current:
410  if self.namename in current:
411  return current
412  if current.node.parent:
413  current = current.node.parent.scope.symbol_table
414  else:
415  # We can't go any further up the hierarchy and we haven't
416  # found a SymbolTable that contains this symbol.
417  return None
418  except SymbolError:
419  # Failed to find any enclosing symbol table
420  return None
421 
422  def __str__(self):
423  return f"{self.name}: Symbol<{self.interface}>"
424 
425  @property
426  def is_array(self):
427  '''
428  :returns: True if this symbol is an array and False if it is not or
429  there is not enough symbol information to determine it.
430  :rtype: bool
431 
432  '''
433  return False
434 
435  def is_array_access(self, index_variable=None, access_info=None):
436  '''This method detects if a variable is used as an array or not.
437  If available, it will use the access information, i.e. how the
438  variable is used (e.g. if it has indices somewhere, like `a(i)%b`).
439  This can be incorrect in case of implicit loops (e.g. `a=b+1`,
440  where `a` and `b` are arrays) where the variable usage information
441  will not have information about indices. In this case this function
442  will fallback to querying the symbol itself.
443 
444  This can cause significant slowdown if this symbol is imported
445  from a module, since then these modules need to be parsed.
446  # TODO #1213: Parsing modules is not yet supported.
447 
448  If a `loop_variable` is specified, a variable access will only be
449  considered an array access if the specified variable is used in
450  at least one of the indices. For example:
451  >>> do i=1, n
452  >>> a(j) = 2
453 
454  the access to `a` is not considered an array if `loop_variable` is
455  set to `i`. If `loop_variable` is specified, `access_information`
456  must be specified.
457 
458  :param str index_variable: optional loop variable that is used to \
459  to determine if an access is an array access using this variable.
460  :param access_info: variable access information, optional.
461  :type access_info: \
462  :py:class:`psyclone.core.SingleVariableAccessInfo`
463 
464  :returns: if the variable is an array.
465  :rtype bool:
466 
467  :raises InternalError: if a loop_variable is specified, but no \
468  access information is given.
469 
470  '''
471  # TODO #1270: this function might either be better off elsewhere,
472  # or even do not implement one function that uses both access
473  # information and symbol table - if required, the user can
474  # query both in two simple statements anyway.
475  if index_variable and not access_info:
476  raise InternalError(f"In Symbol.is_array_access: index variable "
477  f"'{index_variable}' specified, but no access "
478  f"information given.")
479 
480  # TODO #1244: If as a result of 1244 we have more reliable
481  # information in the symbol table, the implementation here might
482  # be changed.
483 
484  # Prioritise access information, since this can take the index
485  # variable into account, and avoids potentially parsing other
486  # modules to find type information.
487  if access_info:
488  # Access Info might not have information if a variable is used
489  # as array (e.g. in case of an array expression). In this case
490  # we still need to check the type information in the symbol table.
491  is_array = access_info.is_array(index_variable=index_variable)
492 
493  # Access information might indicate that a variable is not an
494  # array if it is used in array expressions only. In order to
495  # verify, we need to check the symbol type information. But
496  # if an index variable is specified, an array used in an array
497  # expression without the index variable is not considered to
498  # be an array access, since it is independent of the loop
499  # variable. In this case also return the value from `is_array`.
500  if is_array or index_variable is not None:
501  return is_array
502 
503  # Either we don't have access information, or the access information
504  # does not indicate an array. In the latter case we still need to
505  # test the symbol table, since the variable might be used in array
506  # expressions only. Note that we cannot check for index variable usage
507  # in this case.
508  # TODO #1213: check for wildcard imports
509  return self.is_arrayis_array
def specialise(self, subclass, **kwargs)
Definition: symbol.py:167
def is_array_access(self, index_variable=None, access_info=None)
Definition: symbol.py:435
def _process_arguments(self, visibility=None, interface=None)
Definition: symbol.py:115
def find_symbol_table(self, node)
Definition: symbol.py:384
def copy_properties(self, symbol_in)
Definition: symbol.py:152