diff --git a/changelog b/changelog index eff9ed4a04..3f43a5e0c0 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,5 @@ + 5) PR #3377 for #2612. Add support for Fortran character length. + 4) PR #3388 for #3334. Remove the functionality to write (and rename) kernel files from the invoke. Now to modify kernels they must be inlinded first or modified directy in their file (e.g. with lfric transmute pass). diff --git a/doc/developer_guide/psyir_symbols.rst b/doc/developer_guide/psyir_symbols.rst index 2a5f56254e..22a3cefbeb 100644 --- a/doc/developer_guide/psyir_symbols.rst +++ b/doc/developer_guide/psyir_symbols.rst @@ -80,8 +80,8 @@ explicitly listed may be assumed to be unsupported): +======================+====================+====================+ |Variables |ALLOCATABLE |CLASS | +----------------------+--------------------+--------------------+ -| |CHARACTER, DOUBLE |COMPLEX, CHARACTER | -| |PRECISION, INTEGER, |with LEN or KIND | +| |CHARACTER, DOUBLE |COMPLEX | +| |PRECISION, INTEGER, | | | |LOGICAL, REAL | | +----------------------+--------------------+--------------------+ | |Derived Types |'extends', | @@ -89,10 +89,7 @@ explicitly listed may be assumed to be unsupported): | | |CONTAINS; Operator | | | |overloading | +----------------------+--------------------+--------------------+ -| |DIMENSION |Array extents | -| | |specified using | -| | |expressions; | -| | |Assumed-size arrays | +| |DIMENSION |Assumed-size arrays | +----------------------+--------------------+--------------------+ | |INTENT, PARAMETER, |VOLATILE, VALUE, | | |SAVE |POINTER | @@ -102,8 +99,8 @@ explicitly listed may be assumed to be unsupported): +----------------------+--------------------+--------------------+ | |PUBLIC, PRIVATE | | +----------------------+--------------------+--------------------+ -|Initialisation |Explicit | | -|expressions |initialisation | | +|Initialisation |Explicit | Implicit loops, | +|expressions |initialisation | array constructors | +----------------------+--------------------+--------------------+ | |Data statements | | | |(limited) | | diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index 44aa62cfbf..70f0da66b5 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -40,6 +40,8 @@ from a PSyIR tree. ''' # pylint: disable=too-many-lines +from typing import Union + from psyclone.configuration import Config from psyclone.errors import InternalError from psyclone.psyir.backend.language_writer import LanguageWriter @@ -48,13 +50,13 @@ Fparser2Reader, TYPE_MAP_FROM_FORTRAN) from psyclone.psyir.nodes import ( BinaryOperation, Call, Container, CodeBlock, DataNode, IntrinsicCall, - Literal, Node, OMPDependClause, OMPReductionClause, Operation, Range, - Routine, Schedule, UnaryOperation, UnknownDirective) + Literal, Member, Node, OMPDependClause, OMPReductionClause, Operation, + Range, Routine, Schedule, UnaryOperation, UnknownDirective) from psyclone.psyir.symbols import ( - ArgumentInterface, ArrayType, ContainerSymbol, DataSymbol, DataTypeSymbol, - GenericInterfaceSymbol, IntrinsicSymbol, PreprocessorInterface, - RoutineSymbol, ScalarType, StructureType, Symbol, SymbolTable, - UnresolvedInterface, UnresolvedType, UnsupportedFortranType, + ArgumentInterface, ArrayType, ContainerSymbol, DataSymbol, DataType, + DataTypeSymbol, GenericInterfaceSymbol, IntrinsicSymbol, + PreprocessorInterface, RoutineSymbol, ScalarType, StructureType, Symbol, + SymbolTable, UnresolvedInterface, UnresolvedType, UnsupportedFortranType, UnsupportedType, TypedSymbol) @@ -263,20 +265,19 @@ def _reverse_map(reverse_dict, op_map): if mapping_key not in reverse_dict: reverse_dict[mapping_key] = mapping_value.upper() - def gen_datatype(self, datatype, name): + def gen_datatype(self, + datatype: Union[DataType, DataTypeSymbol], + name: str) -> str: '''Given a DataType instance as input, return the Fortran datatype of the symbol including any specific precision properties. :param datatype: the DataType or DataTypeSymbol describing the type of the declaration. - :type datatype: :py:class:`psyclone.psyir.symbols.DataType` or - :py:class:`psyclone.psyir.symbols.DataTypeSymbol` - :param str name: the name of the symbol being declared (only used for - error messages). + :param name: the name of the symbol being declared (only used for + error messages). :returns: the Fortran representation of the symbol's datatype including any precision properties. - :rtype: str :raises NotImplementedError: if the symbol has an unsupported datatype. @@ -284,9 +285,6 @@ def gen_datatype(self, datatype, name): and this is not supported for the datatype. :raises VisitorError: if the size of the explicit precision is not supported for the datatype. - :raises VisitorError: if the size of the symbol is specified by - another variable and the datatype is not one that supports the - Fortran KIND option. :raises NotImplementedError: if the type of the precision object is an unsupported type. @@ -296,9 +294,9 @@ def gen_datatype(self, datatype, name): return f"type({datatype.name})" if (isinstance(datatype, ArrayType) and - isinstance(datatype.intrinsic, DataTypeSymbol)): + isinstance(datatype.elemental_type, DataTypeSymbol)): # Symbol is an array of derived types - return f"type({datatype.intrinsic.name})" + return f"type({datatype.elemental_type.name})" try: fortrantype = TYPE_MAP_TO_FORTRAN[datatype.intrinsic] @@ -308,6 +306,10 @@ def gen_datatype(self, datatype, name): f"'{name}' found in gen_datatype().") from error precision = datatype.precision + if isinstance(datatype, ArrayType): + scalar_type = datatype.elemental_type + else: + scalar_type = datatype if isinstance(precision, int): if fortrantype not in ['real', 'integer', 'logical']: @@ -330,6 +332,16 @@ def gen_datatype(self, datatype, name): # ISO_FORTRAN_ENV; type(type64) :: MyType. return f"{fortrantype}*{precision}" + len_str = "" + if scalar_type.intrinsic == ScalarType.Intrinsic.CHARACTER: + # Include length information for a character type. + if scalar_type.length == ScalarType.CharLengthParameter.ASSUMED: + len_str = "*" + elif scalar_type.length == ScalarType.CharLengthParameter.DEFERRED: + len_str = ":" + else: + len_str = self._visit(scalar_type.length).strip() + if isinstance(precision, ScalarType.Precision): # The precision information is not absolute so is either # machine specific or is specified via the compiler. Fortran @@ -342,16 +354,18 @@ def gen_datatype(self, datatype, name): f"ScalarType.Precision.DOUBLE is not supported for " f"datatypes other than floating point numbers in " f"Fortran, found '{fortrantype}'") + if len_str: + return f"{fortrantype}(len={len_str})" return fortrantype if isinstance(precision, DataNode): - if fortrantype not in ["real", "integer", "logical"]: - raise VisitorError( - f"kind not supported for datatype '{fortrantype}' in " - f"symbol '{name}' in Fortran backend.") + len_txt = "" + if len_str: + len_txt = f", len={len_str}" # The precision information is provided by a parameter, # so use KIND. - return f"{fortrantype}(kind={self._visit(precision)})" + return (f"{fortrantype}(kind={self._visit(precision).strip()}" + f"{len_txt})") raise VisitorError( f"Unsupported precision type '{type(precision).__name__}' found " @@ -499,18 +513,17 @@ def gen_use(self, symbol, symbol_table): f"{renames}\n") return f"{self._nindent}use{intrinsic_str}{symbol.name}\n" - def gen_vardecl(self, symbol, include_visibility=False): + def gen_vardecl(self, + symbol: Union[DataSymbol, Member], + include_visibility: bool = False) -> str: '''Create and return the Fortran variable declaration for this Symbol or derived-type member. :param symbol: the symbol or member instance. - :type symbol: :py:class:`psyclone.psyir.symbols.DataSymbol` or - :py:class:`psyclone.psyir.nodes.MemberReference` - :param bool include_visibility: whether to include the visibility of + :param include_visibility: whether to include the visibility of the symbol in the generated declaration (default False). :returns: the Fortran variable declaration as a string. - :rtype: str :raises VisitorError: if the symbol is not typed. :raises VisitorError: if the symbol is of UnresolvedType. diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 0bdc809729..a7d2123cab 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -45,7 +45,7 @@ import re import os import sys -from typing import Iterable, Optional, Union +from typing import Iterable, Optional, Tuple, Union from fparser.common.readfortran import ( FortranStringReader, FortranFileReader, FortranReaderBase) @@ -1705,28 +1705,27 @@ def _process_use_stmts(self, parent, nodes, visibility_map=None): if symbol.name.lower() in visibility_map: symbol.visibility = visibility_map[symbol.name.lower()] - def _process_type_spec(self, parent, type_spec): + def _process_type_spec( + self, + parent: Node, + type_spec: Union[Fortran2003.Intrinsic_Type_Spec, + Fortran2003.Declaration_Type_Spec] + ) -> Tuple[ + Union[ScalarType, DataTypeSymbol], + Union[ScalarType.Precision, DataSymbol, int, None]]: ''' Processes the fparser2 parse tree of a type specification in order to extract the type and precision that are specified. :param parent: the parent of the current PSyIR node under construction. - :type parent: :py:class:`psyclone.psyir.nodes.Node` :param type_spec: the fparser2 parse tree of the type specification. - :type type_spec: \ - :py:class:`fparser.two.Fortran2003.Intrinsic_Type_Spec` or \ - :py:class:`fparser.two.Fortran2003.Declaration_Type_Spec` :returns: the type and precision specified by the type-spec. - :rtype: 2-tuple of :py:class:`psyclone.psyir.symbols.ScalarType` or \ - :py:class:`psyclone.psyir.symbols.DataTypeSymbol` and \ - :py:class:`psyclone.psyir.symbols.DataSymbol.Precision` or \ - :py:class:`psyclone.psyir.symbols.DataSymbol` or int or NoneType :raises NotImplementedError: if an unsupported intrinsic type is found. - :raises SymbolError: if a symbol already exists for the name of a \ + :raises SymbolError: if a symbol already exists for the name of a derived type but is not a DataTypeSymbol. - :raises NotImplementedError: if the supplied type specification is \ + :raises NotImplementedError: if the supplied type specification is not for an intrinsic type or a derived type. ''' @@ -1751,12 +1750,13 @@ def _process_type_spec(self, parent, type_spec): precision = self._process_precision(type_spec, parent) if not precision: precision = default_precision(data_name) - # We don't support len or kind specifiers for character variables - if fort_type == "character" and type_spec.children[1]: - raise NotImplementedError( - f"Length or kind attributes not supported on a character " - f"variable: '{type_spec}'") - base_type = ScalarType(data_name, precision) + + char_len = None + if fort_type == "character": + # Character types can have a length + char_len = self._process_char_length(type_spec, parent) + + base_type = ScalarType(data_name, precision, length=char_len) elif isinstance(type_spec, Fortran2003.Declaration_Type_Spec): # This is a variable of derived type @@ -1962,6 +1962,7 @@ def _process_decln( decln_access_spec = None # 6) Whether this declaration has the SAVE attribute. has_save_attr = False + if attr_specs: for attr in attr_specs.items: if isinstance(attr, (Fortran2003.Attr_Spec, @@ -2046,11 +2047,14 @@ def _process_decln( (name, array_spec, char_len, initialisation) = entity.items init_expr = None + # Since specifiers on an individual entity can override those in + # the general declaration, we may have to take a copy. + this_type = base_type + # If the entity has an array-spec shape, it has priority. # Otherwise use the declaration attribute shape. if array_spec is not None: - entity_shape = \ - self._parse_dimensions(array_spec, symbol_table) + entity_shape = self._parse_dimensions(array_spec, symbol_table) else: entity_shape = attribute_shape @@ -2084,9 +2088,13 @@ def _process_decln( init_expr = dummynode.children[0].detach() if char_len is not None: - raise NotImplementedError( - f"Could not process {decl.items}. Character length " - f"specifications are not supported.") + # Handle any character length specification. This takes + # precedence over anything in the declaration attributes + # handled earlier. + clen = self._process_char_length(char_len, scope) + if clen: + this_type = base_type.copy() + this_type.length = clen sym_name = str(name).lower() @@ -2120,10 +2128,10 @@ def _process_decln( if entity_shape: # array - datatype = ArrayType(base_type, entity_shape) + datatype = ArrayType(this_type, entity_shape) else: # scalar - datatype = base_type + datatype = this_type # Make sure the declared symbol exists in the SymbolTable. tag = None @@ -2920,7 +2928,10 @@ def _process_common_blocks(nodes, psyir_parent): f"The symbol interface of a common block variable " f"could not be updated because of {error}.") from error - def _process_precision(self, type_spec, psyir_parent): + def _process_precision(self, + type_spec: Fortran2003.Intrinsic_Type_Spec, + psyir_parent: Node) -> Optional[ + Union[ScalarType.Precision, DataNode]]: '''Processes the fparser2 parse tree of the type specification of a variable declaration in order to extract precision information. Two formats for specifying precision are @@ -2928,38 +2939,42 @@ def _process_precision(self, type_spec, psyir_parent): kind=KIND(x). :param type_spec: the fparser2 parse tree of the type specification. - :type type_spec: \ - :py:class:`fparser.two.Fortran2003.Intrinsic_Type_Spec` - :param psyir_parent: the parent PSyIR node where the new node \ + :param psyir_parent: the parent PSyIR node where the new node will be attached. - :type psyir_parent: :py:class:`psyclone.psyir.nodes.Node` :returns: the precision associated with the type specification. - :rtype: :py:class:`psyclone.psyir.symbols.DataSymbol.Precision` or \ - :py:class:`psyclone.psyir.nodes.DataNode` or int or NoneType - :raises NotImplementedError: if a KIND intrinsic is found with an \ + :raises NotImplementedError: if a KIND intrinsic is found with an argument other than a real or integer literal. - :raises NotImplementedError: if we have `kind=xxx` but cannot find \ + :raises NotImplementedError: if we have `kind=xxx` but cannot find a valid variable name. ''' symbol_table = psyir_parent.scope.symbol_table - if not isinstance(type_spec.items[1], Fortran2003.Kind_Selector): + is_char = False + for child in type_spec.children: + if isinstance(child, Fortran2003.Kind_Selector): + kind_selector = child + break + if isinstance(child, Fortran2003.Char_Selector): + # A CHARACTER declaration can be of Char_Selector type. + # The second child of Char_Selector holds the precision. + is_char = True + kind_selector = child.children[1] + break + else: # No precision is specified return None - kind_selector = type_spec.items[1] - - if (isinstance(kind_selector.children[0], str) and - kind_selector.children[0] == "*"): + if not is_char and (isinstance(kind_selector.children[0], str) and + kind_selector.children[0] == "*"): # Precision is provided in the form *N precision = int(str(kind_selector.children[1])) return precision # Precision is supplied in the form "kind=..." - intrinsics = walk(kind_selector.items, + intrinsics = walk(kind_selector, Fortran2003.Intrinsic_Function_Reference) if intrinsics and isinstance(intrinsics[0].items[0], Fortran2003.Intrinsic_Name) and \ @@ -2984,8 +2999,10 @@ def _process_precision(self, type_spec, psyir_parent): # Create a dummy Routine and Assignment to capture the kind=... # so we can capture expressions such as 2*wp. - # The input from fparser2 is ['(', kind, ')'] - kind_items = kind_selector.items[1] + # The input from fparser2 is ['(', kind, ')'] if it is not a + # Char_Selector, otherwise kind_selector already holds the kind + # expression. + kind_items = kind_selector.items[1] if not is_char else kind_selector fake_routine = Routine(RoutineSymbol("dummy")) # Create a dummy assignment "a = " to place the kind statement on # the rhs of. @@ -3008,6 +3025,62 @@ def _process_precision(self, type_spec, psyir_parent): ) return kind_expression + def _process_char_length( + self, + type_spec: Union[Fortran2003.Intrinsic_Type_Spec, + Fortran2003.Int_Literal_Constant, + Fortran2003.Char_Length], + psyir_parent: Node) -> Optional[ + Union[ScalarType.CharLengthParameter, DataNode]]: + ''' + Process any length attribute on a CHARACTER declaration. + + :param type_spec: the fparser2 parse tree describing the type. + :param psyir_parent: the parent node in the PSyIR tree. + + :returns: the length of the character string or None if it is + unspecified. + + ''' + if isinstance(type_spec, Fortran2003.Intrinsic_Type_Spec): + for child in type_spec.children: + if isinstance(child, Fortran2003.Length_Selector): + # Child 0 holds '(' for a '(len=xxx)' or '*' for a + # '* char-length'. Either way, child 1 holds the length. + if isinstance(child.children[1], Fortran2003.Char_Length): + char_len = child.children[1].children[1] + else: + char_len = child.children[1] + break + + if isinstance(child, Fortran2003.Char_Selector): + # A CHARACTER declaration can be of Char_Selector type. + # The first child of Char_Selector holds the length (which + # may be None if it is unspecified). + char_len = child.children[0] + if not char_len: + return None + break + else: + # No length is specified + return None + elif isinstance(type_spec, Fortran2003.Char_Length): + # e.g. Char_Length('(', Name('MAX_LEN'), ')') + char_len = type_spec.children[1] + else: + char_len = type_spec + + if isinstance(char_len, Fortran2003.Type_Param_Value): + if char_len.string == ":": + return ScalarType.CharLengthParameter.DEFERRED + return ScalarType.CharLengthParameter.ASSUMED + + # Create a dummy assignment so we can process the length expression. + dummy = Assignment(parent=psyir_parent) + dummy.addchild(Reference(Symbol("a"))) + self.process_nodes(parent=dummy, nodes=[char_len]) + return dummy.rhs.detach() + def _add_comments_to_tree(self, parent: Node, preceding_comments, psy_child: Node) -> None: ''' @@ -3871,18 +3944,16 @@ def _create_select_type( ''' pointer_symbols = [] - # Create a symbol from the supplied base name. Store as an - # UnsupportedFortranType in the symbol table as we do not natively - # support character strings (as opposed to scalars) in the PSyIR at - # the moment. + # Create a symbol from the supplied base name. # TODO #2550 will improve this by using an integer instead. type_string_name = parent.scope.symbol_table.next_available_name( type_string_name) # Length is hardcoded here so could potentially be too short. # TODO #2550 will improve this by using an integer instead. - type_string_type = UnsupportedFortranType( - f"character(256) :: {type_string_name}") - type_string_symbol = DataSymbol(type_string_name, type_string_type) + type_string_symbol = DataSymbol( + type_string_name, + ScalarType(ScalarType.Intrinsic.CHARACTER, + ScalarType.Precision.UNDEFINED, length=256)) parent.scope.symbol_table.add(type_string_symbol) # Create text for a select type construct using the information @@ -5636,7 +5707,6 @@ def _subroutine_handler(self, node, parent): :returns: PSyIR representation of node. :rtype: :py:class:`psyclone.psyir.nodes.Routine` - :raises NotImplementedError: if the node contains a Contains clause. :raises NotImplementedError: if the node contains an ENTRY statement. :raises NotImplementedError: if an unsupported prefix is found. @@ -5691,10 +5761,10 @@ def _subroutine_handler(self, node, parent): if routine_node.name.lower() == name.lower(): routine = routine_node break - if routine is None: + else: routine = Routine.create(name) # We add this to the parent so the finally of the next block - # can safe call detach on the routine. This handles the case + # can safely call detach on the routine. This handles the case # where an error occurs which should result in a codeblock, but # we had forward declared the Routine and we need to ensure the # empty Routine is detached from the tree. diff --git a/src/psyclone/psyir/nodes/array_reference.py b/src/psyclone/psyir/nodes/array_reference.py index 07831516a4..18180ee7ca 100644 --- a/src/psyclone/psyir/nodes/array_reference.py +++ b/src/psyclone/psyir/nodes/array_reference.py @@ -178,7 +178,12 @@ def datatype(self) -> DataType: base_type = UnresolvedType() else: # Create a copy of the base datatype. - base_type = self.symbol.datatype.elemental_type.copy() + if isinstance(self.symbol.datatype, ArrayType): + base_type = self.symbol.datatype.elemental_type.copy() + else: + # TODO #3240 - sometimes we have an ArrayReference that is + # actually a character sub-string. + base_type = self.symbol.datatype.copy() return ArrayType(base_type, shape) # Otherwise, we're accessing a single element of the array. if type(self.symbol) is Symbol: diff --git a/src/psyclone/psyir/nodes/datanode.py b/src/psyclone/psyir/nodes/datanode.py index 1e2c75488c..157564fd9f 100644 --- a/src/psyclone/psyir/nodes/datanode.py +++ b/src/psyclone/psyir/nodes/datanode.py @@ -37,6 +37,8 @@ ''' This module contains the DataNode abstract node implementation.''' +from typing import Optional + from psyclone.psyir.nodes.node import Node @@ -64,28 +66,25 @@ def datatype(self): return INTEGER_TYPE return UnresolvedType() - def is_character(self, unknown_as=None): + def is_character(self, unknown_as: Optional[bool] = None) -> bool: ''' :param unknown_as: Determines behaviour in the case where it cannot be determined whether the DataNode is a character. Defaults to None, in which case an exception is raised. - :type unknown_as: Optional[bool] :returns: True if this DataNode is a character, otherwise False. - :rtype: bool :raises ValueError: if the intrinsic type cannot be determined. ''' - # pylint: disable=import-outside-toplevel - from psyclone.psyir.symbols.datatypes import ScalarType - if not hasattr(self.datatype, "intrinsic"): + dtype = self.datatype + if not hasattr(dtype, "intrinsic"): if unknown_as is None: raise ValueError( "is_character could not resolve whether the expression" f" '{self.debug_string()}' operates on characters." ) return unknown_as - return ( - self.datatype.intrinsic == ScalarType.Intrinsic.CHARACTER - ) + # pylint: disable=import-outside-toplevel + from psyclone.psyir.symbols.datatypes import ScalarType + return dtype.intrinsic == ScalarType.Intrinsic.CHARACTER diff --git a/src/psyclone/psyir/nodes/intrinsic_call.py b/src/psyclone/psyir/nodes/intrinsic_call.py index cea33cea06..9b132ca149 100644 --- a/src/psyclone/psyir/nodes/intrinsic_call.py +++ b/src/psyclone/psyir/nodes/intrinsic_call.py @@ -791,7 +791,8 @@ class Intrinsic(IAttr, Enum): types=DataNode, arg_names=(("string",),)), optional_args={}, - # TODO 2612 This may be more complex if we support character len + # Returned string is of the same length as the input (trailing + # spaces are added as needed). return_type=lambda node: _type_of_named_argument(node, "string"), reference_accesses=lambda node: ( _compute_reference_accesses( @@ -810,7 +811,8 @@ class Intrinsic(IAttr, Enum): types=DataNode, arg_names=(("string",),)), optional_args={}, - # TODO 2612 This may be more complex if we support character len + # Returned string is of the same length as the input (leading + # spaces are added as needed). return_type=lambda node: _type_of_named_argument(node, "string"), reference_accesses=lambda node: ( _compute_reference_accesses( diff --git a/src/psyclone/psyir/nodes/literal.py b/src/psyclone/psyir/nodes/literal.py index f72bfe522a..17b07ee1da 100644 --- a/src/psyclone/psyir/nodes/literal.py +++ b/src/psyclone/psyir/nodes/literal.py @@ -45,7 +45,8 @@ from psyclone.core import VariablesAccessMap, Signature, AccessType from psyclone.psyir.nodes.datanode import DataNode -from psyclone.psyir.symbols import ScalarType, ArrayType, Symbol +from psyclone.psyir.symbols.symbol import Symbol +from psyclone.psyir.symbols.datatypes import ScalarType, ArrayType class Literal(DataNode): diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index eea60425e1..02eb0162e5 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -45,7 +45,7 @@ from collections import OrderedDict from dataclasses import dataclass from enum import Enum -from typing import Any, Optional, Union +from typing import Any, Optional, Union, TYPE_CHECKING from psyclone.configuration import Config from psyclone.errors import InternalError @@ -53,6 +53,9 @@ from psyclone.psyir.symbols.datasymbol import DataSymbol from psyclone.psyir.symbols.data_type_symbol import DataTypeSymbol from psyclone.psyir.symbols.symbol import Symbol +if TYPE_CHECKING: + from psyclone.psyir.nodes.datanode import DataNode + from psyclone.psyir.symbols import SymbolTable class DataType(metaclass=abc.ABCMeta): @@ -357,17 +360,29 @@ class ScalarType(DataType): '''Describes a scalar datatype (and its precision). :param intrinsic: the intrinsic of this scalar type. - :type intrinsic: :py:class:`pyclone.psyir.datatypes.ScalarType.Intrinsic` :param precision: the precision of this scalar type. - :type precision: :py:class:`psyclone.psyir.symbols.ScalarType.Precision` | - int | :py:class:`psyclone.psyir.nodes.DataNode` + :param length: optionally, the length of a character type. :raises TypeError: if any of the arguments are of the wrong type. :raises ValueError: if any of the argument have unexpected values. ''' - class Intrinsic(Enum): + class ScalarTypeAttribute(Enum): + ''' + Provides some common functionality to the various classes that + describe attributes of a ScalarType. + + ''' + def copy(self) -> ScalarType.ScalarTypeAttribute: + ''':returns: a copy of self.''' + return copy.copy(self) + + def debug_string(self) -> str: + ''':returns: the name of the Enum item.''' + return self.name + + class Intrinsic(ScalarTypeAttribute): '''Enumeration of the different intrinsic scalar datatypes that are supported by the PSyIR. @@ -377,7 +392,7 @@ class Intrinsic(Enum): BOOLEAN = 3 CHARACTER = 4 - class Precision(Enum): + class Precision(ScalarTypeAttribute): '''Enumeration of the different types of 'default' precision that may be specified for a scalar datatype. @@ -386,12 +401,17 @@ class Precision(Enum): DOUBLE = 2 UNDEFINED = 3 - def copy(self): - ''' - :returns: a copy of self. - :rtype: :py:class:`psyclone.psyir.symbols.ScalarType.Precision` - ''' - return copy.copy(self) + class CharLengthParameter(ScalarTypeAttribute): + '''Enumeration of different length characteristics that a character + type may have. + + ''' + #: The length is defined by some other variable. In Fortran + ## this is indicated with an asterisk. + ASSUMED = 1 + #: The length can change during program execution. In Fortran this + ## is indicated with a colon. + DEFERRED = 2 #: Mapping from PSyIR scalar data types to intrinsic Python types #: ignoring precision. @@ -401,7 +421,13 @@ def copy(self): Intrinsic.BOOLEAN: bool, Intrinsic.REAL: float} - def __init__(self, intrinsic, precision): + def __init__( + self, + intrinsic: ScalarType.Intrinsic, + precision: Union[int, ScalarType.Precision, "DataNode"], + length: Optional[ + Union[int, ScalarType.CharLengthParam, "DataNode"]] = None + ): if not isinstance(intrinsic, ScalarType.Intrinsic): raise TypeError( f"ScalarType expected 'intrinsic' argument to be of type " @@ -436,11 +462,81 @@ def __init__(self, intrinsic, precision): # possible due to circular imports. self._precision = precision + # The 'length' setter includes validation checks. + self._length = None + self.length = length + @property - def intrinsic(self): + def length(self) -> "DataNode": + ''' + :returns: the length of a character type. + + :raises TypeError: if this ScalarType instance is not of + character type. + ''' + if self._intrinsic != ScalarType.Intrinsic.CHARACTER: + raise TypeError( + f"A ScalarType of intrinsic type '{self._intrinsic}' does not " + f"have the 'length' property.") + return self._length + + @length.setter + def length(self, value: Union[int, "DataNode", None]): + ''' + Setter for the length of a character string. If the new value + is supplied as an int then this is converted into a Literal. + + If this type is a character string and the `value` is None then + the length is set to the Fortran default of 1. + + :value: the new length to assign. + + :raises TypeError: if value is not None and this is not a + character type. + :raises ValueError: if the supplied value is an int with value < 0. + :raises TypeError: if the supplied value is of the wrong type. + + ''' + if self._intrinsic != ScalarType.Intrinsic.CHARACTER: + if value is None: + self._length = None + return + raise TypeError( + f"Only ScalarTypes of character type support the length " + f"property but length '{value}' was supplied to an intrinsic" + f" type of '{self._intrinsic}'") + + # This is a character type. + if value is None: + # pylint: disable=import-outside-toplevel + from psyclone.psyir.nodes.literal import Literal + # Default length of a character string is 1. + self._length = Literal("1", INTEGER_TYPE) + return + + # pylint: disable=import-outside-toplevel + from psyclone.psyir.nodes.datanode import DataNode + if isinstance(value, ScalarType.CharLengthParameter): + self._length = value + elif isinstance(value, int) and not isinstance(value, bool): + if value < 0: + raise ValueError( + f"If the length of a character ScalarType is specified " + f"using an int then it must be >= 0 but got: {value}") + from psyclone.psyir.nodes.literal import Literal + self._length = Literal(str(value), INTEGER_TYPE) + elif isinstance(value, DataNode): + self._length = value + else: + raise TypeError( + f"The length property of a character ScalarType must be a non-" + f"negative int, ScalarType.CharLengthParameter " + f"or DataNode but got '{type(value).__name__}'") + + @property + def intrinsic(self) -> ScalarType.Intrinsic: ''' :returns: the intrinsic used by this scalar type. - :rtype: :py:class:`pyclone.psyir.datatypes.ScalarType.Intrinsic` ''' return self._intrinsic @@ -453,28 +549,36 @@ def precision(self): ''' return self._precision - def __str__(self): + def __str__(self) -> str: ''' :returns: a description of this scalar datatype. - :rtype: str ''' if isinstance(self.precision, ScalarType.Precision): precision_str = self.precision.name else: precision_str = str(self.precision) - return f"Scalar<{self.intrinsic.name}, {precision_str}>" - def __eq__(self, other): + if self._length: + len_str = f", len:{self._length}" + else: + len_str = "" + + return f"Scalar<{self.intrinsic.name}, {precision_str}{len_str}>" + + def __eq__(self, other: Any) -> bool: ''' - :param Any other: the object to check equality to. + :param other: the object to check equality to. :returns: whether this type is equal to the 'other' type. - :rtype: bool + ''' if not super().__eq__(other): return False + if self.intrinsic != other.intrinsic: + return False + # TODO #2659 - the following should be sufficient but isn't because # currently, each new instance of an LFRicIntegerScalarDataType ends # up with a brand new instance of a precision symbol. @@ -491,9 +595,18 @@ def __eq__(self, other): ) else: precision_match = self.precision == other.precision - return precision_match and self.intrinsic == other.intrinsic - def replace_symbols_using(self, table_or_symbol): + if self.intrinsic == ScalarType.Intrinsic.CHARACTER: + # We've already checked that the two are of the same intrinsic type + length_match = self._length == other.length + else: + length_match = True + + return precision_match and length_match + + def replace_symbols_using( + self, + table_or_symbol: Union[SymbolTable, Symbol]) -> None: ''' Replace any Symbols referred to by this object with those in the supplied SymbolTable (or just the supplied Symbol instance) if they @@ -501,15 +614,14 @@ def replace_symbols_using(self, table_or_symbol): left unchanged. :param table_or_symbol: the symbol table from which to get replacement - symbols or a single, replacement Symbol. - :type table_or_symbol: :py:class:`psyclone.psyir.symbols.SymbolTable` | - :py:class:`psyclone.psyir.symbols.Symbol` - + symbols or a single, replacement Symbol. ''' # pylint: disable=import-outside-toplevel from psyclone.psyir.nodes.datanode import DataNode if isinstance(self.precision, DataNode): self._precision.replace_symbols_using(table_or_symbol) + if isinstance(self._length, DataNode): + self._length.replace_symbols_using(table_or_symbol) def get_all_accessed_symbols(self) -> set[Symbol]: ''' @@ -522,20 +634,23 @@ def get_all_accessed_symbols(self) -> set[Symbol]: from psyclone.psyir.nodes.datanode import DataNode if isinstance(self.precision, DataNode): symbols.update(self.precision.get_all_accessed_symbols()) - + if isinstance(self._length, DataNode): + symbols.update(self._length.get_all_accessed_symbols()) return symbols - def copy(self): + def copy(self) -> ScalarType: ''' :returns: a copy of self. - :rtype: :py:class:`psyclone.psyir.symbols.DatatTypes.ScalarType` ''' - # TODO #3135 After the precision is always either a Precision or - # a DataNode this hasattr check can be removed. - if hasattr(self.precision, "copy"): - return ScalarType(self.intrinsic, self.precision.copy()) + if isinstance(self.precision, int): + # TODO #3135 - ideally precision will always be stored as a + # DataNode and this branch of the `if` won't be necessary. + precision = self.precision else: - return ScalarType(self.intrinsic, self.precision) + precision = self.precision.copy() + if self._length: + return ScalarType(self.intrinsic, precision, self._length.copy()) + return ScalarType(self.intrinsic, precision) class ArrayType(DataType): @@ -1332,7 +1447,7 @@ def get_all_accessed_symbols(self) -> set[Symbol]: BOOLEAN_TYPE = ScalarType(ScalarType.Intrinsic.BOOLEAN, ScalarType.Precision.UNDEFINED) CHARACTER_TYPE = ScalarType(ScalarType.Intrinsic.CHARACTER, - ScalarType.Precision.UNDEFINED) + ScalarType.Precision.UNDEFINED, 1) # For automatic documentation generation diff --git a/src/psyclone/psyir/transformations/acc_kernels_trans.py b/src/psyclone/psyir/transformations/acc_kernels_trans.py index 6d5290f4c2..cb84e5d56f 100644 --- a/src/psyclone/psyir/transformations/acc_kernels_trans.py +++ b/src/psyclone/psyir/transformations/acc_kernels_trans.py @@ -40,7 +40,6 @@ ''' This module provides the ACCKernelsTrans transformation. ''' -import re from typing import Any, Dict, Union import warnings @@ -50,7 +49,9 @@ ACCEnterDataDirective, ACCKernelsDirective, Assignment, Call, CodeBlock, Literal, Loop, Node, PSyDataNode, Reference, Return, Routine, Statement, WhileLoop) -from psyclone.psyir.symbols import INTEGER_TYPE, UnsupportedFortranType +from psyclone.psyir.symbols import ( + ArrayType, DataTypeSymbol, INTEGER_TYPE, ScalarType, + UnsupportedFortranType) from psyclone.psyir.transformations.arrayassignment2loops_trans import ( ArrayAssignment2LoopsTrans) from psyclone.psyir.transformations.region_trans import RegionTrans @@ -261,12 +262,6 @@ def validate( "GOcean InvokeSchedules") super().validate(node_list, options, **kwargs) - # The regex we use to determine whether a character declaration is - # of assumed size ('LEN=*' or '*(*)'). - # TODO #2612 - improve the fparser2 frontend support for character - # declarations. - assumed_size = re.compile(r"\(\s*len\s*=\s*\*\s*\)|\*\s*\(\s*\*\s*\)") - # Construct a list of any symbols that correspond to assumed-size # character strings. These can only be routine arguments. char_syms = [] @@ -274,14 +269,19 @@ def validate( if parent_routine: arg_syms = parent_routine.symbol_table.argument_datasymbols for sym in arg_syms: - # Currently the fparser2 frontend does not support any type - # of LEN= specification on a character variable so we resort - # to a regex to check whether it is assumed-size. - if isinstance(sym.datatype, UnsupportedFortranType): - type_txt = sym.datatype.type_text.lower() - if (type_txt.startswith("character") and - assumed_size.search(type_txt)): - char_syms.append(sym) + dtype = sym.datatype + if isinstance(dtype, UnsupportedFortranType): + dtype = dtype.partial_datatype + if not dtype: + continue + if isinstance(dtype, DataTypeSymbol): + continue + if dtype.intrinsic != ScalarType.Intrinsic.CHARACTER: + continue + if isinstance(dtype, ArrayType): + dtype = dtype.elemental_type + if isinstance(dtype.length, ScalarType.CharLengthParameter): + char_syms.append(sym) for node in node_list: # Check that there are no assumed-size character variables as these diff --git a/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py b/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py index bb0c90c578..2ac4d26fc6 100644 --- a/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py +++ b/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py @@ -215,11 +215,9 @@ def validate( not have Range specifying the access to at least one of its dimensions. :raises TransformationError: if two or more of the loop ranges - in the assignment are different or are not known to be the - same. + in the assignment are different or are not known to be the same. :raises TransformationError: if node contains a character type - child and the allow_strings option is - not set. + child and the allow_strings option is not set. ''' super().validate(node, **kwargs) @@ -424,19 +422,13 @@ def validate_no_char(node: Node, message: str, verbose: bool) -> None: ''' for child in node.walk((Literal, Reference)): - try: - forbidden = ScalarType.Intrinsic.CHARACTER - if (child.is_character(unknown_as=False) or - (child.symbol.datatype.intrinsic == forbidden)): - if verbose: - node.append_preceding_comment(message) - # pylint: disable=cell-var-from-loop - raise TransformationError(LazyString( - lambda: f"{message}, but found:" - f"\n{node.debug_string()}")) - except (NotImplementedError, AttributeError): - # We cannot always get the datatype, we ignore this for now - pass + if child.is_character(unknown_as=False): + if verbose: + node.append_preceding_comment(message) + # pylint: disable=cell-var-from-loop + raise TransformationError(LazyString( + lambda: f"{message}, but found:" + f"\n{node.debug_string()}")) def _walk_until_non_elemental_call( diff --git a/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py b/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py index dd2d9f6231..6111c6242c 100644 --- a/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py +++ b/src/psyclone/tests/domain/gocean/transformations/globalstoargs_test.py @@ -243,7 +243,7 @@ def create_data_symbol(arg): trans.apply(kernel) assert ("The imported variable 'rdt' could not be promoted to an argument " "because the GOcean infrastructure does not have any scalar type " - "equivalent to the PSyIR Scalar type." + "equivalent to the PSyIR Scalar' has no representation in " - f"the SIR backend." in str(excinfo.value)) + err_msg = str(excinfo.value) + assert f"PSyIR type 'Scalar<{datatype}, " in err_msg + assert ">' has no representation in the SIR backend" in err_msg # (1/5) Method unaryoperation_node diff --git a/src/psyclone/tests/psyir/frontend/fparser2_char_decln_test.py b/src/psyclone/tests/psyir/frontend/fparser2_char_decln_test.py new file mode 100644 index 0000000000..85cdae2c69 --- /dev/null +++ b/src/psyclone/tests/psyir/frontend/fparser2_char_decln_test.py @@ -0,0 +1,137 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2026, Science and Technology Facilities Council. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# * Neither the name of the copyright holder nor the names of its +# contributors may be used to endorse or promote products derived from +# this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +# COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +# POSSIBILITY OF SUCH DAMAGE. +# ----------------------------------------------------------------------------- +# Author: A. R. Porter +# ----------------------------------------------------------------------------- + +''' Performs pytest tests on the handling of character declarations in the + fparser2 PSyIR frontend. ''' + +import pytest + +from fparser.common.readfortran import FortranStringReader +from fparser.common.sourceinfo import FortranFormat +from fparser.two import Fortran2003 + +from psyclone.psyir.frontend.fparser2 import ( + Fparser2Reader) +from psyclone.psyir.nodes import Reference, Routine +from psyclone.psyir.symbols import ( + ScalarType, Symbol, UnsupportedFortranType) + + +@pytest.mark.usefixtures("f2008_parser") +@pytest.mark.parametrize("len_expr,length,kind", + [("", "1", ScalarType.Precision.UNDEFINED), + ("(len=3)", "3", ScalarType.Precision.UNDEFINED), + ("(3)", "3", ScalarType.Precision.UNDEFINED), + ("*3", "3", ScalarType.Precision.UNDEFINED), + ("*(3)", "3", ScalarType.Precision.UNDEFINED), + ("(len=2*max_len)", "2 * max_len", + ScalarType.Precision.UNDEFINED), + ("*(2*max_len)", "2 * max_len", + ScalarType.Precision.UNDEFINED), + ("(len=:)", "DEFERRED", + ScalarType.Precision.UNDEFINED), + ("(:)", "DEFERRED", ScalarType.Precision.UNDEFINED), + ("*(:)", "DEFERRED", ScalarType.Precision.UNDEFINED), + ("(len=*)", "ASSUMED", + ScalarType.Precision.UNDEFINED), + ("(*)", "ASSUMED", ScalarType.Precision.UNDEFINED), + ("*(*)", "ASSUMED", ScalarType.Precision.UNDEFINED), + ("(len=3, kind=ckind)", "3", + Reference(Symbol("ckind"))), + ("(len=*, kind=ckind)", "ASSUMED", + Reference(Symbol("ckind")))]) +def test_char_decln_length_handling(len_expr, length, kind): + ''' + Test the handling of kind and length specifiers. + ''' + fake_parent = Routine.create("dummy_schedule") + symtab = fake_parent.symbol_table + processor = Fparser2Reader() + + # Test simple declarations + reader = FortranStringReader(f"character{len_expr} :: l1") + # Set reader to free format (otherwise this is a comment in fixed format) + reader.set_format(FortranFormat(True, True)) + fparser2spec = Fortran2003.Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + l1_var = symtab.lookup("l1") + assert l1_var.datatype.intrinsic == ScalarType.Intrinsic.CHARACTER + assert l1_var.datatype.precision == kind + assert l1_var.datatype.length.debug_string() == length + + +@pytest.mark.usefixtures("f2008_parser") +def test_char_decln_with_char_kind(): + ''' + Check that we get the expected UnsupportedFortranType if the kind is + specified using a character literal. + + ''' + fake_parent = Routine.create("dummy_schedule") + symtab = fake_parent.symbol_table + processor = Fparser2Reader() + reader = FortranStringReader("character(len=3, kind=KIND('h')) :: l1") + # Set reader to free format (otherwise this is a comment in fixed format) + reader.set_format(FortranFormat(True, True)) + fparser2spec = Fortran2003.Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + l1_var = symtab.lookup("l1") + assert isinstance(l1_var.datatype, UnsupportedFortranType) + + +@pytest.mark.usefixtures("f2008_parser") +def test_char_len_inline(): + ''' + Check that specifying the character length of an individual entity is + supported and correctly overrides any length in the base declaration. + + ''' + fake_parent = Routine.create("dummy_schedule") + symtab = fake_parent.symbol_table + processor = Fparser2Reader() + reader = FortranStringReader( + "character*5 :: l1*3, l2*(MAX_LEN), l3, l4*(:)") + # Set reader to free format (otherwise this is a comment in fixed format) + reader.set_format(FortranFormat(True, True)) + fparser2spec = Fortran2003.Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + l1_var = symtab.lookup("l1") + assert l1_var.datatype.length.value == "3" + l2_var = symtab.lookup("l2") + assert l2_var.datatype.length.symbol is symtab.lookup("MAX_LEN") + assert symtab.lookup("l3").datatype.length.value == "5" + assert (symtab.lookup("l4").datatype.length == + ScalarType.CharLengthParameter.DEFERRED) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py b/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py index c432da13dc..ba694f61b6 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_parameter_stmts_test.py @@ -154,14 +154,14 @@ def test_parameter_statements_with_unsupported_symbols(): # Test with a UnsupportedType declaration reader = FortranStringReader(''' - character*5 :: var1 - parameter (var1='hello')''') + complex :: var1 + parameter (var1=(1.0,1.0))''') fparser2spec = Specification_Part(reader) with pytest.raises(NotImplementedError) as error: processor.process_declarations(routine, fparser2spec.content, []) - assert ("Could not process 'PARAMETER(var1 = 'hello')' because 'var1' has " - "an UnsupportedType." in str(error.value)) + assert ("Could not process 'PARAMETER(var1 = (1.0, 1.0))' because 'var1' " + "has an UnsupportedType." in str(error.value)) # Test with a symbol which is not a DataSymbol symtab.add(Symbol("var2")) @@ -196,8 +196,8 @@ def test_unsupported_parameter_statements_produce_codeblocks(fortran_reader, contains subroutine my_sub() integer :: var - character*5 :: var1 - parameter (var=3, var1='hello') + complex :: var1 + parameter (var=3, var1=(1.0, 1.0)) end subroutine my_sub end module my_mod ''') @@ -206,8 +206,8 @@ def test_unsupported_parameter_statements_produce_codeblocks(fortran_reader, psyir = fortran_reader.psyir_from_source(''' module my_mod - character*5 :: var1 - parameter (var1='hello') + complex :: var1 + parameter (var1=(1.0, 1.0)) contains subroutine my_sub() integer :: var @@ -222,11 +222,11 @@ def test_unsupported_parameter_statements_produce_codeblocks(fortran_reader, code = fortran_writer(psyir) assert code == '''\ ! PSyclone CodeBlock (unsupported code) reason: -! - Could not process 'PARAMETER(var1 = 'hello')' because 'var1' has an \ +! - Could not process 'PARAMETER(var1 = (1.0, 1.0))' because 'var1' has an \ UnsupportedType. MODULE my_mod - CHARACTER*5 :: var1 - PARAMETER(var1 = 'hello') + COMPLEX :: var1 + PARAMETER(var1 = (1.0, 1.0)) CONTAINS SUBROUTINE my_sub INTEGER :: var diff --git a/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py b/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py index 6d9a8ad491..5505c51a0c 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_select_case_test.py @@ -512,8 +512,8 @@ def has_cmp_interface(code): # Check that the char implementation is in the code assert '''function test_psyclone_internal_cmp_char(op1, op2) - CHARACTER(LEN = *), INTENT(IN) :: op1 - CHARACTER(LEN = *), INTENT(IN) :: op2 + character(len=*), intent(in) :: op1 + character(len=*), intent(in) :: op2 logical :: test_psyclone_internal_cmp_char test_psyclone_internal_cmp_char = op1 == op2 diff --git a/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py b/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py index e72162024f..51066c4a22 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py @@ -74,7 +74,7 @@ def test_type(fortran_reader, fortran_writer, tmpdir): "end module\n") expected1 = "CLASS(*), TARGET :: type_selector" expected2 = ( - " character(256) :: type_string\n" + " character(len=256) :: type_string\n" " INTEGER, pointer :: ptr_INTEGER => null()\n" " REAL, pointer :: ptr_REAL => null()\n\n" " type_string = ''\n" @@ -135,7 +135,7 @@ def test_default(fortran_reader, fortran_writer, tmpdir): "end subroutine\n" "end module\n") expected = ( - " character(256) :: type_string\n" + " character(len=256) :: type_string\n" " INTEGER, pointer :: ptr_INTEGER => null()\n" " REAL, pointer :: ptr_REAL => null()\n\n" " type_string = ''\n" @@ -207,7 +207,7 @@ def test_class(fortran_reader, fortran_writer, tmpdir): "end module\n") expected1 = "class(*), pointer :: type" expected2 = ( - " character(256) :: type_string\n" + " character(len=256) :: type_string\n" " type(type2), pointer :: ptr_type2 => null()\n" " INTEGER, pointer :: ptr_INTEGER => null()\n" " type(type3), pointer :: ptr_type3 => null()\n" @@ -382,7 +382,7 @@ def test_kind(fortran_reader, fortran_writer, tmpdir): " integer :: branch2\n" " REAL(KIND=4) :: rinfo1\n" " REAL(KIND=8) :: rinfo2\n" - " character(256) :: type_string\n" + " character(len=256) :: type_string\n" " REAL(KIND = 4), pointer :: ptr_REAL_4 => null()\n" " REAL(KIND = 8), pointer :: ptr_REAL_8 => null()\n").lower() expected2 = ( @@ -441,7 +441,7 @@ def test_derived(fortran_reader, fortran_writer, tmpdir): " end type field_type\n" " type(field_type) :: field_type_info\n" " integer :: branch1\n" - " character(256) :: type_string\n" + " character(len=256) :: type_string\n" " type(field_type), pointer :: ptr_field_type => null()\n") expected2 = ( " type_string = ''\n" @@ -497,11 +497,11 @@ def test_datatype(fortran_reader, fortran_writer, tmpdir): " integer :: branch2\n" " integer :: branch3\n" " logical :: logical_type\n" - " CHARACTER(LEN = 256) :: character_type\n" + " character(len=256) :: character_type\n" " COMPLEX :: complex_type\n" - " character(256) :: type_string\n" + " character(len=256) :: type_string\n" " LOGICAL, pointer :: ptr_LOGICAL => null()\n" - " CHARACTER(LEN=256), pointer :: ptr_CHARACTER_star => null()\n" + " character(len=256), pointer :: ptr_CHARACTER_star => null()\n" " COMPLEX, pointer :: ptr_COMPLEX => null()\n").lower() expected2 = ( " type_string = ''\n" @@ -540,8 +540,8 @@ def test_datatype(fortran_reader, fortran_writer, tmpdir): @pytest.mark.parametrize( "char_type_in, char_type_out", - (["*256", "*256"], ["(256)", "(LEN = 256)"], - ["(LEN = 256)", "(LEN = 256)"])) + (["*256", "(len=256)"], ["(256)", "(len=256)"], + ["(LEN = 256)", "(len=256)"])) def test_character(fortran_reader, fortran_writer, tmpdir, char_type_in, char_type_out): '''Check that the correct code is output with literal and implicit @@ -566,7 +566,7 @@ def test_character(fortran_reader, fortran_writer, tmpdir, char_type_in, f" subroutine select_type(type_selector)\n" f" CLASS(*), TARGET :: type_selector\n" f" CHARACTER{char_type_out} :: character_type\n" - f" character(256) :: type_string\n" + f" character(len=256) :: type_string\n" f" CHARACTER(LEN=256), pointer :: ptr_CHARACTER_star => " f"null()\n").lower() expected2 = ( @@ -611,7 +611,7 @@ def test_character_assumed_len(fortran_reader, fortran_writer, tmpdir, f" subroutine select_type(type_selector)\n" f" CLASS(*), TARGET :: type_selector\n" f" CHARACTER{char_type_out}, POINTER :: character_type => null()\n" - f" character(256) :: type_string\n" + f" character(len=256) :: type_string\n" f" CHARACTER(LEN=256), pointer :: ptr_CHARACTER_star => " f"null()\n").lower() expected2 = ( @@ -633,24 +633,24 @@ def test_character_assumed_len(fortran_reader, fortran_writer, tmpdir, @pytest.mark.parametrize( "char_type_in, char_type_out, pointer", - (["(LEN=*, KIND=1)", "(LEN = *, KIND = 1)", ""], - ["(LEN=*, KIND=1*1)", "(LEN = *, KIND = 1 * 1)", ""], - ["(LEN=1*2, KIND=1*1)", "(LEN = 1 * 2, KIND = 1 * 1)", ""], - ["(*, KIND=1*1)", "(LEN = *, KIND = 1 * 1)", ""], - ["(256*1, KIND=1*1)", "(LEN = 256 * 1, KIND = 1 * 1)", ""], - ["(*, 1*1)", "(LEN = *, KIND = 1 * 1)", ""], - ["(256*1, 1*1)", "(LEN = 256 * 1, KIND = 1 * 1)", ""], - ["(KIND=1*1, LEN=*)", "(LEN = *, KIND = 1 * 1)", ""], - ["(KIND=1*1, LEN=256*1)", "(LEN = 256 * 1, KIND = 1 * 1)", ""], - ["(KIND=1*1)", "(KIND = 1 * 1)", ""], + (["(LEN=*, KIND=1)", "(KIND=1, LEN=*)", ""], + ["(LEN=*, KIND=1*1)", "(KIND=1 * 1, LEN=*)", ""], + ["(LEN=1*2, KIND=1*1)", "(KIND=1 * 1, LEN=1 * 2)", ""], + ["(*, KIND=1*1)", "(KIND=1 * 1, LEN=*)", ""], + ["(256*1, KIND=1*1)", "(KIND=1 * 1, LEN=256 * 1)", ""], + ["(*, 1*1)", "(KIND=1 * 1, LEN=*)", ""], + ["(256*1, 1*1)", "(KIND=1 * 1, LEN=256 * 1)", ""], + ["(KIND=1*1, LEN=*)", "(KIND=1 * 1, LEN=*)", ""], + ["(KIND=1*1, LEN=256*1)", "(KIND=1 * 1, LEN=256 * 1)", ""], + ["(KIND=1*1)", "(KIND=1 * 1, LEN=1)", ""], ["(LEN=:, KIND=1*1)", "(LEN = :, KIND = 1 * 1)", ", POINTER"], - ["(:, KIND=1*1)", "(LEN = :, KIND = 1 * 1)", ", POINTER"], + ["(*, KIND=1*1)", "(LEN = *, KIND = 1 * 1)", ", POINTER"], ["(:, 1*1)", "(LEN = :, KIND = 1 * 1)", ", POINTER"], ["(KIND=1*1, LEN=:)", "(LEN = :, KIND = 1 * 1)", ", POINTER"])) def test_character_kind( fortran_reader, fortran_writer, tmpdir, char_type_in, char_type_out, pointer): - '''Test that characters with kind clauses in various formats are + '''Test that characters with kind and len clauses in various formats are supported. ''' @@ -674,7 +674,7 @@ def test_character_kind( f" subroutine select_type(type_selector, character_type)\n" f" class(*), target :: type_selector\n" f" character{char_type_out}{pointer} :: character_type\n" - f" character(256) :: type_string\n" + f" character(len=256) :: type_string\n" f" character(len=256), pointer :: ptr_character_star => null()\n\n" f" type_string = ''\n" f" select type(type_selector)\n" @@ -724,8 +724,8 @@ def test_class_target( f" contains\n" f" subroutine select_type(type_selector, character_type)\n" f" CLASS(*), {post_attribute} :: type_selector\n" - f" CHARACTER(LEN = *) :: character_type\n" - f" character(256) :: type_string\n" + f" CHARACTER(LEN=*) :: character_type\n" + f" character(len=256) :: type_string\n" f" CHARACTER(LEN=256), pointer :: ptr_CHARACTER_star => null()\n\n" f" type_string = ''\n" f" SELECT TYPE(type_selector)\n" diff --git a/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py index abc0701c90..ddb5a971ac 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py @@ -187,7 +187,7 @@ def test_function_handler(fortran_reader, fortran_writer): @pytest.mark.parametrize("basic_type, rhs_val", [("real", "1.0"), ("integer", "1"), ("logical", ".false."), - ("character", "'b'")]) + ("character(len=1)", "'b'")]) def test_function_type_prefix(fortran_reader, fortran_writer, basic_type, rhs_val): ''' @@ -223,7 +223,9 @@ def test_function_type_prefix(fortran_reader, fortran_writer, assert isinstance(routine, Routine) return_sym = routine.return_symbol assert isinstance(return_sym, DataSymbol) - assert return_sym.datatype.intrinsic == TYPE_MAP_FROM_FORTRAN[basic_type] + # Allow for the "(len=...)" on the end of the character case. + type_name = basic_type.split("(")[0] + assert return_sym.datatype.intrinsic == TYPE_MAP_FROM_FORTRAN[type_name] result = fortran_writer(psyir) assert result == expected @@ -327,8 +329,8 @@ def test_function_unsupported_type(fortran_reader): " my_func = CMPLX(1.0, 1.0)\n" " end function my_func\n" "\n" - " character(len=3) function Agrif_CFixed()\n" - " Agrif_CFixed = '0'\n" + " complex function Agrif_CFixed()\n" + " Agrif_CFixed = (0.0, 1.0)\n" " end function Agrif_CFixed\n" "end module\n") psyir = fortran_reader.psyir_from_source(code) @@ -426,9 +428,9 @@ def test_unsupported_routine_prefix(fortran_reader, fn_prefix, routine_type): assert isinstance(fsym.datatype, UnresolvedType) -def test_unsupported_char_len_function(fortran_reader): - ''' Check that we get a CodeBlock if a Fortran function is of character - type with a specified length. ''' +def test_char_len_function(fortran_reader): + ''' Check that a Fortran function of character type with a specified length + is handled correctly. ''' code = ("module a\n" "contains\n" " character(len=2) function my_func()\n" @@ -437,12 +439,11 @@ def test_unsupported_char_len_function(fortran_reader): " end function my_func\n" "end module\n") psyir = fortran_reader.psyir_from_source(code) - cblock = psyir.children[0].children[0] - assert isinstance(cblock, CodeBlock) - assert "LEN = 2" in str(cblock.parse_tree_nodes[0]) + my_func = psyir.children[0].children[0] + assert isinstance(my_func, Routine) fsym = psyir.children[0].symbol_table.lookup("my_func") assert isinstance(fsym, RoutineSymbol) - assert isinstance(fsym.datatype, UnresolvedType) + assert fsym.datatype.length.value == "2" def test_unsupported_contains_subroutine(fortran_reader): diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 9ca8fa37a2..7dd3b84eff 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -707,9 +707,11 @@ def test_declarations_with_initialisations(fortran_reader): psyir = fortran_reader.psyir_from_source( """ module test + implicit none integer :: a = 1, aa = 4 integer, save :: b = 1 integer, parameter :: c = 1 + integer, parameter :: MAXDIM = 4 contains subroutine mysub() integer :: d = 1 @@ -749,6 +751,37 @@ def test_declarations_with_initialisations(fortran_reader): assert fsym.is_constant is True +@pytest.mark.usefixtures("f2008_parser") +def test_array_declarations_with_initialisations(fortran_reader): + '''Test that Fparser2Reader keeps variable initialisation + expressions for arrays. + + ''' + psyir = fortran_reader.psyir_from_source( + """ + module test + implicit none + integer, parameter :: MAXDIM = 4 + contains + subroutine mysub() + integer, dimension(3) :: g = (/1, 2, 3/) + integer :: i + integer, dimension(MAXDIM) :: h = (/ (i, i=1,MAXDIM) /) + integer, dimension(2,2) :: l = MAXDIM + end subroutine mysub + end module test + """) + inner_st = psyir.walk(Routine)[0].symbol_table + gsym = inner_st.lookup('g') + hsym = inner_st.lookup('h') + all_syms = [gsym, hsym] + + assert all(isinstance(sym, DataSymbol) for sym in all_syms) + assert all(isinstance(sym.initial_value, CodeBlock) for sym in all_syms) + lsym = inner_st.lookup('l') + assert isinstance(lsym.initial_value, Reference) + + @pytest.mark.usefixtures("f2008_parser") def test_process_declarations_accessibility(): ''' Check that process_declarations behaves as expected when a visibility @@ -838,18 +871,6 @@ def test_process_unsupported_declarations(fortran_reader): assert isinstance(c2sym.datatype, UnsupportedFortranType) assert c2sym.datatype.declaration == "COMPLEX :: c2" - # Char lengths are not supported - psyir = fortran_reader.psyir_from_source("program dummy\n" - "character :: l*4\n" - "end program") - assert isinstance(psyir.children[0].symbol_table.lookup("l").datatype, - UnsupportedFortranType) - psyir = fortran_reader.psyir_from_source("program dummy\n" - "character(len=4) :: l\n" - "end program") - assert isinstance(psyir.children[0].symbol_table.lookup("l").datatype, - UnsupportedFortranType) - # Test that CodeBlocks and references to variables initialised with a # CodeBlock are handled correctly reader = FortranStringReader( diff --git a/src/psyclone/tests/psyir/nodes/array_reference_test.py b/src/psyclone/tests/psyir/nodes/array_reference_test.py index 66a9fbf5ac..c7e0f9f911 100644 --- a/src/psyclone/tests/psyir/nodes/array_reference_test.py +++ b/src/psyclone/tests/psyir/nodes/array_reference_test.py @@ -47,9 +47,9 @@ Reference, ArrayReference, Assignment, Literal, BinaryOperation, Range, KernelSchedule, IntrinsicCall) from psyclone.psyir.symbols import ( - ArrayType, DataSymbol, DataTypeSymbol, UnresolvedType, ScalarType, + ArrayType, DataSymbol, DataTypeSymbol, ScalarType, REAL_SINGLE_TYPE, INTEGER_SINGLE_TYPE, REAL_TYPE, Symbol, INTEGER_TYPE, - UnsupportedFortranType, StructureType) + UnsupportedFortranType, StructureType, UnresolvedType) from psyclone.tests.utilities import check_links @@ -479,6 +479,7 @@ def test_array_datatype(fortran_reader): real, dimension(10) :: test real, dimension(10, 8) :: test_2d real, dimension(3:) :: test3 + character(len=10) :: my_string, string2 real :: thing thing = test(1) @@ -486,7 +487,7 @@ def test_array_datatype(fortran_reader): thing = test_2d(2, 2:6:2) thing = test3(:) thing = test_2d(:, 1) - + string2 = my_string(1:10) end subroutine code""" psyir = fortran_reader.psyir_from_source(code) @@ -540,6 +541,14 @@ def test_array_datatype(fortran_reader): assert dtype.shape[0].lower.value == "1" assert dtype.shape[0].upper.value == "10" + # Character sub-strings are currently mis-identified as array + # ranges (TODO #3240): + # my_string(1:10) + dref = refs[5] + dtype = dref.datatype + assert isinstance(dtype, ArrayType) + assert dtype.intrinsic is ScalarType.Intrinsic.CHARACTER + # Reference to a single element of an array of structures. one = Literal("1", INTEGER_TYPE) two = Literal("2", INTEGER_TYPE) diff --git a/src/psyclone/tests/psyir/symbols/datasymbol_test.py b/src/psyclone/tests/psyir/symbols/datasymbol_test.py index 97a4eb18a9..aa5cbf02a6 100644 --- a/src/psyclone/tests/psyir/symbols/datasymbol_test.py +++ b/src/psyclone/tests/psyir/symbols/datasymbol_test.py @@ -306,7 +306,8 @@ def test_datasymbol_initial_value_setter_invalid(): with pytest.raises(ValueError) as error: DataSymbol('a', CHARACTER_TYPE, initial_value=42) assert ("Error setting initial value for symbol 'a'. This DataSymbol " - "instance datatype is 'Scalar' meaning " + "instance datatype is 'Scalar]>' meaning " "the initial value should be") in str(error.value) assert "'str'>' but found " in str(error.value) assert "'int'>'." in str(error.value) diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index 1a0bf3c2b8..468a059809 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -153,6 +153,18 @@ def test_scalartype_enum_precision(intrinsic, precision): assert scalar_type.is_allocatable is False +@pytest.mark.parametrize("attribute", + [ScalarType.Precision.DOUBLE, + ScalarType.Intrinsic.BOOLEAN, + ScalarType.CharLengthParameter.DEFERRED]) +def test_scalartypeattribute(attribute): + ''' + Test the debug_string() and copy() methods provided by ScalarTypeAttribute. + ''' + assert attribute.copy() == attribute + assert attribute.debug_string() == attribute.name + + @pytest.mark.parametrize("precision", [1, 8, 16]) @pytest.mark.parametrize("intrinsic", [ScalarType.Intrinsic.INTEGER, ScalarType.Intrinsic.REAL, @@ -196,6 +208,46 @@ def test_scalartype_datasymbol_precision(intrinsic): assert scalar_type == scalar_type2 +def test_scalartype_character_length(): + ''' + Test the length getter and setter of ScalarType. + ''' + data_type = ScalarType(ScalarType.Intrinsic.CHARACTER, + ScalarType.Precision.UNDEFINED, + length=Literal("5", INTEGER_TYPE)) + assert data_type.length.value == "5" + data_type.length = Reference(Symbol("MAX_LEN")) + assert data_type.length.symbol.name == "MAX_LEN" + data_type.length = ScalarType.CharLengthParameter.DEFERRED + assert data_type.length == ScalarType.CharLengthParameter.DEFERRED + assert data_type.length.debug_string() == "DEFERRED" + + with pytest.raises(ValueError) as err: + data_type.length = -1 + assert ("specified using an int then it must be >= 0 but got: -1" + in str(err.value)) + with pytest.raises(TypeError) as err: + data_type.length = "yes" + assert ("must be a non-negative int, ScalarType.CharLengthParameter or " + "DataNode but got 'str'" in str(err.value)) + + # Now test with a non-character type. + non_char = INTEGER_TYPE + # The getter raises an error. + with pytest.raises(TypeError) as err: + _ = non_char.length + assert ("ScalarType of intrinsic type 'Intrinsic.INTEGER' does not have " + "the 'length' property" in str(err.value)) + # The setter does permit a value of None. + non_char.length = None + # The setter rejects a value that is not None. + with pytest.raises(TypeError) as err: + non_char.length = 10 + assert ("character type support the length property but length '10' was " + "supplied to an intrinsic type of 'Intrinsic.INTEGER'" + in str(err.value)) + + def test_scalartype_equal(): ''' Check that ScalarType instances with different precision or intrinsic type @@ -287,6 +339,12 @@ def test_scalartype_str(): data_type = ScalarType(ScalarType.Intrinsic.BOOLEAN, ScalarType.Precision.UNDEFINED) assert str(data_type) == "Scalar" + str_type = ScalarType(ScalarType.Intrinsic.CHARACTER, + ScalarType.Precision.UNDEFINED, + 4) + assert str(str_type) == ( + "Scalar]>") def test_scalartype_immutable(): @@ -322,6 +380,12 @@ def test_scalartype_replace_symbols(): stype2.replace_symbols_using(table) # Precision symbol should have been updated. assert stype2.precision.symbol is rdef2 + # Repeat but for a Symbol used to define the length of a character string + chartype = ScalarType(ScalarType.Intrinsic.CHARACTER, + ScalarType.Precision.UNDEFINED, + Reference(rdef)) + chartype.replace_symbols_using(table) + assert chartype.length.symbol is rdef2 def test_scalartype_get_all_accessed_symbols(): @@ -331,6 +395,11 @@ def test_scalartype_get_all_accessed_symbols(): Reference(rdef)) dependent_symbols = stype2.get_all_accessed_symbols() assert rdef in dependent_symbols + chartype = ScalarType(ScalarType.Intrinsic.CHARACTER, + ScalarType.Precision.UNDEFINED, + Reference(rdef)) + dependent_symbols2 = chartype.get_all_accessed_symbols() + assert rdef in dependent_symbols2 def test_scalartype_copy(): @@ -357,6 +426,23 @@ def test_scalartype_copy(): assert rcopy.precision == stype2.precision assert rcopy.precision is not stype2.precision + # Repeat but with precision as an int. + # TODO #3135 - once precision is always stored as a DataNode this separate + # test won't be necessary. + itype = ScalarType(ScalarType.Intrinsic.INTEGER, 4) + icopy = itype.copy() + assert icopy.precision == 4 + + # Test a character type with a length. + chartype = ScalarType(ScalarType.Intrinsic.CHARACTER, + ScalarType.Precision.UNDEFINED, + Reference(rdef)) + ccopy = chartype.copy() + # Length expression has been copied. + assert ccopy.length is not chartype.length + # Referenced Symbol is unchanged. + assert ccopy.length.symbol is rdef + # ArrayType class diff --git a/src/psyclone/tests/psyir/transformations/acc_kernels_trans_test.py b/src/psyclone/tests/psyir/transformations/acc_kernels_trans_test.py index 28f80876d9..04692773be 100644 --- a/src/psyclone/tests/psyir/transformations/acc_kernels_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/acc_kernels_trans_test.py @@ -44,8 +44,9 @@ from psyclone.errors import GenerationError from psyclone.psyir.nodes import ( - Assignment, ACCKernelsDirective, Loop, Routine + Assignment, ACCKernelsDirective, Loop, Reference, Routine ) +from psyclone.psyir.symbols import UnsupportedFortranType from psyclone.psyir.transformations import ( ACCKernelsTrans, TransformationError, ProfileTrans) from psyclone.transformations import ACCEnterDataTrans, ACCLoopTrans @@ -442,13 +443,30 @@ def test_no_assumed_size_char_in_kernels(fortran_reader): or intrinsics that aren't available on GPU. ''' + # A routine with some quite complex argument types to check the various + # branches of the code that finds out whether there's a character length + # specified. Although some of these arguments aren't actually used in + # the subroutine, they are needed for test coverage. code = '''\ -subroutine ice(assumed_size_char, assumed2) +subroutine ice(dtype, dtype_ptr, type_list, assumed_size_char, assumed2, & + assumed3, assumed4, ctype) + use some_mod, only: a_type implicit none + type(a_type) :: dtype + ! An unsupported datatype which has a partial_datatype that is a + ! DataTypeSymbol. + type(d_type), pointer :: dtype_ptr + ! An unsupported datatype which has a partial_datatype that is an + ! array of DataTypeSymbol. + type(a_type), dimension(10) :: type_list character(len = *), intent(in) :: assumed_size_char character*(*) :: assumed2 + character(len=*), optional :: assumed3 character(len=10) :: explicit_size_char real, dimension(10,10) :: my_var + character(len=*), dimension(:) :: assumed4 + ! An unsupported declaration for which we have no partial_datatype + complex :: ctype if (assumed_size_char == 'literal') then my_var(:UBOUND(my_var)) = 0.0 @@ -467,7 +485,11 @@ def test_no_assumed_size_char_in_kernels(fortran_reader): explicit_size_char = assumed2 -end + assumed3(:) = '' + + assumed4 = '' + +end subroutine ice ''' psyir = fortran_reader.psyir_from_source(code) sub = psyir.walk(Routine)[0] @@ -514,6 +536,18 @@ def test_no_assumed_size_char_in_kernels(fortran_reader): assert ("Assumed-size character variables cannot be enclosed in an OpenACC" " region but found 'explicit_size_char = assumed2" in str(err.value)) + # Assumed-size within an UnsupportedFortranType + assert isinstance(sub.symbol_table.lookup("assumed3").datatype, + UnsupportedFortranType) + with pytest.raises(TransformationError) as err: + acc_trans.validate(sub.children[6]) + assert ("Assumed-size character variables cannot be enclosed in an OpenACC" + " region but found 'assumed3(:) = ''" in str(err.value)) + # Array of character strings + with pytest.raises(TransformationError) as err: + acc_trans.validate(sub.children[7]) + assert ("Assumed-size character variables cannot be enclosed in an OpenACC" + " region but found 'assumed4 = ''" in str(err.value)) def test_check_async_queue_with_enter_data(fortran_reader): @@ -526,13 +560,17 @@ def test_check_async_queue_with_enter_data(fortran_reader): "or bool, got : 3.5" in str(err.value)) psyir = fortran_reader.psyir_from_source( "program two_loops\n" - " integer :: ji\n" + " integer :: ji, aqueue\n" " real :: array(10,10)\n" " do ji = 1, 5\n" " array(ji,1) = 2.0*array(ji,2)\n" " end do\n" "end program two_loops\n") prog = psyir.walk(Routine)[0] + # Check that we can supply a bool or a Reference to specify the queue. + acc_trans.check_async_queue(prog.walk(Loop), True) + acc_trans.check_async_queue(prog.walk(Loop), + Reference(prog.symbol_table.lookup("aqueue"))) # TODO #2668 deprecate options coverage. This test is left for options # coverage acc_edata_trans.apply(prog, options={"async_queue": 1}) diff --git a/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py b/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py index 5c6d2b8daa..5c3154ffd1 100644 --- a/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py @@ -627,8 +627,8 @@ def test_character_validation(fortran_reader): def test_unsupported_type_character(fortran_reader): ''' Test that the check for character references inside the assignment - being transformed also works with 'unsupported characters arrays' (see - issue #2612). + being transformed also works with character substrings. + ''' code = '''subroutine test() character(LEN=100) :: a @@ -640,8 +640,9 @@ def test_unsupported_type_character(fortran_reader): end subroutine test''' psyir = fortran_reader.psyir_from_source(code) + trans = ArrayAssignment2LoopsTrans() + for assign in psyir.walk(Assignment): - trans = ArrayAssignment2LoopsTrans() with pytest.raises(TransformationError) as info: trans.validate(assign) assert ( diff --git a/src/psyclone/tests/psyir/transformations/datanode_to_temp_trans_test.py b/src/psyclone/tests/psyir/transformations/datanode_to_temp_trans_test.py index f362b7c4ec..f40b56e832 100644 --- a/src/psyclone/tests/psyir/transformations/datanode_to_temp_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/datanode_to_temp_trans_test.py @@ -83,7 +83,7 @@ def test_datanodetotemptrans_validate(fortran_reader, tmp_path): "may enable resolution of these symbols." in str(err.value)) code = """subroutine test - character(len=25) :: a, b + complex :: a, b b = a end subroutine test""" diff --git a/src/psyclone/tests/psyir/transformations/replace_reference_by_literal_trans_test.py b/src/psyclone/tests/psyir/transformations/replace_reference_by_literal_trans_test.py index 315745b009..a5fac5a5c8 100644 --- a/src/psyclone/tests/psyir/transformations/replace_reference_by_literal_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/replace_reference_by_literal_trans_test.py @@ -363,14 +363,14 @@ def test_rrbl_code_not_transformed_because_involves_more_than_just_literal( assert "x = b" in written_code -def test_rrbl_annotating_fortran_code_because_str_not_literal( +def test_rrbl_annotating_fortran_code_because_complex_not_literal( fortran_reader, fortran_writer ): """test fortran code annotation with transformation warning""" source = """subroutine foo() - character(len=4), parameter :: a = "toto" - character(len=4):: x + complex, parameter :: a = (1.0, 1.0) + complex :: x x = a end subroutine""" psyir = fortran_reader.psyir_from_source(source) @@ -382,13 +382,12 @@ def test_rrbl_annotating_fortran_code_because_str_not_literal( rbbl.apply(routine_foo) written_code = fortran_writer(routine_foo.ancestor(Container)) assert "x = a" in written_code - assert 'x = "toto"' not in written_code - toto_var_name = '"toto"' + assert written_code.count("x = ") == 1 assert ( f"{rbbl.name}: only " - + "support constant (parameter) but UnsupportedFortranType" - + f"('CHARACTER(LEN = 4), PARAMETER :: a = {toto_var_name}') " - + "is not seen by Psyclone as a constant." + f"support constant (parameter) but UnsupportedFortranType" + f"('COMPLEX, PARAMETER :: a = (1.0, 1.0)') " + f"is not seen by Psyclone as a constant." in written_code )