From ff28b15f1e6c512a56f0e1d102eb7cae80259f43 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Fri, 31 Jul 2026 16:46:49 +0100 Subject: [PATCH 01/10] #3460 Fix issues with recursive datatype copies --- .../psyir/symbols/data_type_symbol.py | 3 +- src/psyclone/psyir/symbols/datasymbol.py | 9 ++- src/psyclone/psyir/symbols/datatypes.py | 36 ++++++++--- .../tests/psyir/symbols/datatype_test.py | 61 +++++++++++++++++++ 4 files changed, 97 insertions(+), 12 deletions(-) diff --git a/src/psyclone/psyir/symbols/data_type_symbol.py b/src/psyclone/psyir/symbols/data_type_symbol.py index ae983adf83..d325ca9cb5 100644 --- a/src/psyclone/psyir/symbols/data_type_symbol.py +++ b/src/psyclone/psyir/symbols/data_type_symbol.py @@ -74,7 +74,8 @@ def copy(self): :rtype: :py:class:`psyclone.psyir.symbols.TypeSymbol` ''' - copy = type(self)(self.name, self.datatype, visibility=self.visibility, + copy = type(self)(self.name, self.datatype.copy(), + visibility=self.visibility, interface=self.interface.copy()) copy.preceding_comment = self.preceding_comment copy.inline_comment = self.inline_comment diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index aa70b5cdb9..2c6b8a6404 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -317,10 +317,13 @@ def copy(self): new_init_value = self.initial_value.copy() else: new_init_value = None - if self.is_array: + # pylint: disable=import-outside-toplevel + from psyclone.psyir.symbols.datatypes import UnsupportedFortranType + if self.is_array or isinstance(self.datatype, UnsupportedFortranType): # Ensure any References in the shape definition of an ArrayType - # are also copied. They will still point to the - # same Symbols as the original. + # or in the partial datatype of an UnsupportedFortranType are also + # copied. They will still point to the same Symbols as the + # original. new_datatype = self.datatype.copy() else: new_datatype = self.datatype diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index 654c42065a..188678ef9f 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -303,7 +303,8 @@ def replace_symbols_using(self, table_or_symbol): have matching names. If there is no match for a given Symbol then it is left unchanged. - This base implementation simply propagates the call to any child Nodes. + A partial datatype that is a DataTypeSymbol is replaced directly. It + must not be traversed because the datatype definition may be recursive. :param table_or_symbol: the symbol table from which to get replacement symbols or a single, replacement Symbol. @@ -311,8 +312,23 @@ def replace_symbols_using(self, table_or_symbol): :py:class:`psyclone.psyir.symbols.Symbol` ''' - if self.partial_datatype: - self.partial_datatype.replace_symbols_using(table_or_symbol) + partial_datatype = self.partial_datatype + if not partial_datatype: + return + + if not isinstance(partial_datatype, DataTypeSymbol): + partial_datatype.replace_symbols_using(table_or_symbol) + return + + if isinstance(table_or_symbol, Symbol): + if table_or_symbol.name.lower() != partial_datatype.name.lower(): + return + replacement = table_or_symbol + else: + replacement = table_or_symbol.lookup( + partial_datatype.name, otherwise=partial_datatype) + + self._partial_datatype = replacement @property def intrinsic(self): @@ -1467,16 +1483,20 @@ def replace_symbols_using(self, table_or_symbol): component.datatype.name, otherwise=component.datatype) else: - component.datatype.replace_symbols_using(table_or_symbol) - new_type = component.datatype + # Make a copy before updating any Symbol references so that + # replacing them does not modify the original StructureType. + new_type = component.datatype.copy() + new_type.replace_symbols_using(table_or_symbol) - if component.initial_value: - component.initial_value.replace_symbols_using(table_or_symbol) + initial_value = component.initial_value + if initial_value: + initial_value = initial_value.copy() + initial_value.replace_symbols_using(table_or_symbol) # Construct the new ComponentType key_name = component.name.lower() self.add(key_name, new_type, component.visibility, - component.initial_value, + initial_value, preceding_comment=component.preceding_comment, inline_comment=component.inline_comment) diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index b4fe8af1a7..ec855284e8 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -1392,3 +1392,64 @@ def test_structuretype___copy__(): # The components should be the same objects assert copied.components["nancy"] == stype.components["nancy"] assert copied.components["peggy"] == stype.components["peggy"] + + +def test_copy_with_recursions(fortran_reader): + '''Test the copy operation when the datatypes contain recursions between + them.''' + code = ''' + MODULE recursions + IMPLICIT NONE + + TYPE(recursive_type), POINTER :: use_before_decl + TYPE recursive_type + TYPE(recursive_type) :: supported + TYPE(recursive_type), POINTER :: unsupported + END TYPE recursive_type + TYPE(recursive_type), POINTER :: use_after_decl + + CONTAINS + SUBROUTINE mysub(arg) + TYPE(recursive_type), POINTER, INTENT(inout) :: arg + END SUBROUTINE mysub + + END MODULE recursions + ''' + psyir = fortran_reader.psyir_from_source(code) + copiedtree = psyir.copy() + + original_type = psyir.children[0].symbol_table.lookup("recursive_type") + copied_type = copiedtree.children[0].symbol_table.lookup("recursive_type") + assert copied_type is not original_type + + # Both components of the copied type must refer to the copied type symbol, + # and the equivalent components in the original must remain unchanged. + original_supported = original_type.datatype.lookup("supported").datatype + copied_supported = copied_type.datatype.lookup("supported").datatype + assert original_supported is original_type + assert copied_supported is copied_type + + original_unsupported = original_type.datatype.lookup( + "unsupported").datatype.partial_datatype + copied_unsupported = copied_type.datatype.lookup( + "unsupported").datatype.partial_datatype + assert original_unsupported is original_type + assert copied_unsupported is copied_type + + # Symbols declared both before and after the type definition must refer to + # the type symbol belonging to their respective trees. + for name in ["use_before_decl", "use_after_decl"]: + original_symbol = psyir.children[0].symbol_table.lookup(name) + copied_symbol = copiedtree.children[0].symbol_table.lookup(name) + assert copied_symbol is not original_symbol + assert copied_symbol.datatype is not original_symbol.datatype + assert original_symbol.datatype.partial_datatype is original_type + assert copied_symbol.datatype.partial_datatype is copied_type + + # The routine arguments must similarly refer to the type symbol belonging + # to their respective trees. + original_arg = psyir.walk(Routine)[0].symbol_table.lookup("arg") + copied_arg = copiedtree.walk(Routine)[0].symbol_table.lookup("arg") + assert copied_arg is not original_arg + assert original_arg.datatype.partial_datatype is original_type + assert copied_arg.datatype.partial_datatype is copied_type From 751284c07f95aacb7f1725f632d3f8913795db0a Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 3 Aug 2026 21:34:33 +0100 Subject: [PATCH 02/10] #3460 Remove invalid Fortran from test --- .../transformations/inline_trans_test.py | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 6f9366357e..8fdec21240 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -2812,6 +2812,7 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): when inlined. ''' + # TODO #3534: Add an example of a len expression in an argument decalration code = ( "module test_mod\n" "contains\n" @@ -2823,10 +2824,6 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): " integer, intent(in) :: ilen\n" " real, dimension(ilen, ilen), intent(inout) :: x\n" " real, dimension(ilen, ilen) :: work\n" - " type nasty\n" - " integer, dimension(ilen+1) :: flag\n" - " end type nasty\n" - " type(nasty) :: oh_deary_me\n" " work = 2.0\n" " x(:,:) = x(:,:) + work(:,:)\n" "end subroutine sub\n" @@ -2836,15 +2833,20 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): call = psyir.walk(Call)[0] inline_trans = InlineTrans() inline_trans.apply(call) + + # ilen should not be in the caller main = psyir.children[0].find_routine_psyir("main") assert "ilen" not in main.symbol_table + main_output = fortran_writer(main) + assert "real, dimension(10,10) :: work" in main_output + + # The the original should be unmodified + original = psyir.children[0].find_routine_psyir("sub") + original_output = fortran_writer(original) + assert "real, dimension(ilen,ilen) :: work" in original_output + + # The resulting code must be valid Fortran output = fortran_writer(psyir) - assert '''\ - type :: nasty - integer, dimension(10 + 1) :: flag - end type nasty''' in output - assert "real, dimension(10,10) :: work" in output - assert "type(nasty) :: oh_deary_me" in output assert Compile(tmp_path).string_compiles(output) From 280af6856b1d7a90a768cc0ffcececc9b7484ff3 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Mon, 3 Aug 2026 21:59:17 +0100 Subject: [PATCH 03/10] #3460 Fix flake8 --- src/psyclone/tests/psyir/transformations/inline_trans_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 8fdec21240..4402aa5304 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -2844,7 +2844,7 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): original = psyir.children[0].find_routine_psyir("sub") original_output = fortran_writer(original) assert "real, dimension(ilen,ilen) :: work" in original_output - + # The resulting code must be valid Fortran output = fortran_writer(psyir) assert Compile(tmp_path).string_compiles(output) From 835fe98c5dd9a20b4461c24ba8bdb56ebc0adbf2 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Tue, 4 Aug 2026 09:01:25 +0100 Subject: [PATCH 04/10] #3460 Remove unused code --- .../psyir/transformations/inline_trans.py | 30 ++----------------- 1 file changed, 3 insertions(+), 27 deletions(-) diff --git a/src/psyclone/psyir/transformations/inline_trans.py b/src/psyclone/psyir/transformations/inline_trans.py index 79bf05ce6f..a34708c54b 100644 --- a/src/psyclone/psyir/transformations/inline_trans.py +++ b/src/psyclone/psyir/transformations/inline_trans.py @@ -238,6 +238,9 @@ def apply(self, f"have been caught by the validate() method. Original error " f"was {err}") from err + # TODO #3536: A shallow copy will create common subtrees/references + # between the inlined and the original. + # Replace any references to formal arguments with copies of the # actual arguments. formal_args = routine_table.argument_list @@ -275,33 +278,6 @@ def apply(self, new_shape.append(ArrayType.ArrayBounds(lower, upper)) sym.datatype = ArrayType(sym.datatype.elemental_type, new_shape) - for sym in table.datatypesymbols: - if not isinstance(sym.datatype, StructureType): - continue - for name, ctype in sym.datatype.components.items(): - if isinstance(ctype.datatype, ArrayType): - new_shape = [] - for dim in ctype.datatype.shape: - lower = self._replace_formal_args_in_expr( - dim.lower, node, formal_args, - routine_node=routine, - use_first_callee_and_no_arg_check=( - use_first_callee_and_no_arg_check), - ) - upper = self._replace_formal_args_in_expr( - dim.upper, node, formal_args, - routine_node=routine, - use_first_callee_and_no_arg_check=( - use_first_callee_and_no_arg_check), - ) - new_shape.append(ArrayType.ArrayBounds(lower, upper)) - sym.datatype.components[name] = ( - StructureType.ComponentType( - name=name, - datatype=ArrayType(ctype.datatype.elemental_type, - new_shape), - visibility=ctype.visibility, - initial_value=ctype.initial_value)) # Copy the nodes from the Routine into the call site. # TODO #924 - while doing this we should ensure that any References From cd8ec50d9a6d7d50ddd0f55ccc4f2cfe9b6f2b09 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Tue, 4 Aug 2026 09:29:29 +0100 Subject: [PATCH 05/10] #3460 Fix flake8 --- src/psyclone/psyir/transformations/inline_trans.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/psyclone/psyir/transformations/inline_trans.py b/src/psyclone/psyir/transformations/inline_trans.py index a34708c54b..c204794811 100644 --- a/src/psyclone/psyir/transformations/inline_trans.py +++ b/src/psyclone/psyir/transformations/inline_trans.py @@ -53,7 +53,6 @@ ArrayType, ScalarType, DataSymbol, - StructureType, SymbolError, UnresolvedType, UnsupportedType, @@ -278,7 +277,6 @@ def apply(self, new_shape.append(ArrayType.ArrayBounds(lower, upper)) sym.datatype = ArrayType(sym.datatype.elemental_type, new_shape) - # Copy the nodes from the Routine into the call site. # TODO #924 - while doing this we should ensure that any References # to common/shared Symbols in the inlined code are updated to point From 6706cf28b09023b087e42da3812b03f425224a26 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Tue, 4 Aug 2026 10:10:58 +0100 Subject: [PATCH 06/10] #3460 Improve InlineTrans test coverage --- .../tests/psyir/transformations/inline_trans_test.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 4402aa5304..ecae64a014 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -2815,15 +2815,16 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): # TODO #3534: Add an example of a len expression in an argument decalration code = ( "module test_mod\n" + " integer, parameter :: N = 10\n" "contains\n" "subroutine main()\n" - " real, dimension(10, 10) :: var = 0.0\n" + " real, dimension(N+10, 10) :: var = 0.0\n" " call sub(var, 10)\n" "end subroutine main\n" "subroutine sub(x, ilen)\n" " integer, intent(in) :: ilen\n" - " real, dimension(ilen, ilen), intent(inout) :: x\n" - " real, dimension(ilen, ilen) :: work\n" + " real, dimension(N+ilen, ilen), intent(inout) :: x\n" + " real, dimension(N+ilen, ilen) :: work\n" " work = 2.0\n" " x(:,:) = x(:,:) + work(:,:)\n" "end subroutine sub\n" @@ -2838,12 +2839,12 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): main = psyir.children[0].find_routine_psyir("main") assert "ilen" not in main.symbol_table main_output = fortran_writer(main) - assert "real, dimension(10,10) :: work" in main_output + assert "real, dimension(n + 10,10) :: work" in main_output # The the original should be unmodified original = psyir.children[0].find_routine_psyir("sub") original_output = fortran_writer(original) - assert "real, dimension(ilen,ilen) :: work" in original_output + assert "real, dimension(n + ilen,ilen) :: work" in original_output # The resulting code must be valid Fortran output = fortran_writer(psyir) From 45e1c441d212d2c690d86e58b3270b6c87af6ee9 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Tue, 4 Aug 2026 15:37:45 +0100 Subject: [PATCH 07/10] #3460 Add xfail check --- .../psyir/transformations/inline_trans_test.py | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index ecae64a014..3c2628ac76 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -46,9 +46,10 @@ from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes import ( Assignment, Call, IntrinsicCall, Loop, Node, Reference, - Routine, Statement) + Routine, Statement, Literal) from psyclone.psyir.symbols import ( - AutomaticInterface, DataSymbol, ImportInterface, UnresolvedType) + AutomaticInterface, DataSymbol, ImportInterface, UnresolvedType, + ScalarType) from psyclone.psyir.transformations import ( InlineTrans, TransformationError) from psyclone.tests.utilities import Compile, get_invoke @@ -2825,6 +2826,7 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): " integer, intent(in) :: ilen\n" " real, dimension(N+ilen, ilen), intent(inout) :: x\n" " real, dimension(N+ilen, ilen) :: work\n" + " character(len=3) :: string_work\n" " work = 2.0\n" " x(:,:) = x(:,:) + work(:,:)\n" "end subroutine sub\n" @@ -2850,6 +2852,18 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): output = fortran_writer(psyir) assert Compile(tmp_path).string_compiles(output) + # After inlining whe should be able to modify them independently ( + # whithout ) + str_work = main.symbol_table.lookup("string_work") + str_work.datatype.length = Literal("1", ScalarType.integer_type()) + main_output = fortran_writer(main) + original_output = fortran_writer(original) + assert "character(len=1) :: string_work" in main_output + if "character(len=1) :: string_work" in original_output: + # This should still be (len=3) + pytest.xfail("#3536: After inlining symbols should be independent" + "copies") + def test_apply_array_access_check_unresolved_override_option( fortran_reader): From 7e76bb034387df10c18f74d3d7628a15d9fabd79 Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Wed, 5 Aug 2026 09:16:19 +0100 Subject: [PATCH 08/10] #3460 Remove leftover --- src/psyclone/tests/psyir/transformations/inline_trans_test.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 3c2628ac76..2baa227ef8 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -2852,8 +2852,7 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): output = fortran_writer(psyir) assert Compile(tmp_path).string_compiles(output) - # After inlining whe should be able to modify them independently ( - # whithout ) + # After inlining whe should be able to modify them independently str_work = main.symbol_table.lookup("string_work") str_work.datatype.length = Literal("1", ScalarType.integer_type()) main_output = fortran_writer(main) From 61a56b9eef0a4991f8d7609b252c15e5ab72300c Mon Sep 17 00:00:00 2001 From: Sergi Siso Date: Wed, 5 Aug 2026 14:42:28 +0100 Subject: [PATCH 09/10] #3460 Refactor xfail check --- .../tests/psyir/transformations/inline_trans_test.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index 2baa227ef8..befc9ffb05 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -2858,10 +2858,11 @@ def test_apply_symbol_dependencies(fortran_reader, fortran_writer, tmp_path): main_output = fortran_writer(main) original_output = fortran_writer(original) assert "character(len=1) :: string_work" in main_output - if "character(len=1) :: string_work" in original_output: - # This should still be (len=3) - pytest.xfail("#3536: After inlining symbols should be independent" - "copies") + + # The original should be (len=3) + assert "character(len=1) :: string_work" in original_output + pytest.xfail("#3536: After inlining symbols should be independent" + "copies") def test_apply_array_access_check_unresolved_override_option( From cd36fa9c5065a92e14f7c22038790b4a50443db2 Mon Sep 17 00:00:00 2001 From: LonelyCat124 <3043914+LonelyCat124@users.noreply.github.com.> Date: Wed, 5 Aug 2026 14:49:05 +0100 Subject: [PATCH 10/10] #3460 update changelog --- changelog | 2 ++ 1 file changed, 2 insertions(+) diff --git a/changelog b/changelog index eefb52933b..9f5a9c54ff 100644 --- a/changelog +++ b/changelog @@ -1,3 +1,5 @@ + 21) PR #3521 for #3460. Fixes issues with recursive datatype copies. + 20) PR #3518 for #3516. Fix issue with disappearing Interface declarations. 19) PR #3515 for #3513. Add a compilation test job with ifx and OpenMP flags.