diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index ce4cadf042..a4de95fbec 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -1439,6 +1439,20 @@ def literal_node(self, node): return result + def arrayconstructor_node(self, node): + '''This method is called when an ArrayConstructor instance is + found in the PSyIR tree. + + :param node: an ArrayConstructor PSyIR node. + :type node: :py:class:`psyclone.psyir.nodes.ArrayConstructor` + + :returns: the Fortran code as a string. + :rtype: str + + ''' + contents = ", ".join([self._visit(child) for child in node.children]) + return "[" + contents + "]" + def ifblock_node(self, node): '''This method is called when an IfBlock instance is found in the PSyIR tree. diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index c43856e4d3..7fb17e0297 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -60,6 +60,7 @@ from psyclone.errors import InternalError, GenerationError from psyclone.psyir.commentable_mixin import CommentableMixin from psyclone.psyir.nodes import ( + ArrayConstructor, ArrayMember, ACCRoutineDirective, ArrayOfStructuresReference, ArrayReference, Assignment, BinaryOperation, Call, CodeBlock, Container, DataNode, Directive, FileContainer, IfBlock, IntrinsicCall, Literal, Loop, @@ -991,6 +992,7 @@ def __init__( Fortran2003.Allocate_Stmt: self._allocate_handler, Fortran2003.Allocate_Shape_Spec: self._allocate_shape_spec_handler, Fortran2003.Assignment_Stmt: self._assignment_handler, + Fortran2003.Array_Constructor: self._array_constructor_handler, Fortran2003.Structure_Constructor: self._call_handler, Fortran2003.Data_Pointer_Object: self._structure_accessor_handler, Fortran2003.Data_Ref: self._structure_accessor_handler, @@ -4975,6 +4977,43 @@ def _assignment_handler(self, node, parent): return assignment + def _array_constructor_handler(self, node, parent): + ''' + Transforms an fparser2 Array_Constructor to the PSyIR representation. + + :param node: node in fparser2 AST. + :type node: :py:class:`fparser.two.Fortran2003.Array_Constructor` + :param parent: Parent node of the PSyIR node we are constructing. + :type parent: :py:class:`psyclone.psyir.nodes.Node` + + :returns: PSyIR representation of node. + :rtype: :py:class:`psyclone.psyir.nodes.ArrayConstructor` + + :raises NotImplementedError: if the parse tree contains unsupported + elements. + ''' + ac_spec = node.items[1] + if isinstance(ac_spec, Fortran2003.Ac_Value_List): + elems = node.items[1].items + # Check that no elements are 'Ac_Implied_Do' + for elem in elems: + if isinstance(elem, Fortran2003.Ac_Implied_Do): + raise NotImplementedError( + "Array constructors with implied do loops cannot be " + "handled in the PSyIR") + array_cons = ArrayConstructor(ast=node, parent=parent) + self.process_nodes(parent=array_cons, nodes=elems) + return array_cons + elif isinstance(ac_spec, Fortran2003.Ac_Spec): + raise NotImplementedError( + "Array constructors with type specifications cannot be " + "handled in the PSyIR") + else: # pragma: no cover + # This should never be reached, but we defensively raise + # an exception just in case. + raise NotImplementedError( + "Unexpected array constructor form encountered") + def _structure_accessor_handler(self, node, parent): ''' Create the PSyIR for structure accessors found in fparser2 Data_Ref, diff --git a/src/psyclone/psyir/nodes/__init__.py b/src/psyclone/psyir/nodes/__init__.py index e964604174..466694a0f1 100644 --- a/src/psyclone/psyir/nodes/__init__.py +++ b/src/psyclone/psyir/nodes/__init__.py @@ -39,6 +39,7 @@ ''' PSyIR nodes package module ''' +from psyclone.psyir.nodes.array_constructor import ArrayConstructor from psyclone.psyir.nodes.acc_clauses import ( ACCAsyncQueueClause, ACCCopyClause, ACCCopyInClause, ACCCopyOutClause) @@ -113,6 +114,7 @@ # this package e.g. 'from psyclone.psyir.nodes import Literal' __all__ = [ 'colored', + 'ArrayConstructor', 'ArrayMember', 'ArrayReference', 'ArrayOfStructuresMember', diff --git a/src/psyclone/psyir/nodes/array_constructor.py b/src/psyclone/psyir/nodes/array_constructor.py new file mode 100644 index 0000000000..14a0ab4af3 --- /dev/null +++ b/src/psyclone/psyir/nodes/array_constructor.py @@ -0,0 +1,153 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2017-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. +# ----------------------------------------------------------------------------- +# Authors M. Naylor, University of Cambridge, UK +# ----------------------------------------------------------------------------- + +''' This module contains the ArrayConstructor node implementation.''' + +from psyclone.core import VariablesAccessMap +from psyclone.psyir.symbols import ( + Symbol, UnresolvedType, ScalarType, ArrayType, DataTypeSymbol) +from psyclone.psyir.nodes.datanode import DataNode + + +class ArrayConstructor(DataNode): + ''' + Node representing an array constructor. + ''' + + # Textual description of the node. + _children_valid_format = "[DataNode]*" + _text_name = "ArrayConstructor" + _colour = "yellow" + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + @staticmethod + def create(*elems: DataNode): + '''Create and ArrayConstructor instance representing an array + with the given elements. + + :param elems: the elements of the array being constructed. + :returns: an ArrayConstructor instance. + :rtype: :py:class:`psyclone.psyir.nodes.IfBlock` + + :raises GenerationError: if the arguments are not of the \ + expected type. + ''' + array_cons = ArrayConstructor() + for elem in elems: + array_cons.children.append(elem) + return array_cons + + @staticmethod + def _validate_child(position, child): + ''' + :param int position: the position to be validated. + :param child: a child to be validated. + :type child: :py:class:`psyclone.psyir.nodes.Node` + + :return: whether the given child and position are valid for this node. + :rtype: bool + ''' + return isinstance(child, DataNode) + + @property + def datatype(self): + ''' + :returns: the type of this array constructor. + :rtype: :py:class:`psyclone.psyir.symbols.DataType` + ''' + # The result of an array constructor is always a rank-1 array. + # We look through the children to find the array-element type. + elem_type = UnresolvedType() + for child in self.children: + if isinstance(child.datatype, ArrayType): + elem_type = child.datatype.elemental_type + break + elif isinstance(child.datatype, ScalarType): + elem_type = child.datatype + break + elif isinstance(child.datatype, DataTypeSymbol): + elem_type = child.datatype + break + return ArrayType(elem_type, [ArrayType.Extent.ATTRIBUTE]) + + def node_str(self, colour=True): + ''' + Construct a text representation of this node, optionally containing + colour control codes. + + :param bool colour: whether or not to include colour control codes. + + :returns: description of this PSyIR node. + :rtype: str + ''' + return f"{self.coloured_name(colour)}[]" + + def get_all_accessed_symbols(self) -> set[Symbol]: + ''' + :returns: a set of all the symbols accessed inside this + ArrayConstructor. + ''' + return super().get_all_accessed_symbols() + + def reference_accesses(self) -> VariablesAccessMap: + ''' + :returns: a map of all the symbol accessed inside this node, the + keys are Signatures (unique identifiers to a symbol and its + structure accessors) and the values are AccessSequence + (a sequence of AccessTypes). + ''' + return super().reference_accesses() + + def replace_symbols_using(self, table_or_symbol): + ''' + Replace any Symbols referred to by this object with those in the + supplied SymbolTable (or just the supplied Symbol instance) if they + have matching names. If there is no match for a given Symbol then it + is 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` + + ''' + super().replace_symbols_using(table_or_symbol) + + +# For AutoAPI documentation generation +__all__ = ['ArrayConstructor'] diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index 5c9e6861d9..aa70b5cdb9 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -218,7 +218,7 @@ def initial_value(self, new_value): # pylint: disable=import-outside-toplevel from psyclone.psyir.nodes import ( Assignment, Node, Literal, Operation, Reference, - CodeBlock, IntrinsicCall) + CodeBlock, IntrinsicCall, ArrayConstructor) from psyclone.psyir.symbols.datatypes import (ScalarType, ArrayType, UnsupportedType) @@ -239,7 +239,8 @@ def initial_value(self, new_value): if isinstance(new_value, Node): for node in new_value.walk(Node): if not isinstance(node, (Literal, Operation, Reference, - CodeBlock, IntrinsicCall)): + CodeBlock, IntrinsicCall, + ArrayConstructor)): raise ValueError( f"Error setting initial value for symbol " f"'{self.name}'. PSyIR static expressions can only" diff --git a/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py b/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py index 852f0fe77e..feec8bd583 100644 --- a/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py +++ b/src/psyclone/psyir/transformations/arrayassignment2loops_trans.py @@ -47,7 +47,7 @@ from psyclone.psyGen import Transformation from psyclone.psyir.nodes import ( Assignment, Call, IntrinsicCall, Loop, Literal, Node, Range, Reference, - CodeBlock, Routine, BinaryOperation) + CodeBlock, Routine, BinaryOperation, ArrayConstructor) from psyclone.psyir.nodes.array_mixin import ArrayMixin from psyclone.psyir.nodes.structure_accessor_mixin import ( StructureAccessorMixin) @@ -245,6 +245,17 @@ def validate( raise TransformationError(LazyString( lambda: f"{message}, but found:\n{node.debug_string()}")) + # Do not allow to transform expressions with ArrayConstructors + # TODO: this could be relaxed in future + if node.has_descendant(ArrayConstructor): + message = (f"{self.name} does not support array assignments that" + " contain an ArrayConstructor anywhere in" + " the expression") + if verbose: + node.append_preceding_comment(message) + raise TransformationError(LazyString( + lambda: f"{message}, but found:\n{node.debug_string()}")) + try: node.scope except SymbolError: diff --git a/src/psyclone/tests/core/symbolic_maths_test.py b/src/psyclone/tests/core/symbolic_maths_test.py index bd042da0c9..2561fc0021 100644 --- a/src/psyclone/tests/core/symbolic_maths_test.py +++ b/src/psyclone/tests/core/symbolic_maths_test.py @@ -251,7 +251,7 @@ def test_symbolic_maths_never_equal_error(fortran_reader): source = ( "program test_prog\n" " integer :: a(2)\n" - " a(:) = (/1, 2/)\n" + " a(:) = (/integer :: 1, 2/)\n" "end program test_prog\n") psyir = fortran_reader.psyir_from_source(source) assignment = psyir.children[0][0] diff --git a/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py b/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py index 067237d141..9519fb2a07 100644 --- a/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py @@ -164,7 +164,7 @@ def test_gen_param_decls_case_insensitive(fortran_reader, "integer(kind=an_int), parameter :: a_second_int = 5_i_def\n" "integer, parameter :: nfieldnames3d = 4\n" "integer, dimension(nfieldnames3d), parameter :: " - "interpolationlevels = [2, 0, HUGE(InterpolationLevels) / 3, 0]\n") + "InterpolationLevels = [2, 0, HUGE(InterpolationLevels) / 3, 0]\n") def test_gen_decls(fortran_writer): diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index 81442f18c7..847d6e9955 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -54,7 +54,8 @@ ArrayReference, ArrayOfStructuresReference, Range, StructureReference, Schedule, Routine, Return, FileContainer, IfBlock, OMPTaskloopDirective, OMPMasterDirective, OMPParallelDirective, Loop, OMPNumTasksClause, - OMPDependClause, IntrinsicCall, OMPReductionClause, UnknownDirective) + OMPDependClause, IntrinsicCall, OMPReductionClause, UnknownDirective, + ArrayConstructor) from psyclone.psyir.symbols import ( ArgumentInterface, ContainerSymbol, DataSymbol, GenericInterfaceSymbol, ImportInterface, RoutineSymbol, StaticInterface, Symbol, SymbolTable, @@ -1742,7 +1743,7 @@ def test_fw_codeblock_2(fortran_reader, fortran_writer, tmpdir): '''Check the FortranWriter class codeblock method correctly prints out the Fortran representation when there is a code block that is part of a line (not a whole line). In this case the data initialisation - of the array 'a' "(/ 0.0 /)" is a code block. + of the array 'a' "(/ real :: 0.0 /)" is a code block. ''' # Generate fparser2 parse tree from Fortran code. @@ -1751,7 +1752,7 @@ def test_fw_codeblock_2(fortran_reader, fortran_writer, tmpdir): "contains\n" "subroutine tmp()\n" " real a(1)\n" - " a = (/ 0.0 /)\n" + " a = (/ real :: 0.0 /)\n" "end subroutine tmp\n" "end module test") psyir = fortran_reader.psyir_from_source(code) @@ -1761,7 +1762,7 @@ def test_fw_codeblock_2(fortran_reader, fortran_writer, tmpdir): # Generate Fortran from the PSyIR result = fortran_writer(psyir) - assert "a = (/0.0/)" in result + assert "a = (/REAL :: 0.0/)" in result assert Compile(tmpdir).string_compiles(result) @@ -2351,3 +2352,17 @@ def test_fw_unknowndirective(fortran_writer): assert fortran_writer(direc) == "!$omp atomic\n" direc = UnknownDirective(" IVDEP", "DIR") assert fortran_writer(direc) == "!DIR$ IVDEP\n" + + +def test_array_constructor(fortran_writer): + ''' + Test that the ArrayConstructor visitor generates the expected string. + ''' + array_cons = ArrayConstructor.create( + Literal("1", ScalarType.integer_type()), + Literal("2", ScalarType.integer_type()), + ArrayConstructor.create( + Literal("3", ScalarType.integer_type()), + Literal("4", ScalarType.integer_type()))) + output = fortran_writer(array_cons) + assert output == "[1, 2, [3, 4]]" diff --git a/src/psyclone/tests/psyir/frontend/fparser2_array_constructor_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_array_constructor_handler_test.py new file mode 100644 index 0000000000..d5702ae02a --- /dev/null +++ b/src/psyclone/tests/psyir/frontend/fparser2_array_constructor_handler_test.py @@ -0,0 +1,213 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2019-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. +# ----------------------------------------------------------------------------- +# Authors: M. Naylor, University of Cambridge + + +'''Performs py.test tests on the support for array constructors in the fparser2 +PSyIR front-end ''' + +import pytest +from psyclone.psyir.nodes import ( + ArrayConstructor, Reference, Literal, BinaryOperation, CodeBlock) +from psyclone.psyir.symbols import ScalarType, ArrayType, DataTypeSymbol + + +def test_handling_array_constructor_assignment(fortran_reader): + '''Check that the fparser2 frontend can handle simple + array constructors, without type specs and implicit do loops. + ''' + code = """ +program my_prog + implicit none + integer :: arr(3) + integer :: x + x = 10 + arr(:) = [1, x, x+2, [x]] +end program my_prog +""" + prog = fortran_reader.psyir_from_source(code) + ctrs = prog.walk(ArrayConstructor) + + # Program has two array constructors, one with 4 elements one with 1 + assert len(ctrs) == 2 + assert len(ctrs[0].children) == 4 + assert len(ctrs[1].children) == 1 + + # Look at the outer array constructor first + ctr = ctrs[0] + + # The first element is the literal 1 + assert isinstance(ctr.children[0], Literal) + assert ctr.children[0].value == "1" + + # The second element is the reference "x" + assert isinstance(ctr.children[1], Reference) + assert ctr.children[1].name == "x" + + # The third element is the binary operation "x+2" + assert isinstance(ctr.children[2], BinaryOperation) + assert ctr.children[2].operator == BinaryOperation.Operator.ADD + assert isinstance(ctr.children[2].operands[0], Reference) + assert ctr.children[2].operands[0].name == "x" + assert isinstance(ctr.children[2].operands[1], Literal) + assert ctr.children[2].operands[1].value == "2" + + # The fourth element is a one-element ArrayConstructor holding "x" + assert isinstance(ctr.children[3], ArrayConstructor) + assert len(ctr.children[3].children) == 1 + assert isinstance(ctr.children[3].children[0], Reference) + assert ctr.children[3].children[0].name == "x" + + +def test_handling_array_constructor_arg(fortran_reader): + '''Check that the fparser2 frontend can handle array + constructors in arguments. + ''' + code = """ +program my_prog + implicit none + integer :: arr(4), arr2(2,2) + arr(:) = [0, 1, 2, 3] + arr2(:,:) = reshape(arr, [2,2]) +end program my_prog +""" + prog = fortran_reader.psyir_from_source(code) + ctrs = prog.walk(ArrayConstructor) + + # Program has two array constructors + assert len(ctrs) == 2 + + # The first has four elements (0, 1, 2, and 3) + assert len(ctrs[0].children) == 4 + for i in range(0, 4): + assert isinstance(ctrs[0].children[i], Literal) + assert ctrs[0].children[i].value == str(i) + + # The second has two elements (both 2) + assert len(ctrs[1].children) == 2 + for i in range(0, 2): + assert isinstance(ctrs[1].children[i], Literal) + assert ctrs[1].children[i].value == "2" + + +@pytest.mark.parametrize("arr_type", ["real", "integer"]) +def test_handling_array_constructor_datatype(fortran_reader, arr_type): + '''Check that the datatype of a parsed ArrayConstructor is correct. + ''' + if arr_type == "real": + one = "1.0" + else: + one = "1" + code = f""" +program my_prog + implicit none + {arr_type}, allocatable :: arr(:) + {arr_type} :: arr2(2, 2) + {arr_type} :: x + x = {one} + arr(:) = [x] + arr(:) = [x+{one}] + arr(:) = [[{one}]] + arr2(:,:) = {one} + arr(:) = [arr2] + arr(:) = [{one}, arr2] + arr(:) = [{arr_type} ::] ! Currently parsed to CodeBlock + arr(:) = [{arr_type} :: {one}, x] ! Currently parsed to CodeBlock +end program my_prog +""" + prog = fortran_reader.psyir_from_source(code) + + # Check that all ArrayConstructors are rank-1 arrays of scalars + count = 0 + for ctr in prog.walk(ArrayConstructor): + dt = ctr.datatype + assert isinstance(dt, ArrayType) + assert len(dt.shape) == 1 + assert isinstance(dt.elemental_type, ScalarType) + if arr_type == "real": + assert dt.elemental_type.intrinsic == ScalarType.Intrinsic.REAL + else: + assert dt.elemental_type.intrinsic == ScalarType.Intrinsic.INTEGER + count += 1 + + # Check number of ArrayConstructors + assert count == 6 + + # Check number of CodeBlocks + assert len(prog.walk(CodeBlock)) == 2 + + +def test_handling_array_constructor_derived_type(fortran_reader): + '''Check that the datatype of a parsed ArrayConstructor is correct + in the case of a derived type. + ''' + code = """ +module my_mod + type :: my_type + integer :: val + end type +contains + subroutine sub() + implicit none + type(my_type), allocatable :: arr(:) + type(my_type) :: arr2(2, 2) + type(my_type) :: x + x%val = 1 + arr(:) = [x] + arr(:) = [[x]] + arr2(:,:) = x + arr(:) = [arr2] + arr(:) = [x, arr2] + arr(:) = [my_type ::] ! Currently parsed to CodeBlock + arr(:) = [my_type :: x, x] ! Currently parsed to CodeBlock + end subroutine +end module +""" + prog = fortran_reader.psyir_from_source(code) + + # Check that all ArrayConstructors are rank-1 arrays of derived type + count = 0 + for ctr in prog.walk(ArrayConstructor): + dt = ctr.datatype + assert isinstance(dt, ArrayType) + assert len(dt.shape) == 1 + assert isinstance(dt.elemental_type, DataTypeSymbol) + assert dt.elemental_type.name == "my_type" + count += 1 + + # Check number of ArrayConstructors + assert count == 5 + + # Check number of CodeBlocks + assert len(prog.walk(CodeBlock)) == 2 diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 5e11cba552..462499da17 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -58,7 +58,7 @@ Schedule, CodeBlock, Assignment, Return, UnaryOperation, BinaryOperation, IfBlock, Reference, ArrayReference, Literal, KernelSchedule, RegionDirective, Routine, StandaloneDirective, - Call, IntrinsicCall) + Call, IntrinsicCall, ArrayConstructor) from psyclone.psyir.symbols import ( DataSymbol, ContainerSymbol, ArgumentInterface, ArrayType, SymbolError, ScalarType, RoutineSymbol, UnsupportedFortranType, @@ -776,7 +776,8 @@ def test_array_declarations_with_initialisations(fortran_reader): 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) + assert isinstance(gsym.initial_value, ArrayConstructor) + assert isinstance(hsym.initial_value, CodeBlock) lsym = inner_st.lookup('l') assert isinstance(lsym.initial_value, Reference) diff --git a/src/psyclone/tests/psyir/nodes/array_constructor_test.py b/src/psyclone/tests/psyir/nodes/array_constructor_test.py new file mode 100644 index 0000000000..fd31b40edc --- /dev/null +++ b/src/psyclone/tests/psyir/nodes/array_constructor_test.py @@ -0,0 +1,95 @@ +# ----------------------------------------------------------------------------- +# BSD 3-Clause License +# +# Copyright (c) 2019-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. +# ----------------------------------------------------------------------------- +# Authors: M. Naylor, University of Cambridge +# ----------------------------------------------------------------------------- + +''' Performs py.test tests on the ArrayConstructor PSyIR node. ''' + +import pytest +from psyclone.errors import GenerationError +from psyclone.psyir.nodes import ( + ArrayConstructor, Literal, BinaryOperation, Assignment, + Reference, IfBlock) +from psyclone.core import AccessType, Signature +from psyclone.psyir.symbols import ScalarType, DataSymbol +from psyclone.psyir.nodes.node import colored + + +def test_array_construction_valid(): + '''Test array construction and addition of children.''' + expr1 = Literal("1", ScalarType.integer_type()) + expr2 = Literal("2", ScalarType.integer_type()) + array_cons = ArrayConstructor.create(expr1, expr2) + expr3 = Literal("3", ScalarType.integer_type()) + expr4 = Literal("4", ScalarType.integer_type()) + expr5 = BinaryOperation.create(BinaryOperation.Operator.ADD, + expr3, expr4) + array_cons.children.append(expr5) + assert isinstance(array_cons.children[0], Literal) + assert isinstance(array_cons.children[1], Literal) + assert isinstance(array_cons.children[2], BinaryOperation) + + +def test_array_construction_invalid(): + '''Test invalid array construction.''' + # Construct an IfBlock + if_condition = Literal('true', ScalarType.boolean_type()) + if_body = [Assignment.create( + Reference(DataSymbol("tmp", ScalarType.real_single_type())), + Literal("10", ScalarType.integer_type()))] + ifblock = IfBlock.create(if_condition, if_body) + # Check that IfBlock cannot be an element of an ArrayConstructor + with pytest.raises(GenerationError) as err: + ArrayConstructor.create(ifblock) + assert ("Generation Error: Item 'IfBlock' can't be child 0 of " + "'ArrayConstructor'. The valid format is: '[DataNode]*" + in str(err.value)) + + +def test_array_construction_reference_accesses(): + '''Test the reference_accesses() method of an array constructor''' + ref = Reference(DataSymbol("tmp", ScalarType.integer_type())) + arr = ArrayConstructor.create(ref) + accs = arr.reference_accesses() + access_seq = accs[Signature("tmp")] + assert len(access_seq) == 1 + assert access_seq[0].access_type is AccessType.READ + + +def test_array_constructor_node_str(): + ''' Check the node_str method of the ArrayConstructor class.''' + lit = Literal("1", ScalarType.integer_single_type()) + array_cons = ArrayConstructor.create(lit) + coloured_array_cons = colored("ArrayConstructor", ArrayConstructor._colour) + assert f"{coloured_array_cons}[]" == array_cons.node_str() diff --git a/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py b/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py index d0a15ec3d7..86c288d5de 100644 --- a/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/arrayassignment2loops_trans_test.py @@ -939,13 +939,15 @@ def test_validate_indirect_indexing(fortran_reader): INTEGER, DIMENSION(8,kfld) :: ishtSi2 INTEGER :: jf ! Assignment with CodeBlock on RHS. - iwewe(:) = (/ jpwe,jpea,jpwe,jpea /) + iwewe(:) = (/integer :: jpwe,jpea,jpwe,jpea /) ! Assignment with CodeBlock in array-index expression. - iwewe(:) = ishtSi((/ jpwe,jpea,jpwe,jpea /), 1) + iwewe(:) = ishtSi((/integer :: jpwe,jpea,jpwe,jpea /), 1) ! Index expression that evaluate to an array is a valid range ishtSi(5:8,jf) = ishtSi2(iwewe+1, jf) ! Index expression contains a call to an unknown function. ishtSi(5:8,jf) = ishtSi2(my_func(1), jf) + ! Assignment with ArrayConstructor on RHS. + iwewe(:) = (/jpwe,jpea,jpwe,jpea /) end program test ''') assignments = psyir.walk(Assignment) @@ -966,6 +968,13 @@ def test_validate_indirect_indexing(fortran_reader): assert ("ArrayAssignment2LoopsTrans does not accept calls which are not " "guaranteed to return a scalar or be elemental, but found " "'my_func'" in str(err.value)) + with pytest.raises(TransformationError) as err: + trans.validate(assignments[4], verbose=True) + assert ("ArrayAssignment2LoopsTrans does not support array assignments " + "that contain an ArrayConstructor anywhere in the expression" + in str(err.value)) + assert ("that contain an ArrayConstructor" + in assignments[4].preceding_comment) def test_validate_structure(fortran_reader):