Reference Guide  2.5.0
datatypes.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2019-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 # Modified J. Henrichs, Bureau of Meteorology
36 # -----------------------------------------------------------------------------
37 
38 ''' This module contains the datatype definitions.'''
39 
40 import abc
41 from collections import OrderedDict, namedtuple
42 from enum import Enum
43 
44 from psyclone.errors import InternalError
45 from psyclone.psyir.symbols.data_type_symbol import DataTypeSymbol
46 from psyclone.psyir.symbols.datasymbol import DataSymbol
47 from psyclone.psyir.symbols.symbol import Symbol
48 
49 
50 class DataType(metaclass=abc.ABCMeta):
51  '''Abstract base class from which all types are derived.'''
52 
53  @abc.abstractmethod
54  def __str__(self):
55  '''
56  :returns: a description of this type.
57  :rtype: str
58 
59  '''
60 
61  def __eq__(self, other):
62  '''
63  :param Any other: the object to check equality to.
64 
65  :returns: whether this type is equal to the 'other' type.
66  :rtype: bool
67  '''
68  return type(other) is type(self)
69 
70 
72  # pylint: disable=too-few-public-methods
73  ''' Indicates that the type declaration has not been found yet. '''
74 
75  def __str__(self):
76  return "UnresolvedType"
77 
78 
80  # pylint: disable=too-few-public-methods
81  ''' Indicates that the associated symbol has an empty type (equivalent
82  to `void` in C). '''
83 
84  def __str__(self):
85  return "NoType"
86 
87 
88 class UnsupportedType(DataType, metaclass=abc.ABCMeta):
89  '''
90  Indicates that a variable declaration is not supported by the PSyIR.
91  This class is abstract and must be subclassed for each language
92  supported by the PSyIR frontends.
93 
94  :param str declaration_txt: the original textual declaration of
95  the symbol.
96 
97  :raises TypeError: if the supplied declaration_txt is not a str.
98 
99  '''
100  def __init__(self, declaration_txt):
101  if not isinstance(declaration_txt, str):
102  raise TypeError(
103  f"UnsupportedType constructor expects the original variable "
104  f"declaration as a string but got an argument of type "
105  f"'{type(declaration_txt).__name__}'")
106  self._declaration_declaration = declaration_txt
107 
108  @abc.abstractmethod
109  def __str__(self):
110  ''' Abstract method that must be implemented in subclass. '''
111 
112  # Note, an UnsupportedType is immutable so a declaration setter is not
113  # allowed. This is to allow subclasses to extract and provide
114  # parts of the declaration without worrying about their values
115  # becoming invalid due to a change in the original declaration.
116  @property
117  def declaration(self):
118  '''
119  :returns: the original declaration of the symbol. This is obviously \
120  language specific and hence this class must be subclassed.
121  :rtype: str
122  '''
123  return self._declaration_declaration
124 
125 
127  '''Indicates that a Fortran declaration is not supported by the PSyIR.
128 
129  :param str declaration_txt: string containing the original variable
130  declaration.
131  :param partial_datatype: a PSyIR datatype representing the subset
132  of type attributes that are supported.
133  :type partial_datatype: Optional[
134  :py:class:`psyclone.psyir.symbols.DataType` or
135  :py:class:`psyclone.psyir.symbols.DataTypeSymbol`]
136 
137  '''
138  def __init__(self, declaration_txt, partial_datatype=None):
139  super().__init__(declaration_txt)
140  # This will hold the Fortran type specification (as opposed to
141  # the whole declaration).
142  self._type_text_type_text = ""
143  if not isinstance(
144  partial_datatype, (type(None), DataType, DataTypeSymbol)):
145  raise TypeError(
146  f"partial_datatype argument in UnsupportedFortranType "
147  f"initialisation should be a DataType, DataTypeSymbol, or "
148  f"NoneType, but found '{type(partial_datatype).__name__}'.")
149  # This holds a subset of the type in a datatype if it is
150  # possible to determine enough information to create one.
151  self._partial_datatype_partial_datatype = partial_datatype
152 
153  def __str__(self):
154  return f"UnsupportedFortranType('{self._declaration}')"
155 
156  @property
157  def partial_datatype(self):
158  '''
159  :returns: partial datatype information if it can be determined,
160  else None.
161  :rtype: Optional[:py:class:`psyclone.symbols.DataType`]
162  '''
163  return self._partial_datatype_partial_datatype
164 
165  @property
166  def type_text(self):
167  '''
168  Parses the original Fortran declaration and uses the resulting
169  parse tree to extract the type information. This is returned in
170  text form and also cached.
171 
172  TODO #2137 - alter Unsupported(Fortran)Type so that it is only the
173  type information that is stored as a string. i.e. remove the name
174  of the variable being declared. Once that is done this method
175  won't be required.
176 
177  Note that UnsupportedFortranType is also used to hold things like
178  'SAVE :: /my_common/' and thus it is not always possible/appropriate
179  to extract a type expression.
180 
181  :returns: the Fortran code specifying the type.
182  :rtype: str
183 
184  :raises NotImplementedError: if declaration text cannot be
185  extracted from the original Fortran declaration.
186 
187  '''
188  if self._type_text_type_text:
189  return self._type_text_type_text
190 
191  # Encapsulate fparser2 functionality here.
192  # pylint:disable=import-outside-toplevel
193  from fparser.common.readfortran import FortranStringReader
194  from fparser.common.sourceinfo import FortranFormat
195  from fparser.two import Fortran2003
196  from fparser.two.parser import ParserFactory
197  string_reader = FortranStringReader(self._declaration_declaration)
198  # Set reader to free format.
199  string_reader.set_format(FortranFormat(True, False))
200  ParserFactory().create(std="f2008")
201  try:
202  ptree = Fortran2003.Specification_Part(
203  string_reader)
204  except Exception as err:
205  raise NotImplementedError(
206  f"Cannot extract the declaration part from UnsupportedFortran"
207  f"Type '{self._declaration}' because parsing (attempting to "
208  f"match a Fortran2003.Specification_Part) failed.") from err
209  node = ptree.children[0]
210  if isinstance(node, (Fortran2003.Declaration_Construct,
211  Fortran2003.Type_Declaration_Stmt)):
212  self._type_text_type_text = str(node.children[0])
213  elif isinstance(node, Fortran2003.Save_Stmt):
214  self._type_text_type_text = "SAVE"
215  elif isinstance(node, Fortran2003.Common_Stmt):
216  self._type_text_type_text = "COMMON"
217  else:
218  raise NotImplementedError(
219  f"Cannot extract the declaration part from UnsupportedFortran"
220  f"Type '{self._declaration}'. Only Declaration_Construct, "
221  f"Type_Declaration_Stmt, Save_Stmt and Common_Stmt are "
222  f"supported but got '{type(node).__name__}' from the parser.")
223  return self._type_text_type_text
224 
225  def __eq__(self, other):
226  '''
227  :param Any other: the object to check equality to.
228 
229  :returns: whether this type is equal to the 'other' type.
230  :rtype: bool
231  '''
232  if not super().__eq__(other):
233  return False
234 
235  return other.type_text == self.type_texttype_text
236 
237 
239  '''Describes a scalar datatype (and its precision).
240 
241  :param intrinsic: the intrinsic of this scalar type.
242  :type intrinsic: :py:class:`pyclone.psyir.datatypes.ScalarType.Intrinsic`
243  :param precision: the precision of this scalar type.
244  :type precision: :py:class:`psyclone.psyir.symbols.ScalarType.Precision`, \
245  int or :py:class:`psyclone.psyir.symbols.DataSymbol`
246 
247  :raises TypeError: if any of the arguments are of the wrong type.
248  :raises ValueError: if any of the argument have unexpected values.
249 
250  '''
251 
252  class Intrinsic(Enum):
253  '''Enumeration of the different intrinsic scalar datatypes that are
254  supported by the PSyIR.
255 
256  '''
257  INTEGER = 1
258  REAL = 2
259  BOOLEAN = 3
260  CHARACTER = 4
261 
262  class Precision(Enum):
263  '''Enumeration of the different types of 'default' precision that may
264  be specified for a scalar datatype.
265 
266  '''
267  SINGLE = 1
268  DOUBLE = 2
269  UNDEFINED = 3
270 
271  def __init__(self, intrinsic, precision):
272  if not isinstance(intrinsic, ScalarType.Intrinsic):
273  raise TypeError(
274  f"ScalarType expected 'intrinsic' argument to be of type "
275  f"ScalarType.Intrinsic but found "
276  f"'{type(intrinsic).__name__}'.")
277  if not isinstance(precision, (int, ScalarType.Precision, DataSymbol)):
278  raise TypeError(
279  f"ScalarType expected 'precision' argument to be of type "
280  f"int, ScalarType.Precision or DataSymbol, but found "
281  f"'{type(precision).__name__}'.")
282  if isinstance(precision, int) and precision <= 0:
283  raise ValueError(
284  f"The precision of a DataSymbol when specified as an integer "
285  f"number of bytes must be > 0 but found '{precision}'.")
286  if (isinstance(precision, DataSymbol) and
287  not (isinstance(precision.datatype, ScalarType) and
288  precision.datatype.intrinsic ==
289  ScalarType.Intrinsic.INTEGER) and
290  not isinstance(precision.datatype, UnresolvedType)):
291  raise ValueError(
292  f"A DataSymbol representing the precision of another "
293  f"DataSymbol must be of either 'unresolved' or scalar, "
294  f"integer type but got: {precision}")
295 
296  self._intrinsic = intrinsic
297  self._precision = precision
298 
299  @property
300  def intrinsic(self):
301  '''
302  :returns: the intrinsic used by this scalar type.
303  :rtype: :py:class:`pyclone.psyir.datatypes.ScalarType.Intrinsic`
304  '''
305  return self._intrinsic_intrinsic
306 
307  @property
308  def precision(self):
309  '''
310  :returns: the precision of this scalar type.
311  :rtype: :py:class:`psyclone.psyir.symbols.ScalarType.Precision`, \
312  int or :py:class:`psyclone.psyir.symbols.DataSymbol`
313  '''
314  return self._precision_precision
315 
316  def __str__(self):
317  '''
318  :returns: a description of this scalar datatype.
319  :rtype: str
320 
321  '''
322  if isinstance(self.precisionprecisionprecision, ScalarType.Precision):
323  precision_str = self.precisionprecisionprecision.name
324  else:
325  precision_str = str(self.precisionprecisionprecision)
326  return f"Scalar<{self.intrinsic.name}, {precision_str}>"
327 
328  def __eq__(self, other):
329  '''
330  :param Any other: the object to check equality to.
331 
332  :returns: whether this type is equal to the 'other' type.
333  :rtype: bool
334  '''
335  if not super().__eq__(other):
336  return False
337 
338  return (self.precisionprecisionprecision == other.precision and
339  self.intrinsicintrinsicintrinsic == other.intrinsic)
340 
341 
343  '''Describes an array datatype. Can be an array of intrinsic types (e.g.
344  integer) or of structure types. For the latter, the type must currently be
345  specified as a DataTypeSymbol (see #1031).
346 
347  :param datatype: the datatype of the array elements.
348  :type datatype: :py:class:`psyclone.psyir.datatypes.DataType` or \
349  :py:class:`psyclone.psyir.symbols.DataTypeSymbol`
350  :param list shape: shape of the symbol in column-major order (leftmost \
351  index is contiguous in memory). Each entry represents an array \
352  dimension. If it is ArrayType.Extent.ATTRIBUTE the extent of that \
353  dimension is unknown but can be obtained by querying the run-time \
354  system (e.g. using the SIZE intrinsic in Fortran). If it is \
355  ArrayType.Extent.DEFERRED then the extent is also unknown and may or \
356  may not be defined at run-time (e.g. the array is ALLOCATABLE in \
357  Fortran). Otherwise it can be an int or a DataNode (that returns an \
358  int) or a 2-tuple of such quantities. If only a single value is \
359  provided then that is taken to be the upper bound and the lower bound \
360  defaults to 1. If a 2-tuple is provided then the two members specify \
361  the lower and upper bounds, respectively, of the current dimension. \
362  Note that providing an int is supported as a convenience, the provided\
363  value will be stored internally as a Literal node.
364 
365  :raises TypeError: if the arguments are of the wrong type.
366  :raises NotImplementedError: if a structure type does not have a \
367  DataTypeSymbol as its type.
368  '''
369  class Extent(Enum):
370  '''
371  Enumeration of array shape extents that are unspecified at compile
372  time. When the extent must exist and is accessible via the run-time
373  system it is an 'ATTRIBUTE'. When it may or may not be defined in the
374  current scope (e.g. the array may need to be allocated/malloc'd) it
375  is 'DEFERRED'.
376 
377  '''
378  DEFERRED = 1
379  ATTRIBUTE = 2
380 
381  #: namedtuple used to store lower and upper limits of an array dimension
382  ArrayBounds = namedtuple("ArrayBounds", ["lower", "upper"])
383 
384  def __init__(self, datatype, shape):
385 
386  # This import must be placed here to avoid circular dependencies.
387  # pylint: disable=import-outside-toplevel
388  from psyclone.psyir.nodes import Literal, DataNode, Assignment
389 
390  def _node_from_int(var):
391  ''' Helper routine that simply creates a Literal out of an int.
392  If the supplied arg is not an int then it is returned unchanged.
393 
394  :param var: variable for which to create a Literal if necessary.
395  :type var: int | :py:class:`psyclone.psyir.nodes.DataNode` | Extent
396 
397  :returns: the variable with ints converted to DataNodes.
398  :rtype: :py:class:`psyclone.psyir.nodes.DataNode` | Extent
399 
400  '''
401  if isinstance(var, int):
402  return Literal(str(var), INTEGER_TYPE)
403  return var
404 
405  def _dangling_parent(var):
406  ''' Helper routine that copies and adds a dangling parent
407  Assignment to a given node, this implicitly guarantees that the
408  node is not attached anywhere else (and is unexpectedly modified)
409  and also makes it behave like other nodes (e.g. calls inside an
410  expression do not have the "call" keyword in Fortran)
411 
412  :param var: variable with a dangling parent if necessary.
413  :type var: int | :py:class:`psyclone.psyir.nodes.DataNode` | Extent
414 
415  :returns: the variable with dangling parent when necessary.
416  :rtype: :py:class:`psyclone.psyir.nodes.DataNode` | Extent
417  '''
418  if isinstance(var, DataNode):
419  parent = Assignment()
420  parent.addchild(var.copy())
421  return parent.children[0]
422  return var
423 
424  if isinstance(datatype, DataType):
425  if isinstance(datatype, StructureType):
426  # TODO #1031 remove this restriction.
427  raise NotImplementedError(
428  "When creating an array of structures, the type of "
429  "those structures must be supplied as a DataTypeSymbol "
430  "but got a StructureType instead.")
431  if not isinstance(datatype, (UnsupportedType, UnresolvedType)):
432  self._intrinsic = datatype.intrinsic
433  self._precision = datatype.precision
434  else:
435  self._intrinsic = datatype
436  self._precision = None
437  elif isinstance(datatype, DataTypeSymbol):
438  self._intrinsic = datatype
439  self._precision = None
440  else:
441  raise TypeError(
442  f"ArrayType expected 'datatype' argument to be of type "
443  f"DataType or DataTypeSymbol but found "
444  f"'{type(datatype).__name__}'.")
445  # We do not have a setter for shape as it is an immutable property,
446  # therefore we have a separate validation routine.
447  self._validate_shape(shape)
448  # Replace any ints in shape with a Literal. int's are only supported
449  # as they allow a more concise dimension declaration.
450  self._shape = []
451  one = Literal("1", INTEGER_TYPE)
452  for dim in shape:
453  if isinstance(dim, (DataNode, int)):
454  # The lower bound is 1 by default.
455  self._shape.append(
456  ArrayType.ArrayBounds(
457  _dangling_parent(one),
458  _dangling_parent(_node_from_int(dim))))
459  elif isinstance(dim, tuple):
460  self._shape.append(
461  ArrayType.ArrayBounds(
462  _dangling_parent(_node_from_int(dim[0])),
463  _dangling_parent(_node_from_int(dim[1]))))
464  else:
465  self._shape.append(dim)
466 
467  self._datatype = datatype
468 
469  @property
470  def datatype(self):
471  '''
472  :returns: the datatype of each element in the array.
473  :rtype: :py:class:`psyclone.psyir.symbols.DataSymbol`
474  '''
475  # TODO #1857: This property might be affected.
476  return self._datatype_datatype
477 
478  @property
479  def intrinsic(self):
480  '''
481  :returns: the intrinsic type of each element in the array.
482  :rtype: :py:class:`pyclone.psyir.datatypes.ScalarType.Intrinsic` or \
483  :py:class:`psyclone.psyir.symbols.DataSymbol`
484  '''
485  return self._intrinsic_intrinsic
486 
487  @property
488  def precision(self):
489  '''
490  :returns: the precision of each element in the array.
491  :rtype: :py:class:`psyclone.psyir.symbols.ScalarType.Precision`, \
492  int or :py:class:`psyclone.psyir.symbols.DataSymbol`
493  '''
494  return self._precision_precision
495 
496  @property
497  def shape(self):
498  '''
499  :returns: the (validated) shape of the symbol in column-major order \
500  (leftmost index is contiguous in memory) with each entry \
501  representing an array dimension.
502 
503  :rtype: a list of ArrayType.Extent.ATTRIBUTE, \
504  ArrayType.Extent.DEFERRED, or \
505  :py:class:`psyclone.psyir.nodes.ArrayType.ArrayBounds`. If an \
506  entry is ArrayType.Extent.ATTRIBUTE the extent of that dimension \
507  is unknown but can be obtained by querying the run-time \
508  system (e.g. using the SIZE intrinsic in Fortran). If it \
509  is ArrayType.Extent.DEFERRED then the extent is also \
510  unknown and may or may not be defined at run-time \
511  (e.g. the array is ALLOCATABLE in Fortran). Otherwise an \
512  entry is a DataNode that returns an int.
513 
514  '''
515  self._validate_shape_validate_shape(self._shape_shape)
516  return self._shape_shape
517 
518  def _validate_shape(self, extents):
519  '''Check that the supplied shape specification is valid. This is not
520  implemented as a setter because the shape property is immutable.
521 
522  :param extents: list of extents, one for each array dimension.
523  :type extents: List[
524  :py:class:`psyclone.psyir.symbols.ArrayType.Extent` | int
525  | :py:class:`psyclone.psyir.nodes.DataNode` |
526  Tuple[int | :py:class:`psyclone.psyir.nodes.DataNode |
527  :py:class:`psyclone.psyir.symbols.ArrayType.Extent]]
528 
529  :raises TypeError: if extents is not a list.
530  :raises TypeError: if one or more of the supplied extents is a
531  DataSymbol that is not a scalar integer or of
532  Unsupported/UnresolvedType.
533  :raises TypeError: if one or more of the supplied extents is not an
534  int, ArrayType.Extent or DataNode.
535  :raises TypeError: if the extents contain an invalid combination of
536  ATTRIBUTE/DEFERRED and known limits.
537 
538  '''
539  # This import must be placed here to avoid circular
540  # dependencies.
541  # pylint: disable=import-outside-toplevel
542  from psyclone.psyir.nodes import DataNode, Reference
543 
544  def _validate_data_node(dim_node, is_lower_bound=False):
545  '''
546  Checks that the supplied DataNode is valid as part of an
547  array-shape specification.
548 
549  :param dim_node: the node to check for validity.
550  :type dim_node: int | :py:class:`psyclone.psyir.nodes.DataNode`
551  :param bool is_lower_bound: whether the supplied node represents \
552  the lower bound of an array shape.
553 
554  :raises TypeError: if the DataNode is not valid in this context.
555 
556  '''
557  if isinstance(dim_node, int):
558  # An integer value is always valid.
559  return
560 
561  # When issue #1799 is addressed then check that the
562  # datatype returned is an int (or is Unsupported). For the
563  # moment, just check that if the DataNode is a
564  # Reference then the associated symbol is a scalar
565  # integer or is Unsupported.
566  if isinstance(dim_node, Reference):
567  # Check the DataSymbol instance is a scalar
568  # integer or is unsupported
569  dtype = dim_node.datatype
570  if isinstance(dtype, ArrayType) and dtype.shape:
571  raise TypeError(
572  f"If a DataSymbol is referenced in a dimension "
573  f"declaration then it should be a scalar but "
574  f"'{dim_node}' is not.")
575  if not (isinstance(dtype, (UnsupportedType, UnresolvedType)) or
576  dtype.intrinsic == ScalarType.Intrinsic.INTEGER):
577  raise TypeError(
578  f"If a DataSymbol is referenced in a dimension "
579  f"declaration then it should be an integer or "
580  f"of UnsupportedType or UnresolvedType, but "
581  f"'{dim_node.name}' is a '{dtype}'.")
582  # TODO #1089 - add check that any References are not to a
583  # local datasymbol that is not constant (as this would have
584  # no value).
585  return
586 
587  if isinstance(dim_node, ArrayType.Extent):
588  if is_lower_bound:
589  raise TypeError(
590  "If present, the lower bound in an ArrayType 'shape' "
591  "must represent a value but found ArrayType.Extent")
592  return
593 
594  if isinstance(dim_node, DataNode):
595  return
596 
597  raise TypeError(
598  f"ArrayType shape-list elements can only be 'int', "
599  f"ArrayType.Extent, 'DataNode' or a 2-tuple thereof but found "
600  f"'{type(dim_node).__name__}'.")
601 
602  # ---------------------------------------------------------------------
603  if not isinstance(extents, list):
604  raise TypeError(
605  f"ArrayType 'shape' must be of type list but found "
606  f"'{type(extents).__name__}'.")
607 
608  for dimension in extents:
609  if isinstance(dimension, tuple):
610  if len(dimension) != 2:
611  raise TypeError(
612  f"An ArrayType shape-list element specifying lower "
613  f"and upper bounds must be a 2-tuple but "
614  f"'{dimension}' has {len(dimension)} entries.")
615  _validate_data_node(dimension[0], is_lower_bound=True)
616  _validate_data_node(dimension[1])
617  else:
618  _validate_data_node(dimension)
619 
620  if ArrayType.Extent.DEFERRED in extents:
621  if not all(dim == ArrayType.Extent.DEFERRED
622  for dim in extents):
623  raise TypeError(
624  f"A declaration of an allocatable array must have"
625  f" the extent of every dimension as 'DEFERRED' but "
626  f"found shape: {extents}.")
627 
628  if ArrayType.Extent.ATTRIBUTE in extents:
629  # If we have an 'assumed-shape' array then *every*
630  # dimension must have an 'ATTRIBUTE' extent
631  for dim in extents:
632  if not (dim == ArrayType.Extent.ATTRIBUTE or
633  (isinstance(dim, tuple) and
634  dim[-1] == ArrayType.Extent.ATTRIBUTE)):
635  raise TypeError(
636  f"An assumed-shape array must have every "
637  f"dimension unspecified (either as 'ATTRIBUTE' or "
638  f"with the upper bound as 'ATTRIBUTE') but found "
639  f"shape: {extents}.")
640 
641  def __str__(self):
642  '''
643  :returns: a description of this array datatype. If the lower bound \
644  of any dimension has the default value of 1 then it is omitted \
645  for the sake of brevity.
646  :rtype: str
647 
648  :raises InternalError: if the shape of this datatype contains \
649  any elements that aren't ArrayBounds or ArrayType.Extent objects.
650 
651  '''
652  dims = []
653  for dimension in self.shapeshape:
654  if isinstance(dimension, ArrayType.ArrayBounds):
655  dim_text = ""
656  # Have to import locally to avoid circular dependence
657  # pylint: disable=import-outside-toplevel
658  from psyclone.psyir.nodes import Literal
659  # Lower bound. If it is "1" then we omit it.
660  if isinstance(dimension.lower, Literal):
661  if dimension.lower.value != "1":
662  dim_text = dimension.lower.value + ":"
663  else:
664  dim_text = str(dimension.lower) + ":"
665  # Upper bound.
666  if isinstance(dimension.upper, Literal):
667  dim_text += dimension.upper.value
668  else:
669  dim_text += str(dimension.upper)
670  dims.append(dim_text)
671  elif isinstance(dimension, ArrayType.Extent):
672  dims.append(f"'{dimension.name}'")
673  else:
674  raise InternalError(
675  f"Once constructed, every member of an ArrayType shape-"
676  f"list should either be an ArrayBounds object or an "
677  f"instance of ArrayType.Extent but found "
678  f"'{type(dimension).__name__}'")
679 
680  return f"Array<{self._datatype}, shape=[{', '.join(dims)}]>"
681 
682  def __eq__(self, other):
683  '''
684  :param Any other: the object to check equality to.
685 
686  :returns: whether this ArrayType is equal to the 'other' ArrayType.
687  :rtype: bool
688  '''
689  if not super().__eq__(other):
690  return False
691 
692  if (self.intrinsicintrinsic != other.intrinsic or
693  self.precisionprecision != other.precision):
694  return False
695 
696  if len(self.shapeshape) != len(other.shape):
697  return False
698 
699  # TODO #1799 - this implementation currently has some limitations.
700  # e.g. a(1:10) and b(2:11) have the same datatype (an array of 1
701  # dimension and 10 elements) but we will currently return false.
702  # One improvement could be to use the SymbolicMath to do the comparison
703  # but this won't resolve all cases as shape can be references.
704  for this_dim, other_dim in zip(self.shapeshape, other.shape):
705  if this_dim != other_dim:
706  return False
707 
708  return True
709 
710 
712  '''
713  Describes a 'structure' or 'derived' datatype that is itself composed
714  of a list of other datatypes. Those datatypes are stored as an
715  OrderedDict of namedtuples.
716 
717  Note, we could have chosen to use a SymbolTable to store the properties
718  of the constituents of the type. (Since they too have a name, a type,
719  and visibility.) If this class ends up duplicating a lot of the
720  SymbolTable functionality then this decision could be revisited.
721 
722  '''
723  # Each member of a StructureType is represented by a ComponentType
724  # (named tuple).
725  ComponentType = namedtuple("ComponentType", [
726  "name", "datatype", "visibility", "initial_value"])
727 
728  def __init__(self):
729  self._components_components = OrderedDict()
730 
731  def __str__(self):
732  return "StructureType<>"
733 
734  @staticmethod
735  def create(components):
736  '''
737  Creates a StructureType from the supplied list of properties.
738 
739  :param components: the name, type, visibility (whether public or
740  private) and initial value (if any) of each component.
741  :type components: List[tuple[
742  str,
743  :py:class:`psyclone.psyir.symbols.DataType` |
744  :py:class:`psyclone.psyir.symbols.DataTypeSymbol`,
745  :py:class:`psyclone.psyir.symbols.Symbol.Visibility`,
746  Optional[:py:class:`psyclone.psyir.symbols.DataNode`]
747  ]]
748 
749  :returns: the new type object.
750  :rtype: :py:class:`psyclone.psyir.symbols.StructureType`
751 
752  '''
753  stype = StructureType()
754  for component in components:
755  if len(component) != 4:
756  raise TypeError(
757  f"Each component must be specified using a 4-tuple of "
758  f"(name, type, visibility, initial_value) but found a "
759  f"tuple with {len(component)} members: {component}")
760  stype.add(*component)
761  return stype
762 
763  @property
764  def components(self):
765  '''
766  :returns: Ordered dictionary of the components of this type.
767  :rtype: :py:class:`collections.OrderedDict`
768  '''
769  return self._components_components
770 
771  def add(self, name, datatype, visibility, initial_value):
772  '''
773  Create a component with the supplied attributes and add it to
774  this StructureType.
775 
776  :param str name: the name of the new component.
777  :param datatype: the type of the new component.
778  :type datatype: :py:class:`psyclone.psyir.symbols.DataType` |
779  :py:class:`psyclone.psyir.symbols.DataTypeSymbol`
780  :param visibility: whether this component is public or private.
781  :type visibility: :py:class:`psyclone.psyir.symbols.Symbol.Visibility`
782  :param initial_value: the initial value of the new component.
783  :type initial_value: Optional[
784  :py:class:`psyclone.psyir.nodes.DataNode`]
785 
786  :raises TypeError: if any of the supplied values are of the wrong type.
787 
788  '''
789  # This import must be placed here to avoid circular
790  # dependencies.
791  # pylint: disable=import-outside-toplevel
792  from psyclone.psyir.nodes import DataNode
793  if not isinstance(name, str):
794  raise TypeError(
795  f"The name of a component of a StructureType must be a 'str' "
796  f"but got '{type(name).__name__}'")
797  if not isinstance(datatype, (DataType, DataTypeSymbol)):
798  raise TypeError(
799  f"The type of a component of a StructureType must be a "
800  f"'DataType' or 'DataTypeSymbol' but got "
801  f"'{type(datatype).__name__}'")
802  if not isinstance(visibility, Symbol.Visibility):
803  raise TypeError(
804  f"The visibility of a component of a StructureType must be "
805  f"an instance of 'Symbol.Visibility' but got "
806  f"'{type(visibility).__name__}'")
807  if datatype is self:
808  # A StructureType cannot contain a component of its own type
809  raise TypeError(
810  f"Error attempting to add component '{name}' - a "
811  f"StructureType definition cannot be recursive - i.e. it "
812  f"cannot contain components with the same type as itself.")
813  if (initial_value is not None and
814  not isinstance(initial_value, DataNode)):
815  raise TypeError(
816  f"The initial value of a component of a StructureType must "
817  f"be None or an instance of 'DataNode', but got "
818  f"'{type(initial_value).__name__}'.")
819 
820  self._components_components[name] = self.ComponentTypeComponentType(
821  name, datatype, visibility, initial_value)
822 
823  def lookup(self, name):
824  '''
825  :returns: the ComponentType tuple describing the named member of this \
826  StructureType.
827  :rtype: :py:class:`psyclone.psyir.symbols.StructureType.ComponentType`
828  '''
829  return self._components_components[name]
830 
831  def __eq__(self, other):
832  '''
833  :param Any other: the object to check equality to.
834 
835  :returns: whether this StructureType is equal to the 'other' type.
836  :rtype: bool
837  '''
838  if not super().__eq__(other):
839  return False
840 
841  if len(self.componentscomponents) != len(other.components):
842  return False
843 
844  if self.componentscomponents != other.components:
845  return False
846 
847  return True
848 
849 
850 # Create common scalar datatypes
851 REAL_TYPE = ScalarType(ScalarType.Intrinsic.REAL,
852  ScalarType.Precision.UNDEFINED)
853 REAL_SINGLE_TYPE = ScalarType(ScalarType.Intrinsic.REAL,
854  ScalarType.Precision.SINGLE)
855 REAL_DOUBLE_TYPE = ScalarType(ScalarType.Intrinsic.REAL,
856  ScalarType.Precision.DOUBLE)
857 REAL4_TYPE = ScalarType(ScalarType.Intrinsic.REAL, 4)
858 REAL8_TYPE = ScalarType(ScalarType.Intrinsic.REAL, 8)
859 INTEGER_TYPE = ScalarType(ScalarType.Intrinsic.INTEGER,
860  ScalarType.Precision.UNDEFINED)
861 INTEGER_SINGLE_TYPE = ScalarType(ScalarType.Intrinsic.INTEGER,
862  ScalarType.Precision.SINGLE)
863 INTEGER_DOUBLE_TYPE = ScalarType(ScalarType.Intrinsic.INTEGER,
864  ScalarType.Precision.DOUBLE)
865 INTEGER4_TYPE = ScalarType(ScalarType.Intrinsic.INTEGER, 4)
866 INTEGER8_TYPE = ScalarType(ScalarType.Intrinsic.INTEGER, 8)
867 BOOLEAN_TYPE = ScalarType(ScalarType.Intrinsic.BOOLEAN,
868  ScalarType.Precision.UNDEFINED)
869 CHARACTER_TYPE = ScalarType(ScalarType.Intrinsic.CHARACTER,
870  ScalarType.Precision.UNDEFINED)
871 
872 # Mapping from PSyIR scalar data types to intrinsic Python types
873 # ignoring precision.
874 TYPE_MAP_TO_PYTHON = {ScalarType.Intrinsic.INTEGER: int,
875  ScalarType.Intrinsic.CHARACTER: str,
876  ScalarType.Intrinsic.BOOLEAN: bool,
877  ScalarType.Intrinsic.REAL: float}
878 
879 
880 # For automatic documentation generation
881 __all__ = ["UnsupportedType", "UnsupportedFortranType", "UnresolvedType",
882  "ScalarType", "ArrayType", "StructureType"]
def add(self, name, datatype, visibility, initial_value)
Definition: datatypes.py:771