38 ''' This module contains the datatype definitions.'''
41 from collections
import OrderedDict, namedtuple
51 '''Abstract base class from which all types are derived.'''
56 :returns: a description of this type.
63 :param Any other: the object to check equality to.
65 :returns: whether this type is equal to the 'other' type.
68 return type(other)
is type(self)
73 ''' Indicates that the type declaration has not been found yet. '''
76 return "UnresolvedType"
81 ''' Indicates that the associated symbol has an empty type (equivalent
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.
94 :param str declaration_txt: the original textual declaration of
97 :raises TypeError: if the supplied declaration_txt is not a str.
100 def __init__(self, declaration_txt):
101 if not isinstance(declaration_txt, str):
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__}'")
110 ''' Abstract method that must be implemented in subclass. '''
119 :returns: the original declaration of the symbol. This is obviously \
120 language specific and hence this class must be subclassed.
127 '''Indicates that a Fortran declaration is not supported by the PSyIR.
129 :param str declaration_txt: string containing the original variable
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`]
138 def __init__(self, declaration_txt, partial_datatype=None):
139 super().__init__(declaration_txt)
144 partial_datatype, (type(
None), DataType, DataTypeSymbol)):
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__}'.")
154 return f
"UnsupportedFortranType('{self._declaration}')"
159 :returns: partial datatype information if it can be determined,
161 :rtype: Optional[:py:class:`psyclone.symbols.DataType`]
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.
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
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.
181 :returns: the Fortran code specifying the type.
184 :raises NotImplementedError: if declaration text cannot be
185 extracted from the original Fortran declaration.
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)
199 string_reader.set_format(FortranFormat(
True,
False))
200 ParserFactory().create(std=
"f2008")
202 ptree = Fortran2003.Specification_Part(
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):
215 elif isinstance(node, Fortran2003.Common_Stmt):
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.")
227 :param Any other: the object to check equality to.
229 :returns: whether this type is equal to the 'other' type.
232 if not super().
__eq__(other):
235 return other.type_text == self.
type_texttype_text
239 '''Describes a scalar datatype (and its precision).
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`
247 :raises TypeError: if any of the arguments are of the wrong type.
248 :raises ValueError: if any of the argument have unexpected values.
253 '''Enumeration of the different intrinsic scalar datatypes that are
254 supported by the PSyIR.
263 '''Enumeration of the different types of 'default' precision that may
264 be specified for a scalar datatype.
271 def __init__(self, intrinsic, precision):
274 f
"ScalarType expected 'intrinsic' argument to be of type "
275 f
"ScalarType.Intrinsic but found "
276 f
"'{type(intrinsic).__name__}'.")
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:
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)):
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}")
296 self._intrinsic = intrinsic
297 self._precision = precision
302 :returns: the intrinsic used by this scalar type.
303 :rtype: :py:class:`pyclone.psyir.datatypes.ScalarType.Intrinsic`
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`
318 :returns: a description of this scalar datatype.
326 return f
"Scalar<{self.intrinsic.name}, {precision_str}>"
330 :param Any other: the object to check equality to.
332 :returns: whether this type is equal to the 'other' type.
335 if not super().
__eq__(other):
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).
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.
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.
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
382 ArrayBounds = namedtuple(
"ArrayBounds", [
"lower",
"upper"])
384 def __init__(self, datatype, shape):
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.
394 :param var: variable for which to create a Literal if necessary.
395 :type var: int | :py:class:`psyclone.psyir.nodes.DataNode` | Extent
397 :returns: the variable with ints converted to DataNodes.
398 :rtype: :py:class:`psyclone.psyir.nodes.DataNode` | Extent
401 if isinstance(var, int):
402 return Literal(str(var), INTEGER_TYPE)
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)
412 :param var: variable with a dangling parent if necessary.
413 :type var: int | :py:class:`psyclone.psyir.nodes.DataNode` | Extent
415 :returns: the variable with dangling parent when necessary.
416 :rtype: :py:class:`psyclone.psyir.nodes.DataNode` | Extent
418 if isinstance(var, DataNode):
419 parent = Assignment()
420 parent.addchild(var.copy())
421 return parent.children[0]
424 if isinstance(datatype, DataType):
425 if isinstance(datatype, StructureType):
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
435 self._intrinsic = datatype
436 self._precision =
None
437 elif isinstance(datatype, DataTypeSymbol):
438 self._intrinsic = datatype
439 self._precision =
None
442 f
"ArrayType expected 'datatype' argument to be of type "
443 f
"DataType or DataTypeSymbol but found "
444 f
"'{type(datatype).__name__}'.")
447 self._validate_shape(shape)
451 one = Literal(
"1", INTEGER_TYPE)
453 if isinstance(dim, (DataNode, int)):
456 ArrayType.ArrayBounds(
457 _dangling_parent(one),
458 _dangling_parent(_node_from_int(dim))))
459 elif isinstance(dim, tuple):
461 ArrayType.ArrayBounds(
462 _dangling_parent(_node_from_int(dim[0])),
463 _dangling_parent(_node_from_int(dim[1]))))
465 self._shape.append(dim)
467 self._datatype = datatype
472 :returns: the datatype of each element in the array.
473 :rtype: :py:class:`psyclone.psyir.symbols.DataSymbol`
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`
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`
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.
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.
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.
522 :param extents: list of extents, one for each array dimension.
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]]
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.
544 def _validate_data_node(dim_node, is_lower_bound=False):
546 Checks that the supplied DataNode is valid as part of an
547 array-shape specification.
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.
554 :raises TypeError: if the DataNode is not valid in this context.
557 if isinstance(dim_node, int):
566 if isinstance(dim_node, Reference):
569 dtype = dim_node.datatype
570 if isinstance(dtype, ArrayType)
and dtype.shape:
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):
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}'.")
590 "If present, the lower bound in an ArrayType 'shape' "
591 "must represent a value but found ArrayType.Extent")
594 if isinstance(dim_node, DataNode):
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__}'.")
603 if not isinstance(extents, list):
605 f
"ArrayType 'shape' must be of type list but found "
606 f
"'{type(extents).__name__}'.")
608 for dimension
in extents:
609 if isinstance(dimension, tuple):
610 if len(dimension) != 2:
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])
618 _validate_data_node(dimension)
620 if ArrayType.Extent.DEFERRED
in extents:
621 if not all(dim == ArrayType.Extent.DEFERRED
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}.")
628 if ArrayType.Extent.ATTRIBUTE
in extents:
632 if not (dim == ArrayType.Extent.ATTRIBUTE
or
633 (isinstance(dim, tuple)
and
634 dim[-1] == ArrayType.Extent.ATTRIBUTE)):
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}.")
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.
648 :raises InternalError: if the shape of this datatype contains \
649 any elements that aren't ArrayBounds or ArrayType.Extent objects.
653 for dimension
in self.
shapeshape:
654 if isinstance(dimension, ArrayType.ArrayBounds):
660 if isinstance(dimension.lower, Literal):
661 if dimension.lower.value !=
"1":
662 dim_text = dimension.lower.value +
":"
664 dim_text = str(dimension.lower) +
":"
666 if isinstance(dimension.upper, Literal):
667 dim_text += dimension.upper.value
669 dim_text += str(dimension.upper)
670 dims.append(dim_text)
672 dims.append(f
"'{dimension.name}'")
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__}'")
680 return f
"Array<{self._datatype}, shape=[{', '.join(dims)}]>"
684 :param Any other: the object to check equality to.
686 :returns: whether this ArrayType is equal to the 'other' ArrayType.
689 if not super().
__eq__(other):
692 if (self.
intrinsicintrinsic != other.intrinsic
or
693 self.
precisionprecision != other.precision):
696 if len(self.
shapeshape) != len(other.shape):
704 for this_dim, other_dim
in zip(self.
shapeshape, other.shape):
705 if this_dim != other_dim:
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.
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.
725 ComponentType = namedtuple(
"ComponentType", [
726 "name",
"datatype",
"visibility",
"initial_value"])
732 return "StructureType<>"
737 Creates a StructureType from the supplied list of properties.
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[
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`]
749 :returns: the new type object.
750 :rtype: :py:class:`psyclone.psyir.symbols.StructureType`
754 for component
in components:
755 if len(component) != 4:
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)
766 :returns: Ordered dictionary of the components of this type.
767 :rtype: :py:class:`collections.OrderedDict`
771 def add(self, name, datatype, visibility, initial_value):
773 Create a component with the supplied attributes and add it to
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`]
786 :raises TypeError: if any of the supplied values are of the wrong type.
793 if not isinstance(name, str):
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)):
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__}'")
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__}'")
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)):
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__}'.")
821 name, datatype, visibility, initial_value)
825 :returns: the ComponentType tuple describing the named member of this \
827 :rtype: :py:class:`psyclone.psyir.symbols.StructureType.ComponentType`
833 :param Any other: the object to check equality to.
835 :returns: whether this StructureType is equal to the 'other' type.
838 if not super().
__eq__(other):
841 if len(self.
componentscomponents) != len(other.components):
844 if self.
componentscomponents != other.components:
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)
874 TYPE_MAP_TO_PYTHON = {ScalarType.Intrinsic.INTEGER: int,
875 ScalarType.Intrinsic.CHARACTER: str,
876 ScalarType.Intrinsic.BOOLEAN: bool,
877 ScalarType.Intrinsic.REAL: float}
881 __all__ = [
"UnsupportedType",
"UnsupportedFortranType",
"UnresolvedType",
882 "ScalarType",
"ArrayType",
"StructureType"]
def _validate_shape(self, extents)
def add(self, name, datatype, visibility, initial_value)
def partial_datatype(self)