From a5275439acc6a78ba0478734875d623a4dbe6429 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Tue, 25 Jun 2024 16:02:01 +0100 Subject: [PATCH 01/23] Add `type, extends` and `procedure` support in `StructureType`. --- src/psyclone/domain/gocean/kernel/psyir.py | 9 +- .../lfric/kernel/lfric_kernel_metadata.py | 9 +- src/psyclone/psyir/backend/fortran.py | 57 +++++++- src/psyclone/psyir/frontend/fparser2.py | 126 +++++++++++++++-- src/psyclone/psyir/symbols/datatypes.py | 129 +++++++++++++++++- .../gocean/kernel/gocean_kern_psyir_test.py | 19 ++- .../kernel/lfric_kernel_metadata_test.py | 55 +++++--- .../psyad/domain/lfric/test_lfric_adjoint.py | 12 +- .../lfric/test_lfric_adjoint_harness.py | 3 +- .../frontend/fparser2_derived_type_test.py | 31 +++-- .../tests/psyir/frontend/fparser2_test.py | 66 +++++++-- .../tests/psyir/symbols/datatype_test.py | 24 ++-- 12 files changed, 448 insertions(+), 92 deletions(-) diff --git a/src/psyclone/domain/gocean/kernel/psyir.py b/src/psyclone/domain/gocean/kernel/psyir.py index 03c9b0014d..be97c68118 100644 --- a/src/psyclone/domain/gocean/kernel/psyir.py +++ b/src/psyclone/domain/gocean/kernel/psyir.py @@ -50,8 +50,10 @@ from psyclone.errors import InternalError from psyclone.parse.utils import ParseError from psyclone.psyir.frontend.fortran import FortranReader +from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes import Container -from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType +from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType,\ + StructureType class GOceanContainer(Container): @@ -222,6 +224,11 @@ def create_from_psyir(symbol): datatype = symbol.datatype + if isinstance(datatype, StructureType): + type_declaration = FortranWriter().gen_typedecl(symbol).replace(", public", "").replace(", private", "") + return GOceanKernelMetadata.create_from_fortran_string( + type_declaration) + if not isinstance(datatype, UnsupportedFortranType): raise InternalError( f"Expected kernel metadata to be stored in the PSyIR as " diff --git a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py index bb6e2e74e5..75ca1e7c65 100644 --- a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py +++ b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py @@ -75,7 +75,9 @@ from psyclone.errors import InternalError from psyclone.parse.utils import ParseError from psyclone.psyir.frontend.fortran import FortranReader -from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType +from psyclone.psyir.backend.fortran import FortranWriter +from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType,\ + StructureType # pylint: disable=too-many-lines # pylint: disable=too-many-instance-attributes @@ -698,6 +700,11 @@ def create_from_psyir(symbol): datatype = symbol.datatype + if isinstance(datatype, StructureType): + type_declaration = FortranWriter().gen_typedecl(symbol).replace(", public", "").replace(", private", "") + return LFRicKernelMetadata.create_from_fortran_string( + type_declaration) + if not isinstance(datatype, UnsupportedFortranType): raise InternalError( f"Expected kernel metadata to be stored in the PSyIR as " diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index 95cffdba52..5b9bf8d3ff 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -499,6 +499,34 @@ def gen_use(self, symbol, symbol_table): ", ".join(sorted(only_list)) + "\n") return f"{self._nindent}use{intrinsic_str}{symbol.name}\n" + + def gen_proceduredecl(self, symbol, include_visibility=True): + if not isinstance(symbol.datatype, UnsupportedFortranType): + raise VisitorError( + f"gen_proceduredecl() expects a symbol with an " + f"UnsupportedFortranType datatype but got '{symbol.datatype}'") + return f"{self._nindent}{symbol.datatype.declaration}\n" + result = "procedure" + + if include_visibility: + if symbol.visibility == Symbol.Visibility.PRIVATE: + result += ", private" + elif symbol.visibility == Symbol.Visibility.PUBLIC: + result += ", public" + else: + raise InternalError( + f"A Symbol must be either public or private but symbol " + f"'{symbol.name}' has visibility '{symbol.visibility}'") + + # Specify name + result += f" :: {symbol.name}" + + # Specify initialisation expression + if symbol.initial_value: + result += " => " + self._visit(symbol.initial_value) + + return result + "\n" + def gen_vardecl(self, symbol, include_visibility=False): '''Create and return the Fortran variable declaration for this Symbol @@ -707,9 +735,24 @@ def gen_typedecl(self, symbol, include_visibility=True): raise VisitorError( f"Fortran backend cannot generate code for symbol " f"'{symbol.name}' of type '{type(symbol.datatype).__name__}'") + + if isinstance(symbol.datatype, UnresolvedType): + raise VisitorError( + f"Local Symbol '{symbol.name}' is of UnresolvedType and " + f"therefore no declaration can be created for it. Should it " + f"have an ImportInterface?") + + if not isinstance(symbol.datatype, StructureType): + raise VisitorError( + f"gen_typedecl expects a DataTypeSymbol with a StructureType " + f"as its datatype but got: '{type(symbol.datatype).__name__}'") result = f"{self._nindent}type" + if symbol.datatype.extends: + # This is a component of a derived type + result += f", extends({symbol.datatype.extends.name})" + if include_visibility: if symbol.visibility == Symbol.Visibility.PRIVATE: result += ", private" @@ -722,12 +765,6 @@ def gen_typedecl(self, symbol, include_visibility=True): f"type '{type(symbol.visibility).__name__}'") result += f" :: {symbol.name}\n" - if isinstance(symbol.datatype, UnresolvedType): - raise VisitorError( - f"Local Symbol '{symbol.name}' is of UnresolvedType and " - f"therefore no declaration can be created for it. Should it " - f"have an ImportInterface?") - self._depth += 1 for member in symbol.datatype.components.values(): @@ -736,6 +773,14 @@ def gen_typedecl(self, symbol, include_visibility=True): # part of a module. result += self.gen_vardecl(member, include_visibility=include_visibility) + if len(symbol.datatype.procedure_components) > 0: + result += f"{self._nindent}contains\n" + self._depth += 1 + for procedure in symbol.datatype.procedure_components.values(): + result += self.gen_proceduredecl(procedure, + include_visibility=include_visibility) + self._depth -= 1 + self._depth -= 1 result += f"{self._nindent}end type {symbol.name}\n" diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 9ff5254c4d..34c272dfd8 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -2203,21 +2203,121 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): # Populate this StructureType by processing the components of # the derived type try: - # We don't support derived-types with additional - # attributes e.g. "extends" or "abstract". Note, we do + # We support derived-types with additional attribute "extends" + # only. + # We do not support "abstract". Note, we do # support public/private attributes but these are stored # as Access_Spec, not Type_Attr_Spec. derived_type_stmt = decl.children[0] - if walk(derived_type_stmt, Fortran2003.Type_Attr_Spec): - raise NotImplementedError( - "Derived-type definition contains unsupported attributes.") - - # We don't yet support derived-type definitions with a CONTAINS - # section. - contains = walk(decl, Fortran2003.Contains_Stmt) - if contains: - raise NotImplementedError( - "Derived-type definition has a CONTAINS statement.") + type_attr_spec_list = walk(derived_type_stmt, + Fortran2003.Type_Attr_Spec) + if type_attr_spec_list: + for type_attr_spec in type_attr_spec_list: + if type_attr_spec.items[0] == "EXTENDS": + extends_name = type_attr_spec.items[1].string + if extends_name in parent.symbol_table: + extends_symbol = parent.symbol_table.lookup( + extends_name) + if type(extends_symbol) is Symbol: + extends_symbol.specialise(DataTypeSymbol) + extends_symbol.datatype = StructureType() + else: + # The visibility of the symbol representing this derived type + if extends_name in visibility_map: + extends_symbol_vis = visibility_map[extends_name] + else: + extends_symbol_vis = parent.symbol_table.default_visibility + extends_symbol = DataTypeSymbol(extends_name, + StructureType(), + extends_symbol_vis) + parent.symbol_table.add(extends_symbol) + dtype.extends = extends_symbol + else: + raise NotImplementedError("Derived-type definition " + "contains unsupported " + "attributes.") + + # We support derived-type definitions with a CONTAINS section. + contains_blocks = walk(decl, Fortran2003.Type_Bound_Procedure_Part) + if contains_blocks: + if len(contains_blocks) > 1: + raise NotImplementedError( + "Derived-type definition contains multiple CONTAINS " + "statements.") + contains = contains_blocks[0] + if walk(contains, Fortran2003.Contains_Stmt) is None: + raise NotImplementedError( + "Derived-type definition contains a procedure " + "without a CONTAINS statement.") + procedures = walk(contains, Fortran2003.Specific_Binding) + if procedures is None: + # The CONTAINS statement is empty + raise NotImplementedError( + "Derived-type definition contains an empty " + "CONTAINS statement.") + for procedure in procedures: + procedure_name = procedure.items[3].string + if procedure_name in parent.symbol_table: + procedure_symbol = parent.symbol_table.lookup(procedure_name) + partial_datatype = procedure_symbol.datatype + else: + partial_datatype = None + procedure_datatype = UnsupportedFortranType(procedure.string, + partial_datatype) + + procedure_vis = dtype_symbol_vis + if procedure.items[1] is not None: + access_spec = walk(procedure.items[1], Fortran2003.Access_Spec) + if access_spec: + procedure_vis = _process_access_spec(access_spec[0]) + + if procedure.items[4] is not None: + initial_value_name = procedure.items[4].string + if initial_value_name in parent.symbol_table: + initial_value_symbol = parent.symbol_table.lookup( + initial_value_name) + assert isinstance(initial_value_symbol, RoutineSymbol) + else: + initial_value_symbol = RoutineSymbol(initial_value_name, UnresolvedType()) + initial_value = Reference(initial_value_symbol) + #initial_value = None + else: + initial_value = None + + dtype.add_procedure_component(procedure_name, procedure_datatype, procedure_vis, initial_value) + # if procedure.items[0] is not None: + # raise NotImplementedError( + # "Derived-type definition contains an " + # "interface name in a specific binding.") + # if procedure.items[4] is not None: + # initial_value_name = procedure.items[4].string + # if initial_value_name in parent.symbol_table: + # initial_value_symbol = parent.symbol_table.lookup( + # initial_value_name) + # assert isinstance(initial_value_symbol, RoutineSymbol) + # else: + # initial_value_symbol = RoutineSymbol(initial_value_name) + # initial_value = Reference(initial_value_symbol) + # else: + # initial_value = None + # #import pdb; pdb.set_trace() + # procedure_name = procedure.items[3].string + # procedure_vis = dtype_symbol_vis + # if procedure.items[1] is not None: + # access_spec = walk(procedure.items[1], Fortran2003.Access_Spec) + # if access_spec: + # procedure_vis = _process_access_spec(access_spec[0]) + + # if procedure_name in parent.symbol_table: + # procedure_routine_symbol = parent.symbol_table.lookup(procedure_name) + # procedure_datatype = procedure_routine_symbol.datatype + # else: + # procedure_datatype = UnresolvedType() + # dtype.add_procedure_component(procedure_name, procedure_datatype, procedure_vis, initial_value) + #import pdb; pdb.set_trace() + + # raise NotImplementedError( + # "Derived-type definition has a CONTAINS statement.") # Re-use the existing code for processing symbols local_table = SymbolTable( @@ -2226,7 +2326,7 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): self._process_decln(parent, local_table, child) # Convert from Symbols to type information for symbol in local_table.symbols: - dtype.add(symbol.name, symbol.datatype, symbol.visibility, + dtype.add_component(symbol.name, symbol.datatype, symbol.visibility, symbol.initial_value) # Update its type with the definition we've found diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index c9cc6c1aeb..da61977257 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -729,12 +729,14 @@ class StructureType(DataType): def __init__(self): self._components = OrderedDict() + self._procedure_components = OrderedDict() + self._extends = None def __str__(self): return "StructureType<>" @staticmethod - def create(components): + def create(components, procedure_components=None, extends=None): ''' Creates a StructureType from the supplied list of properties. @@ -747,6 +749,17 @@ def create(components): :py:class:`psyclone.psyir.symbols.Symbol.Visibility`, Optional[:py:class:`psyclone.psyir.symbols.DataNode`] ]] + :param procedure_components: the name, type, visibility (whether + public or private) and initial value (if any) of each procedure + component. + :type procedure_components: Optional[List[tuple[ + str, + :py:class:`psyclone.psyir.symbols.DataType, + :py:class:`psyclone.psyir.symbols.Symbol.Visibility, + Optional[:py:class:`psyclone.psyir.symbols.DataNode`] + ]]] + :param extends: the type that this new type extends. + :type extends: :py:class:`psyclone.psyir.symbols.DataTypeSymbol` :returns: the new type object. :rtype: :py:class:`psyclone.psyir.symbols.StructureType` @@ -759,7 +772,18 @@ def create(components): f"Each component must be specified using a 4-tuple of " f"(name, type, visibility, initial_value) but found a " f"tuple with {len(component)} members: {component}") - stype.add(*component) + stype.add_component(*component) + if procedure_components: + for procedure_component in procedure_components: + if len(procedure_components) != 4: + raise TypeError( + f"Each procedure component must be specified using a " + f"4-tuple of (name, type, visibility, initial_value) " + f"but found a tuple with {len(procedure_component)} " + f"members: {procedure_component}") + stype.add_procedure_component(*procedure_component) + if extends: + stype.extends = extends return stype @property @@ -770,7 +794,39 @@ def components(self): ''' return self._components - def add(self, name, datatype, visibility, initial_value): + @property + def procedure_components(self): + ''' + :returns: Ordered dictionary of the procedure components of this type. + :rtype: :py:class:`collections.OrderedDict` + ''' + return self._procedure_components + + @property + def extends(self): + ''' + :returns: the type that this new type extends. + :rtype: :py:class:`psyclone.psyir.symbols.DataTypeSymbol` + + ''' + return self._extends + + @extends.setter + def extends(self, value): + ''' + Set the type that this new type extends. + + :param value: the type that this new type extends. + :type value: :py:class:`psyclone.psyir.symbols.DataTypeSymbol` + + ''' + if not isinstance(value, DataTypeSymbol): + raise TypeError( + f"The type that a StructureType extends must be a " + f"DataTypeSymbol but got '{type(value).__name__}'.") + self._extends = value + + def add_component(self, name, datatype, visibility, initial_value): ''' Create a component with the supplied attributes and add it to this StructureType. @@ -822,7 +878,7 @@ def add(self, name, datatype, visibility, initial_value): self._components[name] = self.ComponentType( name, datatype, visibility, initial_value) - def lookup(self, name): + def lookup_component(self, name): ''' :returns: the ComponentType tuple describing the named member of this \ StructureType. @@ -830,6 +886,65 @@ def lookup(self, name): ''' return self._components[name] + def add_procedure_component(self, name, datatype, visibility, + initial_value): + ''' + Create a procedure component with the supplied attributes and add it to + this StructureType. + + :param str name: the name of the new procedure component. + :param datatype: the type of the new procedure component. + :type datatype: :py:class:`psyclone.psyir.symbols.DataType` + :param visibility: whether this procedure component is public or + private. + :type visibility: :py:class:`psyclone.psyir.symbols.Symbol.Visibility` + :param initial_value: the initial value of the new procedure component. + :type initial_value: + Optional[:py:class:`psyclone.psyir.nodes.DataNode`] + + :raises TypeError: if any of the supplied values are of the wrong type. + + ''' + # This import must be placed here to avoid circular + # dependencies. + # pylint: disable=import-outside-toplevel + from psyclone.psyir.nodes import DataNode + if not isinstance(name, str): + raise TypeError( + f"The name of a procedure component of a StructureType must " + f"be a 'str' but got '{type(name).__name__}'") + if not isinstance(datatype, DataType): + raise TypeError( + f"The type of a procedure component of a StructureType must " + f"be a 'DataType' but got '{type(datatype).__name__}'") + if isinstance(datatype, StructureType): + # A procedure component cannot be of type StructureType + raise TypeError( + f"The type of a procedure component of a StructureType cannot " + f"be a 'StructureType' but got '{type(datatype).__name__}'") + if not isinstance(visibility, Symbol.Visibility): + raise TypeError( + f"The visibility of a component of a StructureType must be " + f"an instance of 'Symbol.Visibility' but got " + f"'{type(visibility).__name__}'") + if (initial_value is not None and + not isinstance(initial_value, DataNode)): + raise TypeError( + f"The initial value of a component of a StructureType must " + f"be None or an instance of 'DataNode', but got " + f"'{type(initial_value).__name__}'.") + + self._procedure_components[name] = self.ComponentType( + name, datatype, visibility, initial_value) + + def lookup_procedure_component(self, name): + ''' + :returns: the ComponentType tuple describing the named procedure + member of this StructureType. + :rtype: :py:class:`psyclone.psyir.symbols.StructureType.ComponentType` + ''' + return self._procedure_components[name] + def __eq__(self, other): ''' :param Any other: the object to check equality to. @@ -843,9 +958,15 @@ def __eq__(self, other): if len(self.components) != len(other.components): return False + if len(self.procedure_components) != len(other.procedure_components): + return False + if self.components != other.components: return False + if self.procedure_components != other.procedure_components: + return False + return True diff --git a/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py b/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py index 8ad1d8a97f..4c43dc617e 100644 --- a/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py +++ b/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py @@ -53,7 +53,8 @@ from psyclone.errors import InternalError from psyclone.parse.utils import ParseError from psyclone.psyir.nodes import Container -from psyclone.psyir.symbols import SymbolTable, REAL_TYPE +from psyclone.psyir.symbols import SymbolTable, REAL_TYPE, StructureType +from psyclone.psyir.backend.fortran import FortranWriter METADATA = ("TYPE, EXTENDS(kernel_type) :: compute_cu\n" " TYPE(go_arg), DIMENSION(4) :: meta_args = (/ &\n" @@ -398,10 +399,16 @@ def test_getproperty(fortran_reader): ''' kernel_psyir = fortran_reader.psyir_from_source(PROGRAM) - datatype = kernel_psyir.children[0].symbol_table.lookup( - "compute_cu").datatype + datatype_symbol = kernel_psyir.children[0].symbol_table.lookup( + "compute_cu") + datatype = datatype_symbol.datatype metadata = GOceanKernelMetadata() - reader = FortranStringReader(datatype.declaration) + # reader = FortranStringReader(datatype.declaration) + + assert isinstance(datatype, StructureType) + type_declaration = FortranWriter().gen_typedecl(datatype_symbol) + reader = FortranStringReader(type_declaration) + spec_part = Fortran2003.Derived_Type_Def(reader) assert metadata._get_property(spec_part, "code").string == \ "compute_cu_code" @@ -409,8 +416,8 @@ def test_getproperty(fortran_reader): "GO_ALL_PTS" with pytest.raises(ParseError) as info: metadata._get_property(spec_part, "not_found") - assert ("'not_found' was not found in TYPE, EXTENDS(kernel_type) :: " - "compute_cu" in str(info.value)) + assert ("'not_found' was not found in TYPE, EXTENDS(kernel_type), PUBLIC " + ":: compute_cu" in str(info.value)) def test_iteratesover(): diff --git a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py index c87def6ed2..6ab69ceb41 100644 --- a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py @@ -50,7 +50,8 @@ from psyclone.errors import InternalError from psyclone.parse.utils import ParseError from psyclone.psyir.symbols import DataTypeSymbol, REAL_TYPE, \ - UnsupportedFortranType + UnsupportedFortranType, StructureType +from psyclone.psyir.backend.fortran import FortranWriter # pylint: disable=too-many-statements @@ -1139,20 +1140,28 @@ def test_get_procedure_name_error(fortran_reader): ''' kernel_psyir = fortran_reader.psyir_from_source(PROGRAM.replace( "procedure, nopass :: code => testkern_code", "")) - datatype = kernel_psyir.children[0].symbol_table.lookup( - "testkern_type").datatype - metadata = LFRicKernelMetadata() - reader = FortranStringReader(datatype.declaration) - spec_part = Fortran2003.Derived_Type_Def(reader) - with pytest.raises(ParseError) as info: - metadata._get_procedure_name(spec_part) - assert "Expecting a type-bound procedure, but found" in str(info.value) + datatype_symbol = kernel_psyir.children[0].symbol_table.lookup( + "testkern_type") + assert isinstance(datatype_symbol, DataTypeSymbol) + structure_type = datatype_symbol.datatype + assert isinstance(structure_type, StructureType) + assert len(structure_type.procedure_components) == 0 + # metadata = LFRicKernelMetadata() + # reader = FortranStringReader(datatype.declaration) + # spec_part = Fortran2003.Derived_Type_Def(reader) + # with pytest.raises(ParseError) as info: + # metadata._get_procedure_name(spec_part) + # assert "Expecting a type-bound procedure, but found" in str(info.value) kernel_psyir = fortran_reader.psyir_from_source(PROGRAM) - datatype = kernel_psyir.children[0].symbol_table.lookup( - "testkern_type").datatype + datatype_symbol = kernel_psyir.children[0].symbol_table.lookup( + "testkern_type") + datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() - reader = FortranStringReader(datatype.declaration) + #reader = FortranStringReader(datatype.declaration) + assert isinstance(datatype, StructureType) + type_declaration = FortranWriter().gen_typedecl(datatype_symbol) + reader = FortranStringReader(type_declaration) spec_part = Fortran2003.Derived_Type_Def(reader) binding = spec_part.children[2] binding.children[1] = binding.children[0] @@ -1163,10 +1172,14 @@ def test_get_procedure_name_error(fortran_reader): kernel_psyir = fortran_reader.psyir_from_source(PROGRAM.replace( "code", "hode")) - datatype = kernel_psyir.children[0].symbol_table.lookup( - "testkern_type").datatype + datatype_symbol = kernel_psyir.children[0].symbol_table.lookup( + "testkern_type") + datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() - reader = FortranStringReader(datatype.declaration) + #reader = FortranStringReader(datatype.declaration) + assert isinstance(datatype, StructureType) + type_declaration = FortranWriter().gen_typedecl(datatype_symbol) + reader = FortranStringReader(type_declaration) spec_part = Fortran2003.Derived_Type_Def(reader) with pytest.raises(ParseError) as info: metadata._get_procedure_name(spec_part) @@ -1182,10 +1195,16 @@ def test_get_procedure_name(fortran_reader): ''' kernel_psyir = fortran_reader.psyir_from_source(PROGRAM) - datatype = kernel_psyir.children[0].symbol_table.lookup( - "testkern_type").datatype + datatype_symbol = kernel_psyir.children[0].symbol_table.lookup( + "testkern_type") + datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() - reader = FortranStringReader(datatype.declaration) + # reader = FortranStringReader(datatype.declaration) + + assert isinstance(datatype, StructureType) + type_declaration = FortranWriter().gen_typedecl(datatype_symbol) + reader = FortranStringReader(type_declaration) + spec_part = Fortran2003.Derived_Type_Def(reader) assert metadata._get_procedure_name(spec_part) == \ "testkern_code" diff --git a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py index 0a98864cce..3c6e6a531b 100644 --- a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py +++ b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py @@ -51,7 +51,7 @@ from psyclone.psyad.domain.lfric.lfric_adjoint import ( _update_access_metadata, _check_or_add_access_symbol) from psyclone.psyir.symbols import ( - DataSymbol, ArgumentInterface, INTEGER_TYPE, REAL_TYPE) + DataSymbol, ArgumentInterface, INTEGER_TYPE, REAL_TYPE, StructureType) from psyclone.psyir.transformations import TransformationError @@ -256,11 +256,13 @@ def test_generate_lfric_adjoint_multi_precision( sym_table = psyir.children[0].symbol_table test_type_symbol = sym_table.lookup("test_type") datatype = test_type_symbol.datatype + assert isinstance(datatype, StructureType) + datatype.procedure_components.clear() # Remove procedure metadata - new_declaration = (datatype.declaration. - replace("PROCEDURE, NOPASS :: kern_code", ""). - replace("CONTAINS", "")) - datatype._declaration = new_declaration + # new_declaration = (datatype.declaration. + # replace("PROCEDURE, NOPASS :: kern_code", ""). + # replace("CONTAINS", "")) + # datatype._declaration = new_declaration ad_psyir = generate_lfric_adjoint(psyir, ["field_1_w0", "field_2_w0"]) result = fortran_writer(ad_psyir) # Check that the metadata type name is updated. diff --git a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py index bed36d7f69..18cc88943d 100644 --- a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py +++ b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py @@ -616,7 +616,8 @@ def test_generate_lfric_adjoint_harness(fortran_reader, fortran_writer): " inner2 = inner2 + ascalar * ascalar_input\n" " inner2 = inner2 + field_field_input_inner_prod\n" in gen) - +@pytest.mark.xfail(reason="func_type and gh_quadrature_xyoz are neither " + "declared nor imported in the TL code.") def test_generate_lfric_adj_test_quadrature(fortran_reader): '''Check that input copies of quadrature arguments are not created.''' # Change the metadata so that it requires quadrature. diff --git a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py index bd86460b91..8ea5774c42 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py @@ -256,14 +256,14 @@ def test_parse_derived_type(use_stmt, type_name): sym = symtab.lookup("my_type") assert isinstance(sym, DataTypeSymbol) assert isinstance(sym.datatype, StructureType) - flag = sym.datatype.lookup("flag") + flag = sym.datatype.lookup_component("flag") assert isinstance(flag.datatype, ScalarType) assert flag.visibility == Symbol.Visibility.PUBLIC - grid = sym.datatype.lookup("grid") + grid = sym.datatype.lookup_component("grid") assert isinstance(grid.datatype, DataTypeSymbol) assert isinstance(grid.datatype.datatype, UnresolvedType) assert grid.visibility == Symbol.Visibility.PRIVATE - posn = sym.datatype.lookup("posn") + posn = sym.datatype.lookup_component("posn") assert isinstance(posn.datatype, ArrayType) var = symtab.lookup("var") assert var.datatype is sym @@ -271,7 +271,7 @@ def test_parse_derived_type(use_stmt, type_name): @pytest.mark.usefixtures("f2008_parser") def test_derived_type_contains(): - ''' Check that we get a DataTypeSymbol of UnsupportedFortranType if a + ''' Check that we get a DataTypeSymbol of StructureType if a derived-type definition has a CONTAINS section. ''' fake_parent = KernelSchedule("dummy_schedule") symtab = fake_parent.symbol_table @@ -288,14 +288,16 @@ def test_derived_type_contains(): sym = symtab.lookup("my_type") # It should still be a DataTypeSymbol but its type is unknown. assert isinstance(sym, DataTypeSymbol) - assert isinstance(sym.datatype, UnsupportedFortranType) - assert sym.datatype.declaration == '''\ -TYPE :: my_type - INTEGER :: flag - REAL, DIMENSION(3) :: posn - CONTAINS - PROCEDURE :: init => obesdv_setup -END TYPE my_type''' + assert isinstance(sym.datatype, StructureType) + assert len(sym.datatype.components) == 2 + assert len(sym.datatype.procedure_components) == 1 +# assert sym.datatype.declaration == '''\ +# TYPE :: my_type +# INTEGER :: flag +# REAL, DIMENSION(3) :: posn +# CONTAINS +# PROCEDURE :: init => obesdv_setup +# END TYPE my_type''' @pytest.mark.usefixtures("f2008_parser") @@ -339,9 +341,10 @@ def test_derived_type_accessibility(): processor.process_declarations(fake_parent, fparser2spec.content, []) sym = symtab.lookup("my_type") assert isinstance(sym, DataTypeSymbol) - flag = sym.datatype.lookup("flag") + assert isinstance(sym.datatype, StructureType) + flag = sym.datatype.components["flag"] assert flag.visibility == Symbol.Visibility.PRIVATE - scale = sym.datatype.lookup("scale") + scale = sym.datatype.components["scale"] assert scale.visibility == Symbol.Visibility.PUBLIC diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index ec27a69875..e6b8de063b 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -3004,12 +3004,13 @@ def test_structures(fortran_reader, fortran_writer): sym_table = psyir.children[0].symbol_table symbol = sym_table.lookup("my_type") assert isinstance(symbol, DataTypeSymbol) - assert isinstance(symbol.datatype, UnsupportedFortranType) + assert isinstance(symbol.datatype, StructureType) + assert isinstance(symbol.datatype.extends, DataTypeSymbol) result = fortran_writer(psyir) assert ( " type, extends(kernel_type), public :: my_type\n" - " INTEGER :: i = 1\n" - "END TYPE my_type\n" in result) + " integer, public :: i = 1\n" + " end type my_type\n" in result) # type that contains a procedure (UnsupportedFortranType) test_code = ( @@ -3027,14 +3028,14 @@ def test_structures(fortran_reader, fortran_writer): sym_table = psyir.children[0].symbol_table symbol = sym_table.lookup("test_type") assert isinstance(symbol, DataTypeSymbol) - assert isinstance(symbol.datatype, UnsupportedFortranType) + assert isinstance(symbol.datatype, StructureType) result = fortran_writer(psyir) assert ( " type, public :: test_type\n" - " INTEGER :: i = 1\n" - " CONTAINS\n" - " PROCEDURE, NOPASS :: test_code\n" - "END TYPE test_type\n" in result) + " integer, public :: i = 1\n" + " contains\n" + " procedure, nopass :: test_code\n" + " end type test_type\n" in result) # type that creates an abstract type and contains a procedure # (UnsupportedFortranType) @@ -3062,6 +3063,49 @@ def test_structures(fortran_reader, fortran_writer): " PROCEDURE, NOPASS :: test_code\n" "END TYPE test_type\n" in result) + # type that contains a procedure + test_code = ( + "module test_mod\n" + " use kernel_mod, only : parent_type\n" + " type, extends(parent_type) :: test_type\n" + " integer :: i = 1\n" + " contains\n" + " procedure :: test_code\n" + " procedure, private :: test_code_private\n" + " procedure, public :: test_code_public\n" + " end type test_type\n" + " contains\n" + " subroutine test_code()\n" + " end subroutine\n" + " subroutine test_code_private()\n" + " end subroutine\n" + " subroutine test_code_public()\n" + " end subroutine\n" + "end module test_mod\n") + psyir = fortran_reader.psyir_from_source(test_code) + sym_table = psyir.children[0].symbol_table + symbol = sym_table.lookup("test_type") + assert isinstance(symbol, DataTypeSymbol) + assert isinstance(symbol.datatype, StructureType) + assert len(symbol.datatype.procedure_components) == 3 + result = fortran_writer(psyir) + # assert ( + # " type, extends(parent_type), public :: test_type\n" + # " integer, public :: i = 1\n" + # " contains\n" + # " procedure :: test_code\n" + # " procedure, private :: test_code_private\n" + # " procedure, public :: test_code_public\n" + # " end type test_type\n" in result) + assert ( + " type, extends(parent_type), public :: test_type\n" + " integer, public :: i = 1\n" + " contains\n" + " procedure :: test_code\n" + " procedure, private :: test_code_private\n" + " procedure, public :: test_code_public\n" + " end type test_type\n" in result) + def test_structures_constants(fortran_reader, fortran_writer): '''Test that Fparser2Reader parses Fortran types correctly when there @@ -3086,7 +3130,7 @@ def test_structures_constants(fortran_reader, fortran_writer): symbol = sym_table.lookup("my_type") assert isinstance(symbol, DataTypeSymbol) assert isinstance(symbol.datatype, StructureType) - i_symbol = symbol.datatype.lookup("i") + i_symbol = symbol.datatype.lookup_component("i") n_reference = i_symbol.initial_value.children[0] assert n_reference.symbol is n_symbol m_reference = i_symbol.initial_value.children[1] @@ -3128,7 +3172,7 @@ def test_structures_constant_scope(fortran_reader, fortran_writer): symbol = sym_table.lookup("my_type") assert isinstance(symbol, DataTypeSymbol) assert isinstance(symbol.datatype, StructureType) - i_symbol = symbol.datatype.lookup("i") + i_symbol = symbol.datatype.lookup_component("i") n_reference = i_symbol.initial_value.children[0] assert n_reference.symbol is n_symbol m_reference = i_symbol.initial_value.children[1] @@ -3178,7 +3222,7 @@ def test_structures_constant_use(fortran_reader, fortran_writer): symbol = sym_table.lookup("my_type") assert isinstance(symbol, DataTypeSymbol) assert isinstance(symbol.datatype, StructureType) - i_symbol = symbol.datatype.lookup("i") + i_symbol = symbol.datatype.lookup_component("i") n_reference = i_symbol.initial_value.children[0] assert n_reference.symbol is n_symbol m_reference = i_symbol.initial_value.children[1] diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index 4029aade4e..22390b76d5 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -692,37 +692,37 @@ def test_structure_type(): stype = StructureType() assert str(stype) == "StructureType<>" assert not stype.components - stype.add("flag", INTEGER_TYPE, Symbol.Visibility.PUBLIC, None) - flag = stype.lookup("flag") + stype.add_component("flag", INTEGER_TYPE, Symbol.Visibility.PUBLIC, None) + flag = stype.lookup_component("flag") assert not flag.initial_value assert isinstance(flag, StructureType.ComponentType) - stype.add("flag2", INTEGER_TYPE, Symbol.Visibility.PUBLIC, + stype.add_component("flag2", INTEGER_TYPE, Symbol.Visibility.PUBLIC, Literal("1", INTEGER_TYPE)) - flag2 = stype.lookup("flag2") + flag2 = stype.lookup_component("flag2") assert isinstance(flag2, StructureType.ComponentType) assert flag2.initial_value.value == "1" with pytest.raises(TypeError) as err: - stype.add(1, "hello", "hello", None) + stype.add_component(1, "hello", "hello", None) assert ("name of a component of a StructureType must be a 'str' but got " "'int'" in str(err.value)) with pytest.raises(TypeError) as err: - stype.add("hello", "hello", "hello", None) + stype.add_component("hello", "hello", "hello", None) assert ("type of a component of a StructureType must be a 'DataType' " "or 'DataTypeSymbol' but got 'str'" in str(err.value)) with pytest.raises(TypeError) as err: - stype.add("hello", INTEGER_TYPE, "hello", None) + stype.add_component("hello", INTEGER_TYPE, "hello", None) assert ("visibility of a component of a StructureType must be an instance " "of 'Symbol.Visibility' but got 'str'" in str(err.value)) with pytest.raises(TypeError) as err: - stype.add("hello", INTEGER_TYPE, Symbol.Visibility.PUBLIC, "Hello") + stype.add_component("hello", INTEGER_TYPE, Symbol.Visibility.PUBLIC, "Hello") assert ("The initial value of a component of a StructureType must be " "None or an instance of 'DataNode', but got 'str'." in str(err.value)) with pytest.raises(KeyError): - stype.lookup("missing") + stype.lookup_component("missing") # Cannot have a recursive type definition with pytest.raises(TypeError) as err: - stype.add("hello", stype, Symbol.Visibility.PUBLIC, None) + stype.add_component("hello", stype, Symbol.Visibility.PUBLIC, None) assert ("attempting to add component 'hello' - a StructureType definition " "cannot be recursive" in str(err.value)) @@ -737,13 +737,13 @@ def test_create_structuretype(): Literal("1.0", REAL_TYPE)), ("barry", tsymbol, Symbol.Visibility.PUBLIC, None)]) assert len(stype.components) == 3 - george = stype.lookup("george") + george = stype.lookup_component("george") assert isinstance(george, StructureType.ComponentType) assert george.name == "george" assert george.datatype == REAL_TYPE assert george.visibility == Symbol.Visibility.PRIVATE assert george.initial_value.value == "1.0" - barry = stype.lookup("barry") + barry = stype.lookup_component("barry") assert isinstance(barry, StructureType.ComponentType) assert barry.datatype is tsymbol assert barry.visibility == Symbol.Visibility.PUBLIC From b95abdc3ea9646a4b62f4c68ffaf2917add7d2e4 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 1 Jul 2024 18:14:42 +0200 Subject: [PATCH 02/23] Add support for `class` keyword in declarations. --- src/psyclone/psyir/backend/fortran.py | 43 +++-- src/psyclone/psyir/frontend/fparser2.py | 151 +++++++++------- .../psyir/symbols/data_type_symbol.py | 40 ++++- src/psyclone/psyir/symbols/datatypes.py | 10 +- .../frontend/fparser2_derived_type_test.py | 6 +- .../frontend/fparser2_select_type_test.py | 164 ++++++++++++++++-- .../tests/psyir/frontend/fparser2_test.py | 12 +- 7 files changed, 329 insertions(+), 97 deletions(-) diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index 5b9bf8d3ff..3a1514131d 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -127,6 +127,8 @@ def gen_datatype(datatype, name): if (isinstance(datatype, ArrayType) and isinstance(datatype.intrinsic, DataTypeSymbol)): # Symbol is an array of derived types + if datatype.intrinsic.is_class: + return f"class({datatype.intrinsic.name})" return f"type({datatype.intrinsic.name})" try: @@ -499,14 +501,34 @@ def gen_use(self, symbol, symbol_table): ", ".join(sorted(only_list)) + "\n") return f"{self._nindent}use{intrinsic_str}{symbol.name}\n" - + def gen_proceduredecl(self, symbol, include_visibility=True): - if not isinstance(symbol.datatype, UnsupportedFortranType): + '''Create and return the Fortran procedure declaration for this Symbol. + + :param symbol: the symbol instance. + :type symbol: Union[:py:class:`psyclone.psyir.symbols.DataSymbol`, + :py:class:`psyclone.psyir.symbols.datatypes. + StructureType.ComponentType`] + :param bool include_visibility: whether to include the visibility of + the symbol in the generated declaration (default True). + + :returns: the Fortran procedure declaration as a string. + :rtype: str + + :raises VisitorError: if the symbol is not a RoutineSymbol. + :raises InternalError: if the visibility is to be included but is + neither PUBLIC nor PRIVATE. + ''' + if not isinstance(symbol, (DataSymbol, StructureType.ComponentType)): raise VisitorError( - f"gen_proceduredecl() expects a symbol with an " - f"UnsupportedFortranType datatype but got '{symbol.datatype}'") - return f"{self._nindent}{symbol.datatype.declaration}\n" - result = "procedure" + f"gen_proceduredecl() expects a 'DataSymbol' or " + f"'StructureType.ComponentType' as its first " + f"argument but got '{type(symbol).__name__}'") + + if isinstance(symbol.datatype, UnsupportedFortranType): + return f"{self._nindent}{symbol.datatype.declaration}\n" + + result = f"{self._nindent}procedure" if include_visibility: if symbol.visibility == Symbol.Visibility.PRIVATE: @@ -527,7 +549,6 @@ def gen_proceduredecl(self, symbol, include_visibility=True): return result + "\n" - def gen_vardecl(self, symbol, include_visibility=False): '''Create and return the Fortran variable declaration for this Symbol or derived-type member. @@ -735,7 +756,7 @@ def gen_typedecl(self, symbol, include_visibility=True): raise VisitorError( f"Fortran backend cannot generate code for symbol " f"'{symbol.name}' of type '{type(symbol.datatype).__name__}'") - + if isinstance(symbol.datatype, UnresolvedType): raise VisitorError( f"Local Symbol '{symbol.name}' is of UnresolvedType and " @@ -778,7 +799,7 @@ def gen_typedecl(self, symbol, include_visibility=True): self._depth += 1 for procedure in symbol.datatype.procedure_components.values(): result += self.gen_proceduredecl(procedure, - include_visibility=include_visibility) + include_visibility) self._depth -= 1 self._depth -= 1 @@ -1012,7 +1033,9 @@ def gen_decls(self, symbol_table, is_module_scope=False): unresolved_symbols = [] for sym in all_symbols[:]: if isinstance(sym.interface, UnresolvedInterface): - unresolved_symbols.append(sym) + # Explicitly deal with '*' as in 'class(*) :: var' + if sym.name != "*": + unresolved_symbols.append(sym) all_symbols.remove(sym) try: internal_interface_symbol = symbol_table.lookup( diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 34c272dfd8..19314f73ea 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -1810,13 +1810,14 @@ def _process_type_spec(self, parent, type_spec): elif isinstance(type_spec, Fortran2003.Declaration_Type_Spec): # This is a variable of derived type - if type_spec.children[0].lower() != "type": - # We don't yet support declarations that use 'class' - # TODO #1504 extend the PSyIR for this variable type. + if type_spec.children[0].lower() not in ("type", "class"): raise NotImplementedError( f"Could not process {type_spec} - declarations " - f"other than 'type' are not yet supported.") - type_name = str(walk(type_spec, Fortran2003.Type_Name)[0]) + f"other than 'type' or 'class' are not yet supported.") + if isinstance(type_spec.items[1], Fortran2003.Type_Name): + type_name = str(type_spec.items[1].string).lower() + else: + type_name = type_spec.items[1].lower() # Do we already have a Symbol for this derived type? type_symbol = _find_or_create_unresolved_symbol(parent, type_name) # pylint: disable=unidiomatic-typecheck @@ -1834,6 +1835,8 @@ def _process_type_spec(self, parent, type_spec): f"Search for a DataTypeSymbol named '{type_name}' " f"(required by specification '{type_spec}') found a " f"'{type(type_symbol).__name__}' instead.") + if type_spec.children[0].lower() == "class": + type_symbol.is_class = True base_type = type_symbol else: @@ -2210,27 +2213,36 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): # as Access_Spec, not Type_Attr_Spec. derived_type_stmt = decl.children[0] type_attr_spec_list = walk(derived_type_stmt, - Fortran2003.Type_Attr_Spec) + Fortran2003.Type_Attr_Spec) if type_attr_spec_list: for type_attr_spec in type_attr_spec_list: + # Deal with 'EXTENDS(parent_type)'. if type_attr_spec.items[0] == "EXTENDS": extends_name = type_attr_spec.items[1].string + # Look up the extended type in the symbol table + # and specialise the symbol if needed. if extends_name in parent.symbol_table: extends_symbol = parent.symbol_table.lookup( extends_name) if type(extends_symbol) is Symbol: extends_symbol.specialise(DataTypeSymbol) extends_symbol.datatype = StructureType() + # If it is not in the symbol table, create a new + # DataTypeSymbol for it. else: - # The visibility of the symbol representing this derived type + # The visibility of the symbol representing this + # derived type. if extends_name in visibility_map: - extends_symbol_vis = visibility_map[extends_name] + extends_symbol_vis = visibility_map[ + extends_name] else: - extends_symbol_vis = parent.symbol_table.default_visibility + extends_symbol_vis = parent.symbol_table.\ + default_visibility extends_symbol = DataTypeSymbol(extends_name, StructureType(), extends_symbol_vis) parent.symbol_table.add(extends_symbol) + # Set it as the extended type of the new type. dtype.extends = extends_symbol else: raise NotImplementedError("Derived-type definition " @@ -2240,84 +2252,82 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): # We support derived-type definitions with a CONTAINS section. contains_blocks = walk(decl, Fortran2003.Type_Bound_Procedure_Part) if contains_blocks: + # First check there is only one CONTAINS section. if len(contains_blocks) > 1: raise NotImplementedError( "Derived-type definition contains multiple CONTAINS " "statements.") + # Get it. contains = contains_blocks[0] + # Check that it's indeed a CONTAINS statement. if walk(contains, Fortran2003.Contains_Stmt) is None: raise NotImplementedError( "Derived-type definition contains a procedure " "without a CONTAINS statement.") + # Get all procedures in the CONTAINS section. procedures = walk(contains, Fortran2003.Specific_Binding) if procedures is None: - # The CONTAINS statement is empty + # The CONTAINS statement is empty. raise NotImplementedError( "Derived-type definition contains an empty " "CONTAINS statement.") + # Process each procedure. for procedure in procedures: + supported = True + # We do not support interfaces. + if procedure.items[0] is not None: + supported = False + # We do not support 'pass', 'nopass', etc. + if procedure.items[1] is not None: + supported = False + + # Get the name, look it up in the symbol table and + # get its datatype or create it if it does not exist. procedure_name = procedure.items[3].string - if procedure_name in parent.symbol_table: - procedure_symbol = parent.symbol_table.lookup(procedure_name) - partial_datatype = procedure_symbol.datatype + if procedure_name in parent.symbol_table and supported: + procedure_symbol = parent.symbol_table.\ + lookup(procedure_name) + procedure_datatype = procedure_symbol.datatype else: - partial_datatype = None - procedure_datatype = UnsupportedFortranType(procedure.string, - partial_datatype) + procedure_datatype = UnsupportedFortranType( + procedure.string, + None) + # Get the visibility of the procedure. procedure_vis = dtype_symbol_vis if procedure.items[1] is not None: - access_spec = walk(procedure.items[1], Fortran2003.Access_Spec) + access_spec = walk(procedure.items[1], + Fortran2003.Access_Spec) if access_spec: - procedure_vis = _process_access_spec(access_spec[0]) + procedure_vis = _process_access_spec( + access_spec[0]) + # Deal with the optional initial value. if procedure.items[4] is not None: initial_value_name = procedure.items[4].string + # Look it up in the symbol table and get its datatype + # or create it if it does not exist. if initial_value_name in parent.symbol_table: initial_value_symbol = parent.symbol_table.lookup( initial_value_name) - assert isinstance(initial_value_symbol, RoutineSymbol) + if (isinstance(procedure_datatype, + UnsupportedFortranType) + and supported): + procedure_datatype = initial_value_symbol.\ + datatype else: - initial_value_symbol = RoutineSymbol(initial_value_name, UnresolvedType()) + initial_value_symbol = RoutineSymbol( + initial_value_name, + UnresolvedType()) initial_value = Reference(initial_value_symbol) - #initial_value = None else: initial_value = None - dtype.add_procedure_component(procedure_name, procedure_datatype, procedure_vis, initial_value) - # if procedure.items[0] is not None: - # raise NotImplementedError( - # "Derived-type definition contains an " - # "interface name in a specific binding.") - # if procedure.items[4] is not None: - # initial_value_name = procedure.items[4].string - # if initial_value_name in parent.symbol_table: - # initial_value_symbol = parent.symbol_table.lookup( - # initial_value_name) - # assert isinstance(initial_value_symbol, RoutineSymbol) - # else: - # initial_value_symbol = RoutineSymbol(initial_value_name) - # initial_value = Reference(initial_value_symbol) - # else: - # initial_value = None - # #import pdb; pdb.set_trace() - # procedure_name = procedure.items[3].string - # procedure_vis = dtype_symbol_vis - # if procedure.items[1] is not None: - # access_spec = walk(procedure.items[1], Fortran2003.Access_Spec) - # if access_spec: - # procedure_vis = _process_access_spec(access_spec[0]) - - # if procedure_name in parent.symbol_table: - # procedure_routine_symbol = parent.symbol_table.lookup(procedure_name) - # procedure_datatype = procedure_routine_symbol.datatype - # else: - # procedure_datatype = UnresolvedType() - # dtype.add_procedure_component(procedure_name, procedure_datatype, procedure_vis, initial_value) - #import pdb; pdb.set_trace() - - # raise NotImplementedError( - # "Derived-type definition has a CONTAINS statement.") + # Add this procedure as a component of the derived type. + dtype.add_procedure_component(procedure_name, + procedure_datatype, + procedure_vis, + initial_value) # Re-use the existing code for processing symbols local_table = SymbolTable( @@ -2326,8 +2336,8 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): self._process_decln(parent, local_table, child) # Convert from Symbols to type information for symbol in local_table.symbols: - dtype.add_component(symbol.name, symbol.datatype, symbol.visibility, - symbol.initial_value) + dtype.add_component(symbol.name, symbol.datatype, + symbol.visibility, symbol.initial_value) # Update its type with the definition we've found tsymbol.datatype = dtype @@ -3512,6 +3522,10 @@ def _add_target_attribute(var_name, table): (e.g. a routine argument or an imported symbol). ''' + #pylint: disable=import-outside-toplevel + # Import here to avoid circular dependencies. + from psyclone.psyir.backend.fortran import FortranWriter + try: symbol = table.lookup(var_name) except KeyError as err: @@ -3524,14 +3538,27 @@ def _add_target_attribute(var_name, table): f"be resolved and a DataSymbol") datatype = symbol.datatype - # Create Fortran text for the supplied datatype from the + # If this is of UnsupportedFortranType, + # create Fortran text for the supplied datatype from the # supplied UnsupportedFortranType text, then parse this into an # fparser2 tree and store the fparser2 representation of the # datatype in type_decl_stmt. - dummy_code = ( - f"subroutine dummy()\n" - f" {datatype.declaration}\n" - f"end subroutine\n") + if isinstance(datatype, UnsupportedFortranType): + dummy_code = ( + f"subroutine dummy()\n" + f" {datatype.declaration}\n" + f"end subroutine\n") + # If not, this is a supported derived type so we can use the + # backend to generate the Fortran text for the datatype. + # But we need to turn its datatype into an UnsupportedFortranType + # in order to add the 'TARGET' attribute to the declaration. + else: + dummy_code = ( + f"subroutine dummy()\n" + f" {FortranWriter().gen_vardecl(symbol)}\n" + f"end subroutine\n") + datatype = UnsupportedFortranType("", datatype) + symbol._datatype = datatype parser = ParserFactory().create(std="f2008") reader = FortranStringReader(dummy_code) fp2_ast = parser(reader) diff --git a/src/psyclone/psyir/symbols/data_type_symbol.py b/src/psyclone/psyir/symbols/data_type_symbol.py index 0f15fdc51d..853f94aec1 100644 --- a/src/psyclone/psyir/symbols/data_type_symbol.py +++ b/src/psyclone/psyir/symbols/data_type_symbol.py @@ -56,13 +56,16 @@ class DataTypeSymbol(Symbol): ''' def __init__(self, name, datatype, visibility=Symbol.DEFAULT_VISIBILITY, - interface=None): + interface=None, + is_class=False): super(DataTypeSymbol, self).__init__(name, visibility, interface) - # The following attribute has a setter method (with error checking) + # The following attributes have setter methods (with error checking) self._datatype = None self.datatype = datatype + self.is_class = is_class + def copy(self): '''Create and return a copy of this object. Any references to the original will not be affected so the copy will not be referred @@ -74,7 +77,7 @@ def copy(self): ''' return type(self)(self.name, self.datatype, visibility=self.visibility, - interface=self.interface) + interface=self.interface, is_class=self.is_class) def __str__(self): return f"{self.name}: {type(self).__name__}" @@ -108,6 +111,31 @@ def datatype(self, value): f"DataType but got: '{type(value).__name__}'") self._datatype = value + @property + def is_class(self): + ''' + :returns: whether this DataTypeSymbol is a 'class' declaration, i.e. + not a 'type' one. + :rtype: bool + ''' + return self._is_class + + @is_class.setter + def is_class(self, value): + ''' Setter for DataTypeSymbol is_class. + + :param bool value: whether this DataTypeSymbol is a 'class' \ + declaration, i.e. not a 'type' one. + + :raises TypeError: if value is not a bool. + + ''' + if not isinstance(value, bool): + raise TypeError( + f"The is_class attribute of a DataTypeSymbol must be a bool " + f"but got: '{type(value).__name__}'") + self._is_class = value + def copy_properties(self, symbol_in): '''Replace all properties in this object with the properties from symbol_in, apart from the name (which is immutable) and visibility. @@ -123,6 +151,12 @@ def copy_properties(self, symbol_in): f"found '{type(symbol_in).__name__}'.") super(DataTypeSymbol, self).copy_properties(symbol_in) self._datatype = symbol_in.datatype + self._is_class = symbol_in.is_class + + def _process_arguments(self, visibility=None, interface=None, + is_class=False): + super()._process_arguments(visibility, interface) + self.is_class = is_class # For automatic documentation generation diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index da61977257..d959a120e0 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -880,13 +880,13 @@ def add_component(self, name, datatype, visibility, initial_value): def lookup_component(self, name): ''' - :returns: the ComponentType tuple describing the named member of this \ + :returns: the ComponentType tuple describing the named member of this StructureType. :rtype: :py:class:`psyclone.psyir.symbols.StructureType.ComponentType` ''' return self._components[name] - def add_procedure_component(self, name, datatype, visibility, + def add_procedure_component(self, name, datatype, visibility, initial_value): ''' Create a procedure component with the supplied attributes and add it to @@ -895,11 +895,11 @@ def add_procedure_component(self, name, datatype, visibility, :param str name: the name of the new procedure component. :param datatype: the type of the new procedure component. :type datatype: :py:class:`psyclone.psyir.symbols.DataType` - :param visibility: whether this procedure component is public or + :param visibility: whether this procedure component is public or private. :type visibility: :py:class:`psyclone.psyir.symbols.Symbol.Visibility` :param initial_value: the initial value of the new procedure component. - :type initial_value: + :type initial_value: Optional[:py:class:`psyclone.psyir.nodes.DataNode`] :raises TypeError: if any of the supplied values are of the wrong type. @@ -939,7 +939,7 @@ def add_procedure_component(self, name, datatype, visibility, def lookup_procedure_component(self, name): ''' - :returns: the ComponentType tuple describing the named procedure + :returns: the ComponentType tuple describing the named procedure member of this StructureType. :rtype: :py:class:`psyclone.psyir.symbols.StructureType.ComponentType` ''' diff --git a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py index 8ea5774c42..758e4f083b 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py @@ -157,9 +157,9 @@ def test_name_clash_derived_type(f2008_parser, type_name): # already contain a RoutineSymbol named 'my_type' with pytest.raises(SymbolError) as err: processor.process_declarations(fake_parent, spec_part.children, []) - assert (f"Search for a DataTypeSymbol named '{type_name}' (required by " - f"specification 'TYPE({type_name})') found a 'RoutineSymbol' " - f"instead" in str(err.value)) + assert (f"Search for a DataTypeSymbol named '{type_name.lower()}' " + f"(required by specification 'TYPE({type_name})') found a " + f"'RoutineSymbol' instead" in str(err.value)) def test_name_clash_derived_type_def(f2008_parser): 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 4d494bdee4..a95b291a0d 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py @@ -72,7 +72,60 @@ def test_type(fortran_reader, fortran_writer, tmpdir): " END SELECT\n" "end subroutine\n" "end module\n") - expected1 = "CLASS(*), TARGET :: type_selector" + expected1 = "class(*), target :: type_selector" + expected2 = ( + " character(256) :: type_string\n" + " INTEGER, pointer :: ptr_INTEGER => null()\n" + " REAL, pointer :: ptr_REAL => null()\n\n" + " type_string = ''\n" + " SELECT TYPE(type_selector)\n" + " TYPE IS (INTEGER)\n" + " type_string = \"integer\"\n" + " ptr_INTEGER => type_selector\n" + " TYPE IS (REAL)\n" + " type_string = \"real\"\n" + " ptr_REAL => type_selector\n" + " END SELECT\n" + " if (type_string == 'integer') then\n" + " branch1 = 1\n" + " branch2 = 0\n" + " iinfo = ptr_INTEGER\n" + " else\n" + " if (type_string == 'real') then\n" + " branch2 = 1\n" + " rinfo = ptr_REAL\n" + " end if\n" + " end if\n") + psyir = fortran_reader.psyir_from_source(code) + result = fortran_writer(psyir).lower() + assert expected1.lower() in result + assert expected2.lower() in result + if_blocks = psyir.walk(IfBlock) + assert "was_type_is" in if_blocks[0].annotations + assert "was_type_is" in if_blocks[1].annotations + assert Compile(tmpdir).string_compiles(result) + + code = ( + "module select_mod\n" + "use my_mod, only: my_class\n" + "contains\n" + "subroutine select_type(type_selector)\n" + " class(my_class), target :: type_selector\n" + " integer :: branch1, branch2\n" + " integer :: iinfo\n" + " real :: rinfo\n" + " SELECT TYPE (type_selector)\n" + " TYPE IS (INTEGER)\n" + " branch1 = 1\n" + " branch2 = 0\n" + " iinfo = type_selector\n" + " TYPE IS (REAL)\n" + " branch2 = 1\n" + " rinfo = type_selector\n" + " END SELECT\n" + "end subroutine\n" + "end module\n") + expected1 = "class(my_class), target :: type_selector" expected2 = ( " character(256) :: type_string\n" " INTEGER, pointer :: ptr_INTEGER => null()\n" @@ -255,6 +308,89 @@ def test_class(fortran_reader, fortran_writer, tmpdir): assert "was_class_is" in if_blocks[2].annotations assert Compile(tmpdir).string_compiles(result) + code = ( + "module select_mod\n" + "use my_mod, only: my_class\n" + "contains\n" + "subroutine select_type(type)\n" + " class(my_class), pointer :: type\n" + " type type2\n" + " integer :: scalar\n" + " end type\n" + " type type3\n" + " integer :: field\n" + " end type\n" + " integer :: branch0, branch1, branch2, branch3\n" + " type(type2) :: my_type2\n" + " type(type3) :: my_type3\n" + " integer :: iinfo\n" + " SELECT TYPE (type)\n" + " CLASS IS(type2)\n" + " branch0 = 1\n" + " my_type2 = type\n" + " TYPE IS (INTEGER)\n" + " branch1 = 1\n" + " branch2 = 0\n" + " iinfo = type\n" + " CLASS IS(type3)\n" + " branch2 = 1\n" + " my_type3 = type\n" + " TYPE IS (REAL)\n" + " branch3 = 1\n" + " ! type not used here\n" + " END SELECT\n" + "end subroutine\n" + "end module\n") + expected1 = "class(my_class), pointer :: type" + expected2 = ( + " character(256) :: type_string\n" + " type(type2), pointer :: ptr_type2 => null()\n" + " INTEGER, pointer :: ptr_INTEGER => null()\n" + " type(type3), pointer :: ptr_type3 => null()\n" + " REAL, pointer :: ptr_REAL => null()\n\n" + " type_string = ''\n" + " SELECT TYPE(type)\n" + " CLASS IS (type2)\n" + " type_string = \"type2\"\n" + " ptr_type2 => type\n" + " TYPE IS (INTEGER)\n" + " type_string = \"integer\"\n" + " ptr_INTEGER => type\n" + " CLASS IS (type3)\n" + " type_string = \"type3\"\n" + " ptr_type3 => type\n" + " TYPE IS (REAL)\n" + " type_string = \"real\"\n" + " ptr_REAL => type\n" + " END SELECT\n" + " if (type_string == 'type2') then\n" + " branch0 = 1\n" + " my_type2 = ptr_type2\n" + " else\n" + " if (type_string == 'integer') then\n" + " branch1 = 1\n" + " branch2 = 0\n" + " iinfo = ptr_INTEGER\n" + " else\n" + " if (type_string == 'type3') then\n" + " branch2 = 1\n" + " my_type3 = ptr_type3\n" + " else\n" + " if (type_string == 'real') then\n" + " branch3 = 1\n" + " end if\n" + " end if\n" + " end if\n" + " end if\n").lower() + psyir = fortran_reader.psyir_from_source(code) + result = fortran_writer(psyir).lower() + assert expected1 in result + assert expected2 in result + if_blocks = psyir.walk(IfBlock) + assert "was_class_is" in if_blocks[0].annotations + assert "was_class_is" in if_blocks[2].annotations + assert Compile(tmpdir).string_compiles(result) + def test_class_with_codeblock(fortran_reader, fortran_writer, tmpdir): '''Check that the handler copes with the case where we have a CodeBlock @@ -377,7 +513,7 @@ def test_kind(fortran_reader, fortran_writer, tmpdir): "end subroutine\n" "end module\n") expected1 = ( - " CLASS(*), TARGET :: type\n" + " class(*), target :: type\n" " integer :: branch1\n" " integer :: branch2\n" " REAL(KIND = 4) :: rinfo1\n" @@ -435,7 +571,7 @@ def test_derived(fortran_reader, fortran_writer, tmpdir): "end subroutine\n" "end module\n") expected1 = ( - " CLASS(*), TARGET :: type\n" + " class(*), target :: type\n" " type :: field_type\n" " integer :: x\n" " end type field_type\n" @@ -455,9 +591,9 @@ def test_derived(fortran_reader, fortran_writer, tmpdir): " field_type_info = ptr_field_type\n" " end if\n") psyir = fortran_reader.psyir_from_source(code) - result = fortran_writer(psyir) - assert expected1 in result - assert expected2 in result + result = fortran_writer(psyir).lower() + assert expected1.lower() in result + assert expected2.lower() in result assert Compile(tmpdir).string_compiles(result) @@ -492,7 +628,7 @@ def test_datatype(fortran_reader, fortran_writer, tmpdir): "end subroutine\n" "end module\n") expected1 = ( - " CLASS(*), TARGET :: type_selector\n" + " class(*), target :: type_selector\n" " integer :: branch1\n" " integer :: branch2\n" " integer :: branch3\n" @@ -564,7 +700,7 @@ def test_character(fortran_reader, fortran_writer, tmpdir, char_type_in, f"end module\n") expected1 = ( f" subroutine select_type(type_selector)\n" - f" CLASS(*), TARGET :: 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), pointer :: ptr_CHARACTER_star => " @@ -609,7 +745,7 @@ def test_character_assumed_len(fortran_reader, fortran_writer, tmpdir, f"end module\n") expected1 = ( f" subroutine select_type(type_selector)\n" - f" CLASS(*), TARGET :: 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), pointer :: ptr_CHARACTER_star => " @@ -723,7 +859,7 @@ def test_class_target( f" public\n\n" f" contains\n" f" subroutine select_type(type_selector, character_type)\n" - f" CLASS(*), {post_attribute} :: type_selector\n" + f" class(*), {post_attribute.lower()} :: type_selector\n" f" CHARACTER(LEN = *) :: character_type\n" f" character(256) :: type_string\n" f" CHARACTER(LEN=256), pointer :: ptr_CHARACTER_star => null()\n\n" @@ -799,3 +935,11 @@ def test_add_target_attribute(fortran_reader): table.add(sym2) fp2reader._add_target_attribute("a_local", table) assert "INTEGER, TARGET :: a_local" in str(sym2.datatype) + +def test_this(fortran_reader): + psy = fortran_reader.psyir_from_source( + '''module mymod + PROCEDURE(chir2xyz_interface), PROTECTED, POINTER :: chir2xyz => null() + end module''') + from psyclone.psyir.nodes import Container + assert isinstance(psy.children[0], Container) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index e6b8de063b..7afb63886b 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -1428,9 +1428,13 @@ def test_process_not_supported_declarations(): fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) sym = fake_parent.symbol_table.lookup("carg") - assert isinstance(sym.datatype, UnsupportedFortranType) - assert (sym.datatype.declaration.lower() == - "class(my_type), intent(in) :: carg") + assert isinstance(sym, DataSymbol) + assert isinstance(sym.datatype, DataTypeSymbol) + assert sym.datatype.is_class is True + assert sym.name == "carg" + assert sym.datatype.name == "my_type" + assert isinstance(sym.interface, ArgumentInterface) + assert (sym.interface.access == ArgumentInterface.Access.READ) # Allocatable but with specified extent. This is invalid Fortran but # fparser2 doesn't spot it (see fparser/#229). @@ -3101,7 +3105,7 @@ def test_structures(fortran_reader, fortran_writer): " type, extends(parent_type), public :: test_type\n" " integer, public :: i = 1\n" " contains\n" - " procedure :: test_code\n" + " procedure, public :: test_code\n" " procedure, private :: test_code_private\n" " procedure, public :: test_code_public\n" " end type test_type\n" in result) From 349bd2b1bdcb2d37314fa4146f485fd92c633297 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Tue, 2 Jul 2024 10:02:57 +0200 Subject: [PATCH 03/23] Cleanup. --- .../tests/psyir/frontend/fparser2_select_type_test.py | 8 -------- 1 file changed, 8 deletions(-) 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 a95b291a0d..06ff91af34 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py @@ -935,11 +935,3 @@ def test_add_target_attribute(fortran_reader): table.add(sym2) fp2reader._add_target_attribute("a_local", table) assert "INTEGER, TARGET :: a_local" in str(sym2.datatype) - -def test_this(fortran_reader): - psy = fortran_reader.psyir_from_source( - '''module mymod - PROCEDURE(chir2xyz_interface), PROTECTED, POINTER :: chir2xyz => null() - end module''') - from psyclone.psyir.nodes import Container - assert isinstance(psy.children[0], Container) From f3bb06147daff1765d190116f0a4d70e91149b53 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Tue, 2 Jul 2024 12:39:17 +0200 Subject: [PATCH 04/23] #2642 Implement `extends`, `procedure` in derived types, `class` keyword. --- src/psyclone/domain/gocean/kernel/psyir.py | 10 ++++++++-- .../domain/lfric/kernel/lfric_kernel_metadata.py | 10 ++++++++-- src/psyclone/psyir/frontend/fparser2.py | 6 +++--- .../domain/lfric/kernel/lfric_kernel_metadata_test.py | 4 ++-- .../psyad/domain/lfric/test_lfric_adjoint_harness.py | 1 + src/psyclone/tests/psyir/symbols/datatype_test.py | 5 +++-- 6 files changed, 25 insertions(+), 11 deletions(-) diff --git a/src/psyclone/domain/gocean/kernel/psyir.py b/src/psyclone/domain/gocean/kernel/psyir.py index be97c68118..77c5693b05 100644 --- a/src/psyclone/domain/gocean/kernel/psyir.py +++ b/src/psyclone/domain/gocean/kernel/psyir.py @@ -52,7 +52,7 @@ from psyclone.psyir.frontend.fortran import FortranReader from psyclone.psyir.backend.fortran import FortranWriter from psyclone.psyir.nodes import Container -from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType,\ +from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType, \ StructureType @@ -224,8 +224,14 @@ def create_from_psyir(symbol): datatype = symbol.datatype + # TODO #2643: This is a temporary solution using FortranWriter + # to allow the current metadata extraction to work with StructureType, + # instead of relying on UnsupportedFortranType. + # This will be removed when the metadata is extracted from the PSyIR + # itself. if isinstance(datatype, StructureType): - type_declaration = FortranWriter().gen_typedecl(symbol).replace(", public", "").replace(", private", "") + type_declaration = FortranWriter().gen_typedecl(symbol) + type_declaration.replace(", public", "").replace(", private", "") return GOceanKernelMetadata.create_from_fortran_string( type_declaration) diff --git a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py index 75ca1e7c65..aa9bc5ec0e 100644 --- a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py +++ b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py @@ -76,7 +76,7 @@ from psyclone.parse.utils import ParseError from psyclone.psyir.frontend.fortran import FortranReader from psyclone.psyir.backend.fortran import FortranWriter -from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType,\ +from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType, \ StructureType # pylint: disable=too-many-lines @@ -700,8 +700,14 @@ def create_from_psyir(symbol): datatype = symbol.datatype + # TODO #2643: This is a temporary solution using FortranWriter + # to allow the current metadata extraction to work with StructureType, + # instead of relying on UnsupportedFortranType. + # This will be removed when the metadata is extracted from the PSyIR + # itself. if isinstance(datatype, StructureType): - type_declaration = FortranWriter().gen_typedecl(symbol).replace(", public", "").replace(", private", "") + type_declaration = FortranWriter().gen_typedecl(symbol) + type_declaration.replace(", public", "").replace(", private", "") return LFRicKernelMetadata.create_from_fortran_string( type_declaration) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 19314f73ea..dcad66da0e 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -3522,10 +3522,10 @@ def _add_target_attribute(var_name, table): (e.g. a routine argument or an imported symbol). ''' - #pylint: disable=import-outside-toplevel + # pylint: disable=import-outside-toplevel # Import here to avoid circular dependencies. from psyclone.psyir.backend.fortran import FortranWriter - + try: symbol = table.lookup(var_name) except KeyError as err: @@ -3538,7 +3538,7 @@ def _add_target_attribute(var_name, table): f"be resolved and a DataSymbol") datatype = symbol.datatype - # If this is of UnsupportedFortranType, + # If this is of UnsupportedFortranType, # create Fortran text for the supplied datatype from the # supplied UnsupportedFortranType text, then parse this into an # fparser2 tree and store the fparser2 representation of the diff --git a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py index 6ab69ceb41..2286d1521b 100644 --- a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py @@ -1158,7 +1158,7 @@ def test_get_procedure_name_error(fortran_reader): "testkern_type") datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() - #reader = FortranStringReader(datatype.declaration) + # reader = FortranStringReader(datatype.declaration) assert isinstance(datatype, StructureType) type_declaration = FortranWriter().gen_typedecl(datatype_symbol) reader = FortranStringReader(type_declaration) @@ -1176,7 +1176,7 @@ def test_get_procedure_name_error(fortran_reader): "testkern_type") datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() - #reader = FortranStringReader(datatype.declaration) + # reader = FortranStringReader(datatype.declaration) assert isinstance(datatype, StructureType) type_declaration = FortranWriter().gen_typedecl(datatype_symbol) reader = FortranStringReader(type_declaration) diff --git a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py index 18cc88943d..548790290f 100644 --- a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py +++ b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py @@ -616,6 +616,7 @@ def test_generate_lfric_adjoint_harness(fortran_reader, fortran_writer): " inner2 = inner2 + ascalar * ascalar_input\n" " inner2 = inner2 + field_field_input_inner_prod\n" in gen) + @pytest.mark.xfail(reason="func_type and gh_quadrature_xyoz are neither " "declared nor imported in the TL code.") def test_generate_lfric_adj_test_quadrature(fortran_reader): diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index 22390b76d5..256bf6f67a 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -697,7 +697,7 @@ def test_structure_type(): assert not flag.initial_value assert isinstance(flag, StructureType.ComponentType) stype.add_component("flag2", INTEGER_TYPE, Symbol.Visibility.PUBLIC, - Literal("1", INTEGER_TYPE)) + Literal("1", INTEGER_TYPE)) flag2 = stype.lookup_component("flag2") assert isinstance(flag2, StructureType.ComponentType) assert flag2.initial_value.value == "1" @@ -714,7 +714,8 @@ def test_structure_type(): assert ("visibility of a component of a StructureType must be an instance " "of 'Symbol.Visibility' but got 'str'" in str(err.value)) with pytest.raises(TypeError) as err: - stype.add_component("hello", INTEGER_TYPE, Symbol.Visibility.PUBLIC, "Hello") + stype.add_component("hello", INTEGER_TYPE, Symbol.Visibility.PUBLIC, + "Hello") assert ("The initial value of a component of a StructureType must be " "None or an instance of 'DataNode', but got 'str'." in str(err.value)) From db3d952520ccbcbcab520856b4f50a73bf9e2278 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 15 Jul 2024 17:08:29 +0200 Subject: [PATCH 05/23] #2642 Add missing `class` keyword in Fortran backend. --- src/psyclone/psyir/backend/fortran.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index 3a1514131d..34a705588d 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -122,6 +122,8 @@ def gen_datatype(datatype, name): ''' if isinstance(datatype, DataTypeSymbol): # Symbol is of derived type + if datatype.is_class: + return f"class({datatype.name})" return f"type({datatype.name})" if (isinstance(datatype, ArrayType) and From 6d0bb74a4ccef31eb4da0530b3d508701b7a3dd6 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 15 Jul 2024 17:24:16 +0200 Subject: [PATCH 06/23] #2642 Fix `replace` calls for #2643 workaround. --- src/psyclone/domain/gocean/kernel/psyir.py | 3 ++- src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/psyclone/domain/gocean/kernel/psyir.py b/src/psyclone/domain/gocean/kernel/psyir.py index 77c5693b05..480538b321 100644 --- a/src/psyclone/domain/gocean/kernel/psyir.py +++ b/src/psyclone/domain/gocean/kernel/psyir.py @@ -231,7 +231,8 @@ def create_from_psyir(symbol): # itself. if isinstance(datatype, StructureType): type_declaration = FortranWriter().gen_typedecl(symbol) - type_declaration.replace(", public", "").replace(", private", "") + type_declaration = type_declaration.replace(", public", "") + type_declaration = type_declaration.replace(", private", "") return GOceanKernelMetadata.create_from_fortran_string( type_declaration) diff --git a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py index aa9bc5ec0e..e79ff2617f 100644 --- a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py +++ b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py @@ -707,7 +707,8 @@ def create_from_psyir(symbol): # itself. if isinstance(datatype, StructureType): type_declaration = FortranWriter().gen_typedecl(symbol) - type_declaration.replace(", public", "").replace(", private", "") + type_declaration = type_declaration.replace(", public", "") + type_declaration = type_declaration.replace(", private", "") return LFRicKernelMetadata.create_from_fortran_string( type_declaration) From 16ed2e4bdb52a5f78df49c8e3573db418ed1d1d8 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Thu, 28 Nov 2024 12:44:51 +0100 Subject: [PATCH 07/23] #2642 Fix flake8 formatting and some tests. --- src/psyclone/psyad/domain/lfric/lfric_adjoint.py | 7 ++++--- src/psyclone/psyir/frontend/fparser2.py | 4 ++-- src/psyclone/psyir/symbols/data_type_symbol.py | 3 ++- src/psyclone/psyir/symbols/datatypes.py | 5 +++-- .../tests/psyad/domain/lfric/test_lfric_adjoint.py | 1 - 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/src/psyclone/psyad/domain/lfric/lfric_adjoint.py b/src/psyclone/psyad/domain/lfric/lfric_adjoint.py index 6a7fd95c87..2b01015d4c 100644 --- a/src/psyclone/psyad/domain/lfric/lfric_adjoint.py +++ b/src/psyclone/psyad/domain/lfric/lfric_adjoint.py @@ -52,7 +52,7 @@ from psyclone.psyad import AdjointVisitor from psyclone.psyad.domain.common import create_adjoint_name from psyclone.psyir.nodes import Routine -from psyclone.psyir.symbols import ContainerSymbol, UnsupportedFortranType +from psyclone.psyir.symbols import (ContainerSymbol, StructureType) from psyclone.psyir.symbols.symbol import ArgumentInterface, ImportInterface @@ -80,8 +80,9 @@ def generate_lfric_adjoint(tl_psyir, active_variables): # linear kernel. tl_container = find_container(tl_psyir) for sym in tl_container.symbol_table.datatypesymbols: - if (isinstance(sym.datatype, UnsupportedFortranType) and - "extends(kernel_type)" in sym.datatype.declaration.lower()): + if (isinstance(sym.datatype, StructureType) and + sym.datatype.extends is not None and + sym.datatype.extends.name.lower() == "kernel_type"): tl_metadata_name = sym.name break else: diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 162b0792b3..f9c1071c52 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -2125,8 +2125,8 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): else: datatype = symbol.datatype initial_value = symbol.initial_value - dtype.add_component(symbol.name, datatype, symbol.visibility, - initial_value) + dtype.add_component(symbol.name, datatype, + symbol.visibility, initial_value) # Update its type with the definition we've found tsymbol.datatype = dtype diff --git a/src/psyclone/psyir/symbols/data_type_symbol.py b/src/psyclone/psyir/symbols/data_type_symbol.py index 445dcec58c..5db283a45d 100644 --- a/src/psyclone/psyir/symbols/data_type_symbol.py +++ b/src/psyclone/psyir/symbols/data_type_symbol.py @@ -77,7 +77,8 @@ def copy(self): ''' return type(self)(self.name, self.datatype, visibility=self.visibility, - interface=self.interface.copy(), is_class=self.is_class) + interface=self.interface.copy(), + is_class=self.is_class) def __str__(self): return f"{self.name}: {type(self).__name__}" diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index ff88bfaef9..c2fee91175 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -1003,8 +1003,9 @@ def procedure_components(self): @property def extends(self): ''' - :returns: the type that this new type extends. - :rtype: :py:class:`psyclone.psyir.symbols.DataTypeSymbol` + :returns: the type that this new type extends, or None. + :rtype: Union[:py:class:`psyclone.psyir.symbols.DataTypeSymbol`, + None] ''' return self._extends diff --git a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py index ec49b463fd..363e5df22a 100644 --- a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py +++ b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py @@ -52,7 +52,6 @@ _update_access_metadata, _check_or_add_access_symbol) from psyclone.psyir.symbols import ( DataSymbol, ArgumentInterface, INTEGER_TYPE, REAL_TYPE, StructureType) -from psyclone.psyir.transformations import TransformationError def test_generate_lfric_adjoint_no_container_error(fortran_reader): From e182c187027839d6a776d9f3e25f421566a436d9 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Thu, 28 Nov 2024 16:18:45 +0100 Subject: [PATCH 08/23] #2642 Some codecov progress, some tests still missing. --- src/psyclone/domain/gocean/kernel/psyir.py | 26 +++++++------------ src/psyclone/psyir/backend/fortran.py | 9 ++----- src/psyclone/psyir/frontend/fparser2.py | 10 ------- .../gocean/kernel/gocean_kern_psyir_test.py | 4 +-- .../tests/psyir/backend/fortran_test.py | 8 ++++-- .../psyir/symbols/data_type_symbol_test.py | 10 +++++++ 6 files changed, 30 insertions(+), 37 deletions(-) diff --git a/src/psyclone/domain/gocean/kernel/psyir.py b/src/psyclone/domain/gocean/kernel/psyir.py index 480538b321..73145bbdd9 100644 --- a/src/psyclone/domain/gocean/kernel/psyir.py +++ b/src/psyclone/domain/gocean/kernel/psyir.py @@ -224,28 +224,22 @@ def create_from_psyir(symbol): datatype = symbol.datatype + if not isinstance(datatype, StructureType): + raise InternalError( + f"Expected kernel metadata to be stored in the PSyIR as " + f"a StructureType, but found " + f"{type(datatype).__name__}.") + # TODO #2643: This is a temporary solution using FortranWriter # to allow the current metadata extraction to work with StructureType, # instead of relying on UnsupportedFortranType. # This will be removed when the metadata is extracted from the PSyIR # itself. - if isinstance(datatype, StructureType): - type_declaration = FortranWriter().gen_typedecl(symbol) - type_declaration = type_declaration.replace(", public", "") - type_declaration = type_declaration.replace(", private", "") - return GOceanKernelMetadata.create_from_fortran_string( - type_declaration) - - if not isinstance(datatype, UnsupportedFortranType): - raise InternalError( - f"Expected kernel metadata to be stored in the PSyIR as " - f"an UnsupportedFortranType, but found " - f"{type(datatype).__name__}.") - - # In an UnsupportedFortranType, the declaration is stored as a - # string, so use create_from_fortran_string() + type_declaration = FortranWriter().gen_typedecl(symbol) + type_declaration = type_declaration.replace(", public", "") + type_declaration = type_declaration.replace(", private", "") return GOceanKernelMetadata.create_from_fortran_string( - datatype.declaration) + type_declaration) @staticmethod def create_from_fortran_string(fortran_string): diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index a5b02c94b8..6272641906 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -505,7 +505,8 @@ def gen_proceduredecl(self, symbol, include_visibility=True): :returns: the Fortran procedure declaration as a string. :rtype: str - :raises VisitorError: if the symbol is not a RoutineSymbol. + :raises VisitorError: if the symbol is not a RoutineSymbol or a + StructureType.ComponentType. :raises InternalError: if the visibility is to be included but is neither PUBLIC nor PRIVATE. ''' @@ -747,12 +748,6 @@ def gen_typedecl(self, symbol, include_visibility=True): f"Fortran backend cannot generate code for symbol " f"'{symbol.name}' of type '{type(symbol.datatype).__name__}'") - if isinstance(symbol.datatype, UnresolvedType): - raise VisitorError( - f"Local Symbol '{symbol.name}' is of UnresolvedType and " - f"therefore no declaration can be created for it. Should it " - f"have an ImportInterface?") - if not isinstance(symbol.datatype, StructureType): raise VisitorError( f"gen_typedecl expects a DataTypeSymbol with a StructureType " diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index f9c1071c52..b08c8c9535 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -2028,18 +2028,8 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): # We support derived-type definitions with a CONTAINS section. contains_blocks = walk(decl, Fortran2003.Type_Bound_Procedure_Part) if contains_blocks: - # First check there is only one CONTAINS section. - if len(contains_blocks) > 1: - raise NotImplementedError( - "Derived-type definition contains multiple CONTAINS " - "statements.") # Get it. contains = contains_blocks[0] - # Check that it's indeed a CONTAINS statement. - if walk(contains, Fortran2003.Contains_Stmt) is None: - raise NotImplementedError( - "Derived-type definition contains a procedure " - "without a CONTAINS statement.") # Get all procedures in the CONTAINS section. procedures = walk(contains, Fortran2003.Specific_Binding) if procedures is None: diff --git a/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py b/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py index 4c43dc617e..74a92c99f1 100644 --- a/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py +++ b/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py @@ -210,8 +210,8 @@ def test_goceankernelmetadata_create1(fortran_reader): symbol._datatype = REAL_TYPE with pytest.raises(InternalError) as info: _ = GOceanKernelMetadata.create_from_psyir(symbol) - assert ("Expected kernel metadata to be stored in the PSyIR as an " - "UnsupportedFortranType, but found ScalarType." in str(info.value)) + assert ("Expected kernel metadata to be stored in the PSyIR as a " + "StructureType, but found ScalarType." in str(info.value)) # create_from_fortran_string diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index 32c6b38074..28e399c686 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -373,8 +373,8 @@ def test_gen_typedecl_validation(fortran_writer, monkeypatch): tsymbol = DataTypeSymbol("my_type", UnresolvedType()) with pytest.raises(VisitorError) as err: fortran_writer.gen_typedecl(tsymbol) - assert ("Local Symbol 'my_type' is of UnresolvedType and therefore no " - "declaration can be created for it." in str(err.value)) + assert ("gen_typedecl expects a DataTypeSymbol with a StructureType " + "as its datatype but got: 'UnresolvedType'" in str(err.value)) def test_gen_typedecl_unsupported_fortran_type(fortran_writer): @@ -760,6 +760,10 @@ def test_fw_gen_vardecl_visibility(fortran_writer): "end type var\n") +def test_fw_gen_proceduredecl(fortran_writer): + pass + + def test_gen_default_access_stmt(fortran_writer): ''' Tests for the gen_default_access_stmt method of FortranWriter. diff --git a/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py b/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py index 11dc71ea85..a77bdfe702 100644 --- a/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py +++ b/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py @@ -60,6 +60,16 @@ def test_create_datatypesymbol_wrong_datatype(): sym.datatype = "integer" assert ("datatype of a DataTypeSymbol must be specified using a " "DataType but got: 'str'" in str(err.value)) + + +def test_create_datatypesymbol_wrong_is_class(): + ''' Check that attempting to specify the is_class attribute of a + DataTypeSymbol with an invalid type results in the expected error. ''' + sym = DataTypeSymbol("my_type", UnresolvedType()) + with pytest.raises(TypeError) as err: + sym.is_class = "integer" + assert ("The is_class attribute of a DataTypeSymbol must be a bool " + "but got: 'str'" in str(err.value)) def test_datatypesymbol_copy(): From 8c789b78e9df57eccc9ec203188eb873bab64bcb Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Fri, 29 Nov 2024 19:14:13 +0100 Subject: [PATCH 09/23] #2642 More tests. --- src/psyclone/psyir/frontend/fparser2.py | 120 +++++++------- src/psyclone/psyir/symbols/datatypes.py | 12 +- .../tests/psyir/backend/fortran_test.py | 58 ++++++- .../tests/psyir/frontend/fparser2_test.py | 154 +++++++++++++++--- .../psyir/symbols/data_type_symbol_test.py | 2 +- .../tests/psyir/symbols/datatype_test.py | 91 ++++++++++- 6 files changed, 338 insertions(+), 99 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index b08c8c9535..f2d929100e 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -1589,10 +1589,6 @@ def _process_type_spec(self, parent, type_spec): elif isinstance(type_spec, Fortran2003.Declaration_Type_Spec): # This is a variable of derived type - if type_spec.children[0].lower() not in ("type", "class"): - raise NotImplementedError( - f"Could not process {type_spec} - declarations " - f"other than 'type' or 'class' are not yet supported.") if isinstance(type_spec.items[1], Fortran2003.Type_Name): type_name = str(type_spec.items[1].string).lower() else: @@ -2032,68 +2028,64 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): contains = contains_blocks[0] # Get all procedures in the CONTAINS section. procedures = walk(contains, Fortran2003.Specific_Binding) - if procedures is None: - # The CONTAINS statement is empty. - raise NotImplementedError( - "Derived-type definition contains an empty " - "CONTAINS statement.") - # Process each procedure. - for procedure in procedures: - supported = True - # We do not support interfaces. - if procedure.items[0] is not None: - supported = False - # We do not support 'pass', 'nopass', etc. - if procedure.items[1] is not None: - supported = False - - # Get the name, look it up in the symbol table and - # get its datatype or create it if it does not exist. - procedure_name = procedure.items[3].string - if procedure_name in parent.symbol_table and supported: - procedure_symbol = parent.symbol_table.\ - lookup(procedure_name) - procedure_datatype = procedure_symbol.datatype - else: - procedure_datatype = UnsupportedFortranType( - procedure.string, - None) - - # Get the visibility of the procedure. - procedure_vis = dtype_symbol_vis - if procedure.items[1] is not None: - access_spec = walk(procedure.items[1], - Fortran2003.Access_Spec) - if access_spec: - procedure_vis = _process_access_spec( - access_spec[0]) - - # Deal with the optional initial value. - if procedure.items[4] is not None: - initial_value_name = procedure.items[4].string - # Look it up in the symbol table and get its datatype - # or create it if it does not exist. - if initial_value_name in parent.symbol_table: - initial_value_symbol = parent.symbol_table.lookup( - initial_value_name) - if (isinstance(procedure_datatype, - UnsupportedFortranType) - and supported): - procedure_datatype = initial_value_symbol.\ - datatype + if len(procedures) > 0: + # Process each procedure. + for procedure in procedures: + supported = True + # We do not support interfaces. + if procedure.items[0] is not None: + supported = False + # We do not support 'pass', 'nopass', 'deferred', etc. + if procedure.items[1] is not None: + supported = False + + # Get the name, look it up in the symbol table and + # get its datatype or create it if it does not exist. + procedure_name = procedure.items[3].string + if procedure_name in parent.symbol_table and supported: + procedure_symbol = parent.symbol_table.\ + lookup(procedure_name) + procedure_datatype = procedure_symbol.datatype else: - initial_value_symbol = RoutineSymbol( - initial_value_name, - UnresolvedType()) - initial_value = Reference(initial_value_symbol) - else: - initial_value = None + procedure_datatype = UnsupportedFortranType( + procedure.string, + None) + + # Get the visibility of the procedure. + procedure_vis = dtype_symbol_vis + if procedure.items[1] is not None: + access_spec = walk(procedure.items[1], + Fortran2003.Access_Spec) + if access_spec: + procedure_vis = _process_access_spec( + access_spec[0]) + + # Deal with the optional initial value. + if procedure.items[4] is not None: + initial_value_name = procedure.items[4].string + # Look it up in the symbol table and get its + # datatype or create it if it does not exist. + if initial_value_name in parent.symbol_table: + initial_value_symbol = parent.symbol_table.\ + lookup(initial_value_name) + if (isinstance(procedure_datatype, + UnsupportedFortranType) + and supported): + procedure_datatype = initial_value_symbol.\ + datatype + else: + initial_value_symbol = RoutineSymbol( + initial_value_name, + UnresolvedType()) + initial_value = Reference(initial_value_symbol) + else: + initial_value = None - # Add this procedure as a component of the derived type. - dtype.add_procedure_component(procedure_name, - procedure_datatype, - procedure_vis, - initial_value) + # Add this procedure as a component of the derived type + dtype.add_procedure_component(procedure_name, + procedure_datatype, + procedure_vis, + initial_value) # Re-use the existing code for processing symbols. This needs to # be able to find any symbols declared in an outer scope but diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index c2fee91175..078f330e9c 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -973,7 +973,7 @@ def create(components, procedure_components=None, extends=None): stype.add_component(*component) if procedure_components: for procedure_component in procedure_components: - if len(procedure_components) != 4: + if len(procedure_component) != 4: raise TypeError( f"Each procedure component must be specified using a " f"4-tuple of (name, type, visibility, initial_value) " @@ -1123,15 +1123,15 @@ def add_procedure_component(self, name, datatype, visibility, f"be a 'StructureType' but got '{type(datatype).__name__}'") if not isinstance(visibility, Symbol.Visibility): raise TypeError( - f"The visibility of a component of a StructureType must be " - f"an instance of 'Symbol.Visibility' but got " + f"The visibility of a procedure component of a StructureType " + f"must be an instance of 'Symbol.Visibility' but got " f"'{type(visibility).__name__}'") if (initial_value is not None and not isinstance(initial_value, DataNode)): raise TypeError( - f"The initial value of a component of a StructureType must " - f"be None or an instance of 'DataNode', but got " - f"'{type(initial_value).__name__}'.") + f"The initial value of a procedure component of a " + f"StructureType must be None or an instance of 'DataNode', " + f"but got '{type(initial_value).__name__}'.") self._procedure_components[name] = self.ComponentType( name, datatype, visibility, initial_value) diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index 28e399c686..08a5aa8c8a 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -761,7 +761,63 @@ def test_fw_gen_vardecl_visibility(fortran_writer): def test_fw_gen_proceduredecl(fortran_writer): - pass + '''Test the FortranWriter class gen_proceduredecl method produces the + expected declarations and raises the expected exceptions. + ''' + with pytest.raises(VisitorError) as err: + fortran_writer.gen_proceduredecl(None) + assert ("gen_proceduredecl() expects a 'DataSymbol' or " + "'StructureType.ComponentType' as its first " + "argument but got 'NoneType'" in str(err.value)) + + # A DataSymbol of UnupportedFortranType + symbol = DataSymbol("my_sub", UnsupportedFortranType( + "procedure, private :: my_unsupported_procedure")) + assert (fortran_writer.gen_proceduredecl(symbol) == + "procedure, private :: my_unsupported_procedure\n") + + # A StructureType.ComponentType with 'public' visibility and no initial + # value + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PUBLIC, None) + assert (fortran_writer.gen_proceduredecl(dtype) == + "procedure, public :: my_procedure\n") + + # A StructureType.ComponentType with 'public' visibility and an initial + # value + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PUBLIC, + Reference(RoutineSymbol("other", + REAL_TYPE))) + assert (fortran_writer.gen_proceduredecl(dtype) == + "procedure, public :: my_procedure => other\n") + + # A StructureType.ComponentType with 'private' visibility and no initial + # value + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PRIVATE, None) + assert fortran_writer.gen_proceduredecl(dtype) == ( + "procedure, private :: my_procedure\n") + + # A StructureType.ComponentType with 'private' visibility and an initial + # value + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PRIVATE, + Reference(RoutineSymbol("other", + REAL_TYPE))) + assert fortran_writer.gen_proceduredecl(dtype) == ( + "procedure, private :: my_procedure => other\n") + + # Check that visibility is not included in the output if include_visibility + # is False + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PUBLIC, None) + assert (fortran_writer.gen_proceduredecl(dtype, include_visibility=False) + == ("procedure :: my_procedure\n")) + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PRIVATE, None) + assert (fortran_writer.gen_proceduredecl(dtype, include_visibility=False) + == ("procedure :: my_procedure\n")) def test_gen_default_access_stmt(fortran_writer): diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index b293ccc72c..0b96c4514f 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -581,6 +581,21 @@ def test_process_declarations(): assert isinstance(ptr_sym.datatype, UnsupportedFortranType) assert isinstance(ptr_sym.initial_value, IntrinsicCall) + # Class declaration + reader = FortranStringReader("class(my_type), intent(in) :: carg") + # Set reader to free format (otherwise this is a comment in fixed format) + reader.set_format(FortranFormat(True, True)) + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + sym = fake_parent.symbol_table.lookup("carg") + assert isinstance(sym, DataSymbol) + assert isinstance(sym.datatype, DataTypeSymbol) + assert sym.datatype.is_class is True + assert sym.name == "carg" + assert sym.datatype.name == "my_type" + assert isinstance(sym.interface, ArgumentInterface) + assert sym.interface.access == ArgumentInterface.Access.READ + @pytest.mark.usefixtures("f2008_parser") @pytest.mark.parametrize("decln_text", @@ -1173,20 +1188,6 @@ def test_process_not_supported_declarations(): assert isinstance(fake_parent.symbol_table.lookup("p3").datatype, UnsupportedFortranType) - reader = FortranStringReader("class(my_type), intent(in) :: carg") - # Set reader to free format (otherwise this is a comment in fixed format) - reader.set_format(FortranFormat(True, True)) - fparser2spec = Specification_Part(reader).content[0] - processor.process_declarations(fake_parent, [fparser2spec], []) - sym = fake_parent.symbol_table.lookup("carg") - assert isinstance(sym, DataSymbol) - assert isinstance(sym.datatype, DataTypeSymbol) - assert sym.datatype.is_class is True - assert sym.name == "carg" - assert sym.datatype.name == "my_type" - assert isinstance(sym.interface, ArgumentInterface) - assert (sym.interface.access == ArgumentInterface.Access.READ) - # Allocatable but with specified extent. This is invalid Fortran but # fparser2 doesn't spot it (see fparser/#229). reader = FortranStringReader("integer, allocatable :: l10(5)") @@ -2563,7 +2564,8 @@ def test_structures(fortran_reader, fortran_writer): " integer, public :: j\n" " end type my_type\n" in result) - # type that extends another type (UnsupportedFortranType) + # type that extends another type, known in the symbol table + # (StructureType) test_code = ( "module test_mod\n" " use kernel_mod, only : kernel_type\n" @@ -2577,13 +2579,35 @@ def test_structures(fortran_reader, fortran_writer): assert isinstance(symbol, DataTypeSymbol) assert isinstance(symbol.datatype, StructureType) assert isinstance(symbol.datatype.extends, DataTypeSymbol) + assert symbol.datatype.extends.name == "kernel_type" result = fortran_writer(psyir) assert ( " type, extends(kernel_type), public :: my_type\n" " integer, public :: i = 1\n" " end type my_type\n" in result) - # type that contains a procedure (UnsupportedFortranType) + # type that extends another type, unknown (StructureType) + test_code = ( + "module test_mod\n" + " use kernel_mod\n" + " type, extends(kernel_type) :: my_type\n" + " integer :: i = 1\n" + " end type my_type\n" + "end module test_mod\n") + psyir = fortran_reader.psyir_from_source(test_code) + sym_table = psyir.children[0].symbol_table + symbol = sym_table.lookup("my_type") + assert isinstance(symbol, DataTypeSymbol) + assert isinstance(symbol.datatype, StructureType) + assert isinstance(symbol.datatype.extends, DataTypeSymbol) + assert symbol.datatype.extends.name == "kernel_type" + result = fortran_writer(psyir) + assert ( + " type, extends(kernel_type), public :: my_type\n" + " integer, public :: i = 1\n" + " end type my_type\n" in result) + + # type that contains a procedure (StructureType) test_code = ( "module test_mod\n" " type :: test_type\n" @@ -2608,6 +2632,24 @@ def test_structures(fortran_reader, fortran_writer): " procedure, nopass :: test_code\n" " end type test_type\n" in result) + # abstract type (UnsupportedFortranType) + test_code = ( + "module test_mod\n" + " type, abstract :: my_type\n" + " integer :: i = 1\n" + " end type my_type\n" + "end module test_mod\n") + psyir = fortran_reader.psyir_from_source(test_code) + sym_table = psyir.children[0].symbol_table + symbol = sym_table.lookup("my_type") + assert isinstance(symbol, DataTypeSymbol) + assert isinstance(symbol.datatype, UnsupportedFortranType) + result = fortran_writer(psyir) + assert ( + " type, abstract, public :: my_type\n" + " INTEGER :: i = 1\n" + "END TYPE my_type\n" in result) + # type that creates an abstract type and contains a procedure # (UnsupportedFortranType) test_code = ( @@ -2634,7 +2676,7 @@ def test_structures(fortran_reader, fortran_writer): " PROCEDURE, NOPASS :: test_code\n" "END TYPE test_type\n" in result) - # type that contains a procedure + # type that contains procedures test_code = ( "module test_mod\n" " use kernel_mod, only : parent_type\n" @@ -2660,14 +2702,6 @@ def test_structures(fortran_reader, fortran_writer): assert isinstance(symbol.datatype, StructureType) assert len(symbol.datatype.procedure_components) == 3 result = fortran_writer(psyir) - # assert ( - # " type, extends(parent_type), public :: test_type\n" - # " integer, public :: i = 1\n" - # " contains\n" - # " procedure :: test_code\n" - # " procedure, private :: test_code_private\n" - # " procedure, public :: test_code_public\n" - # " end type test_type\n" in result) assert ( " type, extends(parent_type), public :: test_type\n" " integer, public :: i = 1\n" @@ -2677,6 +2711,76 @@ def test_structures(fortran_reader, fortran_writer): " procedure, public :: test_code_public\n" " end type test_type\n" in result) + # type that contains a procedure with an interface name + test_code = ( + "module test_mod\n" + " type :: test_type\n" + " integer :: i = 1\n" + " contains\n" + " procedure (my_interface) :: test_code\n" + " end type test_type\n" + " contains\n" + " subroutine test_code()\n" + " end subroutine\n" + "end module test_mod\n") + psyir = fortran_reader.psyir_from_source(test_code) + sym_table = psyir.children[0].symbol_table + symbol = sym_table.lookup("test_type") + assert isinstance(symbol, DataTypeSymbol) + assert isinstance(symbol.datatype, StructureType) + assert len(symbol.datatype.procedure_components) == 1 + assert "test_code" in symbol.datatype.procedure_components + assert isinstance(symbol.datatype.procedure_components["test_code"]. + datatype, UnsupportedFortranType) + result = fortran_writer(psyir) + assert ( + " type, public :: test_type\n" + " integer, public :: i = 1\n" + " contains\n" + " procedure (my_interface) :: test_code\n" + " end type test_type\n" in result) + + # type that contains procedures with pass, nopass and deferred + test_code = ( + "module test_mod\n" + " type :: test_type\n" + " integer :: i = 1\n" + " contains\n" + " procedure, pass :: test_code_pass\n" + " procedure, nopass :: test_code_nopass\n" + " procedure, deferred :: test_code_deferred\n" + " end type test_type\n" + " contains\n" + " subroutine test_code()\n" + " end subroutine\n" + "end module test_mod\n") + psyir = fortran_reader.psyir_from_source(test_code) + sym_table = psyir.children[0].symbol_table + symbol = sym_table.lookup("test_type") + assert isinstance(symbol, DataTypeSymbol) + assert isinstance(symbol.datatype, StructureType) + assert len(symbol.datatype.procedure_components) == 3 + assert "test_code_pass" in symbol.datatype.procedure_components + assert isinstance(symbol.datatype.procedure_components["test_code_pass"]. + datatype, UnsupportedFortranType) + assert "test_code_nopass" in symbol.datatype.procedure_components + assert isinstance(symbol.datatype. + procedure_components["test_code_nopass"]. + datatype, UnsupportedFortranType) + assert "test_code_deferred" in symbol.datatype.procedure_components + assert isinstance(symbol.datatype. + procedure_components["test_code_deferred"]. + datatype, UnsupportedFortranType) + result = fortran_writer(psyir) + assert ( + " type, public :: test_type\n" + " integer, public :: i = 1\n" + " contains\n" + " procedure, pass :: test_code_pass\n" + " procedure, nopass :: test_code_nopass\n" + " procedure, deferred :: test_code_deferred\n" + " end type test_type\n" in result) + def test_structures_constants(fortran_reader, fortran_writer): '''Test that Fparser2Reader parses Fortran types correctly when there diff --git a/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py b/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py index a77bdfe702..2f1d49293c 100644 --- a/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py +++ b/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py @@ -60,7 +60,7 @@ def test_create_datatypesymbol_wrong_datatype(): sym.datatype = "integer" assert ("datatype of a DataTypeSymbol must be specified using a " "DataType but got: 'str'" in str(err.value)) - + def test_create_datatypesymbol_wrong_is_class(): ''' Check that attempting to specify the is_class attribute of a diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index c70f6d7fab..2c82ba0d5e 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -46,7 +46,7 @@ from psyclone.psyir.symbols import ( ArrayType, DataType, UnresolvedType, ScalarType, UnsupportedFortranType, DataSymbol, StructureType, NoType, INTEGER_TYPE, REAL_TYPE, Symbol, - DataTypeSymbol, SymbolTable) + DataTypeSymbol, SymbolTable, RoutineSymbol) # Abstract DataType class @@ -890,7 +890,8 @@ def test_unsupported_fortran_type_replace_symbols(): # StructureType tests def test_structure_type(): - ''' Check the StructureType constructor and that we can add components. ''' + ''' Check the StructureType constructor and that we can add components + and procedure_components. ''' stype = StructureType() assert str(stype) == "StructureType<>" assert not stype.components @@ -929,6 +930,33 @@ def test_structure_type(): assert ("attempting to add component 'hello' - a StructureType definition " "cannot be recursive" in str(err.value)) + # Add a procedure component + stype.add_procedure_component("proc1", INTEGER_TYPE, + Symbol.Visibility.PUBLIC, + None) + proc1 = stype.lookup_procedure_component("proc1") + assert not proc1.initial_value + assert isinstance(proc1, StructureType.ComponentType) + with pytest.raises(TypeError) as err: + stype.add_procedure_component(None, None, None, None) + assert ("The name of a procedure component of a StructureType must " + "be a 'str' but got 'NoneType'" in str(err.value)) + with pytest.raises(TypeError) as err: + stype.add_procedure_component("proc2", None, None, None) + assert ("The type of a procedure component of a StructureType must " + "be a 'DataType' but got 'NoneType'" in str(err.value)) + with pytest.raises(TypeError) as err: + stype.add_procedure_component("proc3", INTEGER_TYPE, None, None) + assert ("The visibility of a procedure component of a StructureType must " + "be an instance of 'Symbol.Visibility' but got 'NoneType'" + in str(err.value)) + with pytest.raises(TypeError) as err: + stype.add_procedure_component("proc4", INTEGER_TYPE, + Symbol.Visibility.PUBLIC, "Hello") + assert ("The initial value of a procedure component of a StructureType " + "must be None or an instance of 'DataNode', but got 'str'." + in str(err.value)) + def test_create_structuretype(): ''' Test the create() method of StructureType. ''' @@ -959,6 +987,36 @@ def test_create_structuretype(): "type, visibility, initial_value) but found a tuple with 2 " "members: ('george', " in str(err.value)) + stype = StructureType.create([], + [("procedure1", INTEGER_TYPE, + Symbol.Visibility.PUBLIC, None), + ("procedure2", REAL_TYPE, + Symbol.Visibility.PRIVATE, + Reference(RoutineSymbol("my_routine", + REAL_TYPE)))]) + assert len(stype.procedure_components) == 2 + proc1 = stype.lookup_procedure_component("procedure1") + assert isinstance(proc1, StructureType.ComponentType) + assert proc1.name == "procedure1" + assert proc1.datatype == INTEGER_TYPE + assert proc1.visibility == Symbol.Visibility.PUBLIC + assert not proc1.initial_value + proc2 = stype.lookup_procedure_component("procedure2") + assert isinstance(proc2, StructureType.ComponentType) + assert proc2.name == "procedure2" + assert proc2.datatype == REAL_TYPE + assert proc2.visibility == Symbol.Visibility.PRIVATE + assert isinstance(proc2.initial_value, Reference) + assert proc2.initial_value.symbol.name == "my_routine" + with pytest.raises(TypeError) as err: + StructureType.create([], + [("procedure1", INTEGER_TYPE, + Symbol.Visibility.PUBLIC, None), + ("procedure2", REAL_TYPE)]) + assert ("Each procedure component must be specified using a 4-tuple of " + "(name, type, visibility, initial_value) but found a tuple with 2 " + "members: ('procedure2', " in str(err.value)) + def test_structuretype_eq(): '''Test the equality operator of StructureType.''' @@ -997,6 +1055,20 @@ def test_structuretype_eq(): ("peggy", REAL_TYPE, Symbol.Visibility.PRIVATE, Literal("1.0", REAL_TYPE)), ("roger", INTEGER_TYPE, Symbol.Visibility.PUBLIC, None)]) + # Different number of procedure components. + assert stype != StructureType.create([ + ("nancy", INTEGER_TYPE, Symbol.Visibility.PUBLIC, None), + ("peggy", REAL_TYPE, Symbol.Visibility.PRIVATE, + Literal("1.0", REAL_TYPE))], [ + ("procedure1", INTEGER_TYPE, Symbol.Visibility.PUBLIC, None)]) + # Different procedure components. + stype.add_procedure_component("procedure2", INTEGER_TYPE, + Symbol.Visibility.PUBLIC, None) + assert stype != StructureType.create([ + ("nancy", INTEGER_TYPE, Symbol.Visibility.PUBLIC, None), + ("peggy", REAL_TYPE, Symbol.Visibility.PRIVATE, + Literal("1.0", REAL_TYPE))], [ + ("procedure1", INTEGER_TYPE, Symbol.Visibility.PUBLIC, None)]) def test_structuretype_replace_symbols(): @@ -1018,3 +1090,18 @@ def test_structuretype_replace_symbols(): table.add(newtsymbol) stype.replace_symbols_using(table) assert stype.components["barry"].datatype is newtsymbol + + +def test_structuretype_extends(): + '''Test the StructureType.extends setter.''' + parent_datatype = DataTypeSymbol("parent_type", StructureType()) + structure_type = StructureType.create([('a', INTEGER_TYPE, + Symbol.Visibility.PUBLIC, None)], + extends=parent_datatype) + assert structure_type.extends is parent_datatype + assert structure_type.extends.name == "parent_type" + + with pytest.raises(TypeError) as err: + structure_type.extends = "invalid" + assert ("The type that a StructureType extends must be a " + "DataTypeSymbol but got 'str'." in str(err.value)) From 1cd37e472f3c184e956744ece9584a8b5a1118f5 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Fri, 29 Nov 2024 19:19:04 +0100 Subject: [PATCH 10/23] #2642 Edit dev doc --- doc/developer_guide/psyir_symbols.rst | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/doc/developer_guide/psyir_symbols.rst b/doc/developer_guide/psyir_symbols.rst index bce982c430..557b4ca9a2 100644 --- a/doc/developer_guide/psyir_symbols.rst +++ b/doc/developer_guide/psyir_symbols.rst @@ -78,16 +78,15 @@ explicitly listed may be assumed to be unsupported): +----------------------+--------------------+--------------------+ | |Supported |Unsupported | +======================+====================+====================+ -|Variables |ALLOCATABLE |CLASS | +|Variables |ALLOCATABLE, CLASS | | +----------------------+--------------------+--------------------+ | |CHARACTER, DOUBLE |COMPLEX, CHARACTER | | |PRECISION, INTEGER, |with LEN or KIND | | |LOGICAL, REAL | | +----------------------+--------------------+--------------------+ -| |Derived Types |'extends', | -| | |'abstract' or with | -| | |CONTAINS; Operator | -| | |overloading | +| |Derived Types, |'abstract', | +| |'extends', |operator overloading| +| |CONTAINS | | +----------------------+--------------------+--------------------+ | |DIMENSION |Array extents | | | |specified using | From 07195593e18c59dbaad5c7662ddc5b49c9719bf6 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Fri, 29 Nov 2024 19:30:34 +0100 Subject: [PATCH 11/23] #2642 Cleanup --- .../domain/lfric/kernel/lfric_kernel_metadata.py | 4 +--- .../domain/lfric/kernel/lfric_kernel_metadata_test.py | 11 ----------- .../tests/psyad/domain/lfric/test_lfric_adjoint.py | 4 ---- .../psyir/frontend/fparser2_derived_type_test.py | 7 ------- 4 files changed, 1 insertion(+), 25 deletions(-) diff --git a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py index 84cf12d1a4..acd2c58c16 100644 --- a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py +++ b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py @@ -706,9 +706,7 @@ def create_from_psyir(symbol): # This will be removed when the metadata is extracted from the PSyIR # itself. if isinstance(datatype, StructureType): - type_declaration = FortranWriter().gen_typedecl(symbol) - type_declaration = type_declaration.replace(", public", "") - type_declaration = type_declaration.replace(", private", "") + type_declaration = FortranWriter().gen_typedecl(symbol, False) return LFRicKernelMetadata.create_from_fortran_string( type_declaration) diff --git a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py index 0233b13afc..69a0df5636 100644 --- a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py @@ -1148,19 +1148,12 @@ def test_get_procedure_name_error(fortran_reader): structure_type = datatype_symbol.datatype assert isinstance(structure_type, StructureType) assert len(structure_type.procedure_components) == 0 - # metadata = LFRicKernelMetadata() - # reader = FortranStringReader(datatype.declaration) - # spec_part = Fortran2003.Derived_Type_Def(reader) - # with pytest.raises(ParseError) as info: - # metadata._get_procedure_name(spec_part) - # assert "Expecting a type-bound procedure, but found" in str(info.value) kernel_psyir = fortran_reader.psyir_from_source(PROGRAM) datatype_symbol = kernel_psyir.children[0].symbol_table.lookup( "testkern_type") datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() - # reader = FortranStringReader(datatype.declaration) assert isinstance(datatype, StructureType) type_declaration = FortranWriter().gen_typedecl(datatype_symbol) reader = FortranStringReader(type_declaration) @@ -1178,7 +1171,6 @@ def test_get_procedure_name_error(fortran_reader): "testkern_type") datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() - # reader = FortranStringReader(datatype.declaration) assert isinstance(datatype, StructureType) type_declaration = FortranWriter().gen_typedecl(datatype_symbol) reader = FortranStringReader(type_declaration) @@ -1201,12 +1193,9 @@ def test_get_procedure_name(fortran_reader): "testkern_type") datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() - # reader = FortranStringReader(datatype.declaration) - assert isinstance(datatype, StructureType) type_declaration = FortranWriter().gen_typedecl(datatype_symbol) reader = FortranStringReader(type_declaration) - spec_part = Fortran2003.Derived_Type_Def(reader) assert metadata._get_procedure_name(spec_part) == \ "testkern_code" diff --git a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py index 363e5df22a..d1c9f3adc5 100644 --- a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py +++ b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py @@ -278,10 +278,6 @@ def test_generate_lfric_adjoint_multi_precision( assert isinstance(datatype, StructureType) datatype.procedure_components.clear() # Remove procedure metadata - # new_declaration = (datatype.declaration. - # replace("PROCEDURE, NOPASS :: kern_code", ""). - # replace("CONTAINS", "")) - # datatype._declaration = new_declaration ad_psyir = generate_lfric_adjoint(psyir, ["field_1_w0", "field_2_w0"]) result = fortran_writer(ad_psyir) # Check that the metadata type name is updated. diff --git a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py index 74a5580a30..88a98d4a93 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py @@ -291,13 +291,6 @@ def test_derived_type_contains(): assert isinstance(sym.datatype, StructureType) assert len(sym.datatype.components) == 2 assert len(sym.datatype.procedure_components) == 1 -# assert sym.datatype.declaration == '''\ -# TYPE :: my_type -# INTEGER :: flag -# REAL, DIMENSION(3) :: posn -# CONTAINS -# PROCEDURE :: init => obesdv_setup -# END TYPE my_type''' @pytest.mark.usefixtures("f2008_parser") From 669842774e250a4559ad3f5257b0c48770f0e245 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Fri, 29 Nov 2024 19:41:48 +0100 Subject: [PATCH 12/23] #2642 More codecov --- src/psyclone/tests/psyir/backend/fortran_test.py | 7 +++++++ src/psyclone/tests/psyir/symbols/datatype_test.py | 4 ++++ 2 files changed, 11 insertions(+) diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index e42809ad8b..3b996bb25d 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -819,6 +819,13 @@ def test_fw_gen_proceduredecl(fortran_writer): assert (fortran_writer.gen_proceduredecl(dtype, include_visibility=False) == ("procedure :: my_procedure\n")) + # Check exception is raised if visibility is not 'public' or 'private' + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + "wrong", None) + with pytest.raises(InternalError) as err: + fortran_writer.gen_proceduredecl(dtype) + assert ("A Symbol must be either public or private but symbol " + "'my_procedure' has visibility 'wrong'" in str(err.value)) def test_gen_default_access_stmt(fortran_writer): ''' diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index 2c82ba0d5e..cc9652faaf 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -945,6 +945,10 @@ def test_structure_type(): stype.add_procedure_component("proc2", None, None, None) assert ("The type of a procedure component of a StructureType must " "be a 'DataType' but got 'NoneType'" in str(err.value)) + with pytest.raises(TypeError) as err: + stype.add_procedure_component("proc3", StructureType(), None, None) + assert ("The type of a procedure component of a StructureType cannot " + "be a 'StructureType' but got 'StructureType'" in str(err.value)) with pytest.raises(TypeError) as err: stype.add_procedure_component("proc3", INTEGER_TYPE, None, None) assert ("The visibility of a procedure component of a StructureType must " From cc6fb67add5520ed0d2774e58f528375549ee4e6 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Fri, 29 Nov 2024 19:45:05 +0100 Subject: [PATCH 13/23] #2642 Flake8 --- src/psyclone/tests/psyir/backend/fortran_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index 3b996bb25d..52a8b45202 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -827,6 +827,7 @@ def test_fw_gen_proceduredecl(fortran_writer): assert ("A Symbol must be either public or private but symbol " "'my_procedure' has visibility 'wrong'" in str(err.value)) + def test_gen_default_access_stmt(fortran_writer): ''' Tests for the gen_default_access_stmt method of FortranWriter. From c8f74cb3c6dc30f1c7d88f197890350b5d8ece78 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 2 Dec 2024 14:21:30 +0100 Subject: [PATCH 14/23] #2642 Remove nonsensical test. Edit psyGen w.r.t. procedure support. --- src/psyclone/psyGen.py | 46 +++++- src/psyclone/psyir/frontend/fparser2.py | 1 - .../frontend/fparser2_select_type_test.py | 136 ------------------ 3 files changed, 45 insertions(+), 138 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index d87f7c153f..f9c4afcbc2 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -60,7 +60,8 @@ UnresolvedType, ImportInterface, INTEGER_TYPE, RoutineSymbol, Symbol) -from psyclone.psyir.symbols.datatypes import UnsupportedFortranType +from psyclone.psyir.symbols.datatypes import (UnsupportedFortranType, + StructureType) # The types of 'intent' that an argument to a Fortran subroutine # may have @@ -1753,6 +1754,8 @@ def _rename_psyir(self, suffix): # the kernel metadata. container_table = container.symbol_table for sym in container_table.datatypesymbols: + # Either the DataTypeSymbol is of UnsupportedFortranType, + # in which case we replace in its whole declaration. if isinstance(sym.datatype, UnsupportedFortranType): new_declaration = sym.datatype.declaration.replace( orig_kern_name, new_kern_name) @@ -1761,6 +1764,47 @@ def _rename_psyir(self, suffix): new_declaration, partial_datatype=sym.datatype.partial_datatype) # pylint: enable=protected-access + # Or the DataTypeSymbol is a StructureType, in which case we + # go through its procedure components. + elif isinstance(sym.datatype, StructureType): + for procedure_name, procedure_component \ + in sym.datatype.procedure_components.items(): + # Either the procedure component is of + # UnsupportedFortranType, in which case we replace in its + # whole declaration. + if isinstance(procedure_component.datatype, + UnsupportedFortranType): + new_declaration = \ + procedure_component.datatype.declaration.replace( + orig_kern_name, new_kern_name) + new_procedure_component = StructureType.ComponentType( + procedure_component.name, + UnsupportedFortranType(new_declaration, + procedure_component.\ + datatype.partial_datatype), + procedure_component.visibility, + procedure_component.initial_value) + sym.datatype.procedure_components[procedure_name] = \ + new_procedure_component + # Or the procedure component has an initial value that is + # a Reference to the original kernel name, in which case + # we replace it with a Reference to the new kernel name. + elif (procedure_component.initial_value is not None + and (procedure_component.initial_value.name.lower() + == orig_kern_name.lower())): + # raise NotImplementedError("HERE") + new_kernel_symbol = RoutineSymbol(new_kern_name) + new_procedure_component = \ + StructureType.ComponentType(procedure_component + .name, + procedure_component + .datatype, + procedure_component\ + .visibility, + Reference( + new_kernel_symbol)) + sym.datatype.procedure_components[procedure_name] = \ + new_procedure_component @property def modified(self): diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index f2d929100e..a22a0caa7d 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -2013,7 +2013,6 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): extends_symbol = DataTypeSymbol(extends_name, StructureType(), extends_symbol_vis) - parent.symbol_table.add(extends_symbol) # Set it as the extended type of the new type. dtype.extends = extends_symbol else: 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 06ff91af34..c1300ac50d 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_select_type_test.py @@ -105,59 +105,6 @@ def test_type(fortran_reader, fortran_writer, tmpdir): assert "was_type_is" in if_blocks[1].annotations assert Compile(tmpdir).string_compiles(result) - code = ( - "module select_mod\n" - "use my_mod, only: my_class\n" - "contains\n" - "subroutine select_type(type_selector)\n" - " class(my_class), target :: type_selector\n" - " integer :: branch1, branch2\n" - " integer :: iinfo\n" - " real :: rinfo\n" - " SELECT TYPE (type_selector)\n" - " TYPE IS (INTEGER)\n" - " branch1 = 1\n" - " branch2 = 0\n" - " iinfo = type_selector\n" - " TYPE IS (REAL)\n" - " branch2 = 1\n" - " rinfo = type_selector\n" - " END SELECT\n" - "end subroutine\n" - "end module\n") - expected1 = "class(my_class), target :: type_selector" - expected2 = ( - " character(256) :: type_string\n" - " INTEGER, pointer :: ptr_INTEGER => null()\n" - " REAL, pointer :: ptr_REAL => null()\n\n" - " type_string = ''\n" - " SELECT TYPE(type_selector)\n" - " TYPE IS (INTEGER)\n" - " type_string = \"integer\"\n" - " ptr_INTEGER => type_selector\n" - " TYPE IS (REAL)\n" - " type_string = \"real\"\n" - " ptr_REAL => type_selector\n" - " END SELECT\n" - " if (type_string == 'integer') then\n" - " branch1 = 1\n" - " branch2 = 0\n" - " iinfo = ptr_INTEGER\n" - " else\n" - " if (type_string == 'real') then\n" - " branch2 = 1\n" - " rinfo = ptr_REAL\n" - " end if\n" - " end if\n") - psyir = fortran_reader.psyir_from_source(code) - result = fortran_writer(psyir).lower() - assert expected1.lower() in result - assert expected2.lower() in result - if_blocks = psyir.walk(IfBlock) - assert "was_type_is" in if_blocks[0].annotations - assert "was_type_is" in if_blocks[1].annotations - assert Compile(tmpdir).string_compiles(result) - def test_default(fortran_reader, fortran_writer, tmpdir): '''Check that the correct code is output when select type has a @@ -308,89 +255,6 @@ def test_class(fortran_reader, fortran_writer, tmpdir): assert "was_class_is" in if_blocks[2].annotations assert Compile(tmpdir).string_compiles(result) - code = ( - "module select_mod\n" - "use my_mod, only: my_class\n" - "contains\n" - "subroutine select_type(type)\n" - " class(my_class), pointer :: type\n" - " type type2\n" - " integer :: scalar\n" - " end type\n" - " type type3\n" - " integer :: field\n" - " end type\n" - " integer :: branch0, branch1, branch2, branch3\n" - " type(type2) :: my_type2\n" - " type(type3) :: my_type3\n" - " integer :: iinfo\n" - " SELECT TYPE (type)\n" - " CLASS IS(type2)\n" - " branch0 = 1\n" - " my_type2 = type\n" - " TYPE IS (INTEGER)\n" - " branch1 = 1\n" - " branch2 = 0\n" - " iinfo = type\n" - " CLASS IS(type3)\n" - " branch2 = 1\n" - " my_type3 = type\n" - " TYPE IS (REAL)\n" - " branch3 = 1\n" - " ! type not used here\n" - " END SELECT\n" - "end subroutine\n" - "end module\n") - expected1 = "class(my_class), pointer :: type" - expected2 = ( - " character(256) :: type_string\n" - " type(type2), pointer :: ptr_type2 => null()\n" - " INTEGER, pointer :: ptr_INTEGER => null()\n" - " type(type3), pointer :: ptr_type3 => null()\n" - " REAL, pointer :: ptr_REAL => null()\n\n" - " type_string = ''\n" - " SELECT TYPE(type)\n" - " CLASS IS (type2)\n" - " type_string = \"type2\"\n" - " ptr_type2 => type\n" - " TYPE IS (INTEGER)\n" - " type_string = \"integer\"\n" - " ptr_INTEGER => type\n" - " CLASS IS (type3)\n" - " type_string = \"type3\"\n" - " ptr_type3 => type\n" - " TYPE IS (REAL)\n" - " type_string = \"real\"\n" - " ptr_REAL => type\n" - " END SELECT\n" - " if (type_string == 'type2') then\n" - " branch0 = 1\n" - " my_type2 = ptr_type2\n" - " else\n" - " if (type_string == 'integer') then\n" - " branch1 = 1\n" - " branch2 = 0\n" - " iinfo = ptr_INTEGER\n" - " else\n" - " if (type_string == 'type3') then\n" - " branch2 = 1\n" - " my_type3 = ptr_type3\n" - " else\n" - " if (type_string == 'real') then\n" - " branch3 = 1\n" - " end if\n" - " end if\n" - " end if\n" - " end if\n").lower() - psyir = fortran_reader.psyir_from_source(code) - result = fortran_writer(psyir).lower() - assert expected1 in result - assert expected2 in result - if_blocks = psyir.walk(IfBlock) - assert "was_class_is" in if_blocks[0].annotations - assert "was_class_is" in if_blocks[2].annotations - assert Compile(tmpdir).string_compiles(result) - def test_class_with_codeblock(fortran_reader, fortran_writer, tmpdir): '''Check that the handler copes with the case where we have a CodeBlock From c35d98b93fd3e54154cfd1912e90d647bd2b03b8 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 2 Dec 2024 14:50:04 +0100 Subject: [PATCH 15/23] #2642 Avoid useless extends DataTypeSymbol visibility and procedure datatype. Format. --- src/psyclone/psyGen.py | 16 +++++++------- src/psyclone/psyir/frontend/fparser2.py | 28 ++++--------------------- 2 files changed, 12 insertions(+), 32 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index f9c4afcbc2..e5973982b4 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -60,7 +60,7 @@ UnresolvedType, ImportInterface, INTEGER_TYPE, RoutineSymbol, Symbol) -from psyclone.psyir.symbols.datatypes import (UnsupportedFortranType, +from psyclone.psyir.symbols.datatypes import (UnsupportedFortranType, StructureType) # The types of 'intent' that an argument to a Fortran subroutine @@ -1768,7 +1768,7 @@ def _rename_psyir(self, suffix): # go through its procedure components. elif isinstance(sym.datatype, StructureType): for procedure_name, procedure_component \ - in sym.datatype.procedure_components.items(): + in sym.datatype.procedure_components.items(): # Either the procedure component is of # UnsupportedFortranType, in which case we replace in its # whole declaration. @@ -1780,8 +1780,8 @@ def _rename_psyir(self, suffix): new_procedure_component = StructureType.ComponentType( procedure_component.name, UnsupportedFortranType(new_declaration, - procedure_component.\ - datatype.partial_datatype), + procedure_component. + datatype.partial_datatype), procedure_component.visibility, procedure_component.initial_value) sym.datatype.procedure_components[procedure_name] = \ @@ -1796,11 +1796,11 @@ def _rename_psyir(self, suffix): new_kernel_symbol = RoutineSymbol(new_kern_name) new_procedure_component = \ StructureType.ComponentType(procedure_component - .name, + .name, procedure_component - .datatype, - procedure_component\ - .visibility, + .datatype, + procedure_component + .visibility, Reference( new_kernel_symbol)) sym.datatype.procedure_components[procedure_name] = \ diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index a22a0caa7d..179588adf9 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -2002,17 +2002,8 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): # If it is not in the symbol table, create a new # DataTypeSymbol for it. else: - # The visibility of the symbol representing this - # derived type. - if extends_name in visibility_map: - extends_symbol_vis = visibility_map[ - extends_name] - else: - extends_symbol_vis = parent.symbol_table.\ - default_visibility extends_symbol = DataTypeSymbol(extends_name, - StructureType(), - extends_symbol_vis) + StructureType()) # Set it as the extended type of the new type. dtype.extends = extends_symbol else: @@ -2062,20 +2053,9 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): # Deal with the optional initial value. if procedure.items[4] is not None: initial_value_name = procedure.items[4].string - # Look it up in the symbol table and get its - # datatype or create it if it does not exist. - if initial_value_name in parent.symbol_table: - initial_value_symbol = parent.symbol_table.\ - lookup(initial_value_name) - if (isinstance(procedure_datatype, - UnsupportedFortranType) - and supported): - procedure_datatype = initial_value_symbol.\ - datatype - else: - initial_value_symbol = RoutineSymbol( - initial_value_name, - UnresolvedType()) + initial_value_symbol = RoutineSymbol( + initial_value_name, + UnresolvedType()) initial_value = Reference(initial_value_symbol) else: initial_value = None From 18679fa500aad925eeff1e7f805ecab2e8952a6f Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 2 Dec 2024 14:55:21 +0100 Subject: [PATCH 16/23] #2642 Add test about unkown parent type not being declared in the module. --- src/psyclone/tests/psyir/frontend/fparser2_test.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 0b96c4514f..5c0a230881 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -2606,6 +2606,8 @@ def test_structures(fortran_reader, fortran_writer): " type, extends(kernel_type), public :: my_type\n" " integer, public :: i = 1\n" " end type my_type\n" in result) + # Check that kernel_type is not declared in the module + assert ":: kernel_type" not in result # type that contains a procedure (StructureType) test_code = ( From 706bffca9bb242adf945fa15e10f269eddde3e7c Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 2 Dec 2024 17:45:25 +0100 Subject: [PATCH 17/23] #2642 Add psyGen tests and cleanup. --- src/psyclone/psyGen.py | 1 - src/psyclone/tests/psyGen_test.py | 95 ++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 3 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index e5973982b4..021a63e9d3 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1792,7 +1792,6 @@ def _rename_psyir(self, suffix): elif (procedure_component.initial_value is not None and (procedure_component.initial_value.name.lower() == orig_kern_name.lower())): - # raise NotImplementedError("HERE") new_kernel_symbol = RoutineSymbol(new_kern_name) new_procedure_component = \ StructureType.ComponentType(procedure_component diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index c5277faea7..6c8a38e945 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -66,10 +66,11 @@ GlobalSum, InvokeSchedule, BuiltIn) from psyclone.psyir.nodes import (Assignment, BinaryOperation, Container, Literal, Loop, Node, KernelSchedule, Call, - colored, Schedule) + colored, Schedule, Reference) from psyclone.psyir.symbols import (DataSymbol, RoutineSymbol, REAL_TYPE, ImportInterface, ContainerSymbol, Symbol, - INTEGER_TYPE, UnresolvedType, SymbolTable) + INTEGER_TYPE, UnresolvedType, SymbolTable, + StructureType, UnsupportedFortranType) from psyclone.tests.lfric_build import LFRicBuild from psyclone.tests.test_files import dummy_transformations from psyclone.tests.test_files.dummy_transformations import LocalTransformation @@ -663,6 +664,96 @@ def test_codedkern_lower_to_language_level(monkeypatch): assert csymbol.name == "testkern_mod" +def test_codedkern__rename_psyir(): + ''' Check that the CodedKern rename method renames the kernel in the PSyIR + tree. ''' + # pylint: disable=protected-access, too-many-statements + _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), + api="lfric") + psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + schedule = psy.invokes.invoke_list[0].schedule + kern = schedule.children[0].loop_body[0] + assert isinstance(kern, CodedKern) + + # Ensure everything is as expected before renaming + kern_schedule = kern.get_kernel_schedule() + assert kern_schedule.name == "testkern_code" + + container = kern_schedule.ancestor(Container) + assert isinstance(container, Container) + assert container.name == "testkern_mod" + container_table = container.symbol_table + testkern_type = container_table.lookup("testkern_type") + assert isinstance(testkern_type.datatype, StructureType) + assert len(testkern_type.datatype.procedure_components) == 1 + assert "code" in testkern_type.datatype.procedure_components + assert testkern_type.datatype.procedure_components["code"].name == "code" + assert isinstance(testkern_type.datatype.procedure_components["code"] + .datatype, + UnsupportedFortranType) + + # Rename the kernel in the PSyIR tree + kern._rename_psyir(suffix="_new") + kern_schedule = kern.get_kernel_schedule() + assert kern_schedule.name == "testkern_new_code" + container = kern_schedule.ancestor(Container) + assert container.name == "testkern_new_mod" + container_table = container.symbol_table + testkern_type = container_table.lookup("testkern_type") + assert isinstance(testkern_type.datatype.procedure_components["code"] + .datatype, + UnsupportedFortranType) + # In this case string substitution is applied in the .declaration string + assert ("code => testkern_new_code" + in testkern_type.datatype.procedure_components["code"].datatype + .declaration) + + # Make the procedure component of the structure type be of a supported type + _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), + api="lfric") + psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + schedule = psy.invokes.invoke_list[0].schedule + kern = schedule.children[0].loop_body[0] + assert isinstance(kern, CodedKern) + kern_schedule = kern.get_kernel_schedule() + container = kern_schedule.ancestor(Container) + container_table = container.symbol_table + testkern_type = container_table.lookup("testkern_type") + procedure_component = testkern_type.datatype.procedure_components["code"] + assert isinstance(procedure_component.datatype, UnsupportedFortranType) + # Replace the procedure component + new_procedure_component = StructureType.ComponentType( + procedure_component.name, + UnresolvedType(), + procedure_component.visibility, + # This initial_value part should be renamed + Reference(RoutineSymbol("testkern_code"))) + testkern_type.datatype.procedure_components["code"] \ + = new_procedure_component + # Rename + kern._rename_psyir(suffix="_new2") + assert (testkern_type.datatype.procedure_components["code"] + .initial_value.name == "testkern_new2_code") + + # Make the DataTypeSymbol be of UnsupportedFortranType + _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), + api="lfric") + psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + schedule = psy.invokes.invoke_list[0].schedule + kern = schedule.children[0].loop_body[0] + assert isinstance(kern, CodedKern) + container = kern.get_kernel_schedule().ancestor(Container) + container_table = container.symbol_table + testkern_type = container_table.lookup("testkern_type") + assert isinstance(testkern_type.datatype, StructureType) + # This is only to test string substitution so use a fake type string + testkern_type._datatype = UnsupportedFortranType("testkerntype [...] code " + "=> testkern_code [...]") + kern._rename_psyir(suffix="_new") + assert (testkern_type.datatype.declaration == "testkerntype [...] code => " + "testkern_new_code [...]") + + def test_kern_coloured_text(): '''Check that the coloured_name method of both CodedKern and BuiltIn return what we expect. From b72336b7d35e1616c4d8630588729ad36336e315 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Wed, 11 Dec 2024 17:20:46 +0100 Subject: [PATCH 18/23] #2642 Edits w.r.t. Andy's review --- .../lfric/kernel/lfric_kernel_metadata.py | 52 ++--- src/psyclone/psyGen.py | 3 +- .../psyad/domain/lfric/lfric_adjoint.py | 2 +- src/psyclone/psyir/backend/fortran.py | 7 +- src/psyclone/psyir/frontend/fparser2.py | 204 ++++++++++-------- .../psyir/symbols/data_type_symbol.py | 4 +- src/psyclone/psyir/symbols/datatypes.py | 33 +-- .../gocean/kernel/gocean_kern_psyir_test.py | 6 +- .../kernel/lfric_kernel_metadata_test.py | 20 +- src/psyclone/tests/psyGen_test.py | 71 +++++- .../psyad/domain/lfric/test_lfric_adjoint.py | 2 +- .../frontend/fparser2_derived_type_test.py | 2 +- .../tests/psyir/frontend/fparser2_test.py | 63 +++++- .../tests/psyir/symbols/datatype_test.py | 14 +- 14 files changed, 317 insertions(+), 166 deletions(-) diff --git a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py index acd2c58c16..e66ae1a00c 100644 --- a/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py +++ b/src/psyclone/domain/lfric/kernel/lfric_kernel_metadata.py @@ -44,40 +44,40 @@ from fparser.two.utils import walk, get_child from psyclone.domain.lfric import LFRicConstants -from psyclone.domain.lfric.kernel.columnwise_operator_arg_metadata import \ - ColumnwiseOperatorArgMetadata +from psyclone.domain.lfric.kernel.columnwise_operator_arg_metadata import ( + ColumnwiseOperatorArgMetadata) from psyclone.domain.lfric.kernel.field_arg_metadata import FieldArgMetadata -from psyclone.domain.lfric.kernel.field_vector_arg_metadata import \ - FieldVectorArgMetadata -from psyclone.domain.lfric.kernel.inter_grid_arg_metadata import \ - InterGridArgMetadata -from psyclone.domain.lfric.kernel.inter_grid_vector_arg_metadata import \ - InterGridVectorArgMetadata -from psyclone.domain.lfric.kernel.operator_arg_metadata import \ - OperatorArgMetadata +from psyclone.domain.lfric.kernel.field_vector_arg_metadata import ( + FieldVectorArgMetadata) +from psyclone.domain.lfric.kernel.inter_grid_arg_metadata import ( + InterGridArgMetadata) +from psyclone.domain.lfric.kernel.inter_grid_vector_arg_metadata import ( + InterGridVectorArgMetadata) +from psyclone.domain.lfric.kernel.operator_arg_metadata import ( + OperatorArgMetadata) from psyclone.domain.lfric.kernel.common_metadata import CommonMetadata -from psyclone.domain.lfric.kernel.common_meta_arg_metadata import \ - CommonMetaArgMetadata -from psyclone.domain.lfric.kernel.evaluator_targets_metadata import \ - EvaluatorTargetsMetadata -from psyclone.domain.lfric.kernel.meta_args_metadata import \ - MetaArgsMetadata -from psyclone.domain.lfric.kernel.meta_funcs_metadata import \ - MetaFuncsMetadata -from psyclone.domain.lfric.kernel.meta_mesh_metadata import \ - MetaMeshMetadata -from psyclone.domain.lfric.kernel.meta_ref_element_metadata import \ - MetaRefElementMetadata -from psyclone.domain.lfric.kernel.operates_on_metadata import \ - OperatesOnMetadata +from psyclone.domain.lfric.kernel.common_meta_arg_metadata import ( + CommonMetaArgMetadata) +from psyclone.domain.lfric.kernel.evaluator_targets_metadata import ( + EvaluatorTargetsMetadata) +from psyclone.domain.lfric.kernel.meta_args_metadata import ( + MetaArgsMetadata) +from psyclone.domain.lfric.kernel.meta_funcs_metadata import ( + MetaFuncsMetadata) +from psyclone.domain.lfric.kernel.meta_mesh_metadata import ( + MetaMeshMetadata) +from psyclone.domain.lfric.kernel.meta_ref_element_metadata import ( + MetaRefElementMetadata) +from psyclone.domain.lfric.kernel.operates_on_metadata import ( + OperatesOnMetadata) from psyclone.domain.lfric.kernel.scalar_arg_metadata import ScalarArgMetadata from psyclone.domain.lfric.kernel.shapes_metadata import ShapesMetadata from psyclone.errors import InternalError from psyclone.parse.utils import ParseError from psyclone.psyir.frontend.fortran import FortranReader from psyclone.psyir.backend.fortran import FortranWriter -from psyclone.psyir.symbols import DataTypeSymbol, UnsupportedFortranType, \ - StructureType +from psyclone.psyir.symbols import (DataTypeSymbol, UnsupportedFortranType, + StructureType) # pylint: disable=too-many-lines # pylint: disable=too-many-instance-attributes diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 021a63e9d3..6a7d068c6a 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1792,7 +1792,8 @@ def _rename_psyir(self, suffix): elif (procedure_component.initial_value is not None and (procedure_component.initial_value.name.lower() == orig_kern_name.lower())): - new_kernel_symbol = RoutineSymbol(new_kern_name) + new_kernel_symbol = container_table.lookup( + new_kern_name) new_procedure_component = \ StructureType.ComponentType(procedure_component .name, diff --git a/src/psyclone/psyad/domain/lfric/lfric_adjoint.py b/src/psyclone/psyad/domain/lfric/lfric_adjoint.py index 2b01015d4c..73afb7a162 100644 --- a/src/psyclone/psyad/domain/lfric/lfric_adjoint.py +++ b/src/psyclone/psyad/domain/lfric/lfric_adjoint.py @@ -52,7 +52,7 @@ from psyclone.psyad import AdjointVisitor from psyclone.psyad.domain.common import create_adjoint_name from psyclone.psyir.nodes import Routine -from psyclone.psyir.symbols import (ContainerSymbol, StructureType) +from psyclone.psyir.symbols import ContainerSymbol, StructureType from psyclone.psyir.symbols.symbol import ArgumentInterface, ImportInterface diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index 2fe8305177..a7650aab7a 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -1000,6 +1000,9 @@ def gen_decls(self, symbol_table, is_module_scope=False): # We ignore all symbols with a PreprocessorInterface if isinstance(sym.interface, PreprocessorInterface): all_symbols.remove(sym) + # We remove the '*' symbol used in 'class(*) :: var' + if sym.name == "*": + all_symbols.remove(sym) # If the symbol table contains any symbols with an # UnresolvedInterface interface (they are not explicitly @@ -1010,9 +1013,7 @@ def gen_decls(self, symbol_table, is_module_scope=False): unresolved_symbols = [] for sym in all_symbols[:]: if isinstance(sym.interface, UnresolvedInterface): - # Explicitly deal with '*' as in 'class(*) :: var' - if sym.name != "*": - unresolved_symbols.append(sym) + unresolved_symbols.append(sym) all_symbols.remove(sym) try: internal_interface_symbol = symbol_table.lookup( diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 179588adf9..313fa4bffe 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -1592,6 +1592,8 @@ def _process_type_spec(self, parent, type_spec): if isinstance(type_spec.items[1], Fortran2003.Type_Name): type_name = str(type_spec.items[1].string).lower() else: + # If we are processing a `class(*) :: var` declaration, the + # type name is not a Type_Name but a string. type_name = type_spec.items[1].lower() # Do we already have a Symbol for this derived type? type_symbol = _find_or_create_unresolved_symbol(parent, type_name) @@ -1986,85 +1988,36 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): derived_type_stmt = decl.children[0] type_attr_spec_list = walk(derived_type_stmt, Fortran2003.Type_Attr_Spec) - if type_attr_spec_list: - for type_attr_spec in type_attr_spec_list: - # Deal with 'EXTENDS(parent_type)'. - if type_attr_spec.items[0] == "EXTENDS": - extends_name = type_attr_spec.items[1].string - # Look up the extended type in the symbol table - # and specialise the symbol if needed. - if extends_name in parent.symbol_table: - extends_symbol = parent.symbol_table.lookup( - extends_name) - if type(extends_symbol) is Symbol: - extends_symbol.specialise(DataTypeSymbol) - extends_symbol.datatype = StructureType() - # If it is not in the symbol table, create a new - # DataTypeSymbol for it. - else: - extends_symbol = DataTypeSymbol(extends_name, - StructureType()) - # Set it as the extended type of the new type. - dtype.extends = extends_symbol + + for type_attr_spec in type_attr_spec_list: + # Deal with 'EXTENDS(parent_type)'. + if type_attr_spec.items[0] == "EXTENDS": + extends_name = type_attr_spec.items[1].string + # Look up the extended type in the symbol table + # and specialise the symbol if needed. + extends_symbol = parent.symbol_table.lookup( + extends_name, otherwise=None) + if extends_symbol: + # pylint: disable=unidiomatic-typecheck + if type(extends_symbol) is Symbol: + extends_symbol.specialise(DataTypeSymbol) + extends_symbol.datatype = StructureType() + # If it is not in the symbol table, create a new + # DataTypeSymbol for it. + # NOTE: this should *not* be added to the symbol table + # as it might be defined somewhere else if it was imported. else: - raise NotImplementedError("Derived-type definition " - "contains unsupported " - "attributes.") + extends_symbol = DataTypeSymbol(extends_name, + StructureType()) + # Set it as the extended type of the new type. + dtype.extends = extends_symbol + else: + raise NotImplementedError("Derived-type definition " + "contains unsupported " + "attributes.") # We support derived-type definitions with a CONTAINS section. - contains_blocks = walk(decl, Fortran2003.Type_Bound_Procedure_Part) - if contains_blocks: - # Get it. - contains = contains_blocks[0] - # Get all procedures in the CONTAINS section. - procedures = walk(contains, Fortran2003.Specific_Binding) - if len(procedures) > 0: - # Process each procedure. - for procedure in procedures: - supported = True - # We do not support interfaces. - if procedure.items[0] is not None: - supported = False - # We do not support 'pass', 'nopass', 'deferred', etc. - if procedure.items[1] is not None: - supported = False - - # Get the name, look it up in the symbol table and - # get its datatype or create it if it does not exist. - procedure_name = procedure.items[3].string - if procedure_name in parent.symbol_table and supported: - procedure_symbol = parent.symbol_table.\ - lookup(procedure_name) - procedure_datatype = procedure_symbol.datatype - else: - procedure_datatype = UnsupportedFortranType( - procedure.string, - None) - - # Get the visibility of the procedure. - procedure_vis = dtype_symbol_vis - if procedure.items[1] is not None: - access_spec = walk(procedure.items[1], - Fortran2003.Access_Spec) - if access_spec: - procedure_vis = _process_access_spec( - access_spec[0]) - - # Deal with the optional initial value. - if procedure.items[4] is not None: - initial_value_name = procedure.items[4].string - initial_value_symbol = RoutineSymbol( - initial_value_name, - UnresolvedType()) - initial_value = Reference(initial_value_symbol) - else: - initial_value = None - - # Add this procedure as a component of the derived type - dtype.add_procedure_component(procedure_name, - procedure_datatype, - procedure_vis, - initial_value) + self._process_derived_type_contains_block(parent, decl, tsymbol) # Re-use the existing code for processing symbols. This needs to # be able to find any symbols declared in an outer scope but @@ -2098,6 +2051,82 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): tsymbol.datatype = UnsupportedFortranType(str(decl)) tsymbol.interface = UnknownInterface() + def _process_derived_type_contains_block(self, parent, decl, tsymbol): + ''' + Process the supplied fparser2 parse tree for a the CONTAINS section of + a derived-type declaration to add any procedures to the DataTypeSymbol. + + :param parent: PSyIR node in which to insert the symbols found. + :type parent: :py:class:`psyclone.psyir.nodes.ScopingNode` + :param decl: fparser2 parse tree of declaration to process. + :type decl: :py:class:`fparser.two.Fortran2003.Type_Declaration_Stmt` + :param tsymbol: the DataTypeSymbol representing the derived-type. + :type tsymbol: :py:class:`psyclone.psyir.symbols.DataTypeSymbol` + + :returns: None + + ''' + + contains_blocks = walk(decl, Fortran2003.Type_Bound_Procedure_Part) + if contains_blocks: + # Get it. + contains = contains_blocks[0] + # Get all procedures in the CONTAINS section. + procedures = walk(contains, Fortran2003.Specific_Binding) + + # Process each procedure. + for procedure in procedures: + supported = True + # We do not support interfaces. + if procedure.items[0] is not None: + supported = False + # We do not support 'pass', 'nopass', 'deferred', etc. + if procedure.items[1] is not None: + supported = False + + # Get the name, look it up in the symbol table and + # get its datatype or create it if it does not exist. + procedure_name = procedure.items[3].string + procedure_symbol = parent.symbol_table.lookup( + procedure_name, + otherwise=None) + if procedure_symbol and supported: + procedure_datatype = procedure_symbol.datatype + else: + procedure_datatype = UnsupportedFortranType( + procedure.string, + None) + + # Get the visibility of the procedure. + procedure_vis = tsymbol.visibility + if procedure.items[1] is not None: + access_spec = walk(procedure.items[1], + Fortran2003.Access_Spec) + if access_spec: + procedure_vis = _process_access_spec( + access_spec[0]) + + # Deal with the optional initial value. + # This could be, e.g., `null` in `procedure, name => null()` or + # `testkern_code` in `procedure :: code => testkern_code` + if procedure.items[4] is not None: + initial_value_name = procedure.items[4].string + initial_value_symbol = parent.symbol_table.lookup( + initial_value_name, otherwise=None) + if not initial_value_symbol: + initial_value_symbol = RoutineSymbol( + initial_value_name, + UnresolvedType()) + initial_value = Reference(initial_value_symbol) + else: + initial_value = None + + # Add this procedure as a component of the derived type + tsymbol.datatype.add_procedure_component(procedure_name, + procedure_datatype, + procedure_vis, + initial_value) + def _get_partial_datatype(self, node, scope, visibility_map): '''Try to obtain partial datatype information from node by removing any unsupported properties in the declaration. @@ -3340,8 +3369,6 @@ def _add_target_attribute(var_name, table): ''' # pylint: disable=import-outside-toplevel - # Import here to avoid circular dependencies. - from psyclone.psyir.backend.fortran import FortranWriter try: symbol = table.lookup(var_name) @@ -3355,21 +3382,24 @@ def _add_target_attribute(var_name, table): f"be resolved and a DataSymbol") datatype = symbol.datatype - # If this is of UnsupportedFortranType, - # create Fortran text for the supplied datatype from the - # supplied UnsupportedFortranType text, then parse this into an - # fparser2 tree and store the fparser2 representation of the - # datatype in type_decl_stmt. if isinstance(datatype, UnsupportedFortranType): + # If this is of UnsupportedFortranType, + # create Fortran text for the supplied datatype from the + # supplied UnsupportedFortranType text, then parse this into an + # fparser2 tree and store the fparser2 representation of the + # datatype in type_decl_stmt. dummy_code = ( f"subroutine dummy()\n" f" {datatype.declaration}\n" f"end subroutine\n") - # If not, this is a supported derived type so we can use the - # backend to generate the Fortran text for the datatype. - # But we need to turn its datatype into an UnsupportedFortranType - # in order to add the 'TARGET' attribute to the declaration. else: + # If not, this is a supported derived type so we can use the + # backend to generate the Fortran text for the datatype. + # But we need to turn its datatype into an UnsupportedFortranType + # in order to add the 'TARGET' attribute to the declaration. + + # Import here to avoid circular dependencies. + from psyclone.psyir.backend.fortran import FortranWriter dummy_code = ( f"subroutine dummy()\n" f" {FortranWriter().gen_vardecl(symbol)}\n" diff --git a/src/psyclone/psyir/symbols/data_type_symbol.py b/src/psyclone/psyir/symbols/data_type_symbol.py index 5db283a45d..f2cf16f36f 100644 --- a/src/psyclone/psyir/symbols/data_type_symbol.py +++ b/src/psyclone/psyir/symbols/data_type_symbol.py @@ -52,6 +52,8 @@ class DataTypeSymbol(Symbol): :type visibility: :py:class:`psyclone.psyir.symbols.Symbol.Visibility` :param interface: the interface to this symbol. :type interface: :py:class:`psyclone.psyir.symbols.SymbolInterface` + :param bool is_class: whether this symbol is used in a 'class' or 'type' + declaration. ''' def __init__(self, name, datatype, @@ -125,7 +127,7 @@ def is_class(self): def is_class(self, value): ''' Setter for DataTypeSymbol is_class. - :param bool value: whether this DataTypeSymbol is a 'class' \ + :param bool value: whether this DataTypeSymbol is a 'class' declaration, i.e. not a 'type' one. :raises TypeError: if value is not a bool. diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index 078f330e9c..20465ceef3 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -956,8 +956,9 @@ def create(components, procedure_components=None, extends=None): :py:class:`psyclone.psyir.symbols.Symbol.Visibility, Optional[:py:class:`psyclone.psyir.symbols.DataNode`] ]]] - :param extends: the type that this new type extends. - :type extends: :py:class:`psyclone.psyir.symbols.DataTypeSymbol` + :param extends: the type that this new type extends (if any). + :type extends: Optional[ + :py:class:`psyclone.psyir.symbols.DataTypeSymbol`] :returns: the new type object. :rtype: :py:class:`psyclone.psyir.symbols.StructureType` @@ -995,7 +996,7 @@ def components(self): @property def procedure_components(self): ''' - :returns: Ordered dictionary of the procedure components of this type. + :returns: The procedure components of this type. :rtype: :py:class:`collections.OrderedDict` ''' return self._procedure_components @@ -1079,7 +1080,7 @@ def add_component(self, name, datatype, visibility, initial_value): def lookup_component(self, name): ''' - :returns: the ComponentType tuple describing the named member of this + :returns: the ComponentType describing the named member of this StructureType. :rtype: :py:class:`psyclone.psyir.symbols.StructureType.ComponentType` ''' @@ -1104,10 +1105,10 @@ def add_procedure_component(self, name, datatype, visibility, :raises TypeError: if any of the supplied values are of the wrong type. ''' - # This import must be placed here to avoid circular - # dependencies. + # These imports must be placed here to avoid circular dependencies. # pylint: disable=import-outside-toplevel - from psyclone.psyir.nodes import DataNode + from psyclone.psyir.symbols import RoutineSymbol + from psyclone.psyir.nodes import Reference if not isinstance(name, str): raise TypeError( f"The name of a procedure component of a StructureType must " @@ -1116,29 +1117,31 @@ def add_procedure_component(self, name, datatype, visibility, raise TypeError( f"The type of a procedure component of a StructureType must " f"be a 'DataType' but got '{type(datatype).__name__}'") - if isinstance(datatype, StructureType): - # A procedure component cannot be of type StructureType - raise TypeError( - f"The type of a procedure component of a StructureType cannot " - f"be a 'StructureType' but got '{type(datatype).__name__}'") if not isinstance(visibility, Symbol.Visibility): raise TypeError( f"The visibility of a procedure component of a StructureType " f"must be an instance of 'Symbol.Visibility' but got " f"'{type(visibility).__name__}'") if (initial_value is not None and - not isinstance(initial_value, DataNode)): + not isinstance(initial_value, Reference)): raise TypeError( f"The initial value of a procedure component of a " - f"StructureType must be None or an instance of 'DataNode', " + f"StructureType must be None or an instance of 'Reference' " f"but got '{type(initial_value).__name__}'.") + if isinstance(initial_value, Reference): + if not isinstance(initial_value.symbol, RoutineSymbol): + raise TypeError( + f"The initial value of a procedure component of a " + f"StructureType must be None or a Reference to a " + f"RoutineSymbol but got a Reference to a " + f"'{type(initial_value.symbol).__name__}'.") self._procedure_components[name] = self.ComponentType( name, datatype, visibility, initial_value) def lookup_procedure_component(self, name): ''' - :returns: the ComponentType tuple describing the named procedure + :returns: the ComponentType describing the named procedure member of this StructureType. :rtype: :py:class:`psyclone.psyir.symbols.StructureType.ComponentType` ''' diff --git a/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py b/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py index 74a92c99f1..91e8dbdd4d 100644 --- a/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py +++ b/src/psyclone/tests/domain/gocean/kernel/gocean_kern_psyir_test.py @@ -54,7 +54,6 @@ from psyclone.parse.utils import ParseError from psyclone.psyir.nodes import Container from psyclone.psyir.symbols import SymbolTable, REAL_TYPE, StructureType -from psyclone.psyir.backend.fortran import FortranWriter METADATA = ("TYPE, EXTENDS(kernel_type) :: compute_cu\n" " TYPE(go_arg), DIMENSION(4) :: meta_args = (/ &\n" @@ -393,7 +392,7 @@ def test_getproperty_error(): "EXTENDS(kernel_type)" in str(info.value)) -def test_getproperty(fortran_reader): +def test_getproperty(fortran_reader, fortran_writer): '''Test utility function that takes metadata in an fparser2 tree and returns the value associated with the supplied property name. @@ -403,10 +402,9 @@ def test_getproperty(fortran_reader): "compute_cu") datatype = datatype_symbol.datatype metadata = GOceanKernelMetadata() - # reader = FortranStringReader(datatype.declaration) assert isinstance(datatype, StructureType) - type_declaration = FortranWriter().gen_typedecl(datatype_symbol) + type_declaration = fortran_writer.gen_typedecl(datatype_symbol) reader = FortranStringReader(type_declaration) spec_part = Fortran2003.Derived_Type_Def(reader) diff --git a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py index 69a0df5636..602006e4da 100644 --- a/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py +++ b/src/psyclone/tests/domain/lfric/kernel/lfric_kernel_metadata_test.py @@ -51,7 +51,6 @@ from psyclone.parse.utils import ParseError from psyclone.psyir.symbols import DataTypeSymbol, REAL_TYPE, \ UnsupportedFortranType, StructureType -from psyclone.psyir.backend.fortran import FortranWriter # pylint: disable=too-many-statements @@ -1135,27 +1134,18 @@ def test_lower_to_psyir(): assert symbol.datatype.declaration == metadata.fortran_string() -def test_get_procedure_name_error(fortran_reader): +def test_get_procedure_name_error(fortran_reader, fortran_writer): '''Test that all the exceptions are raised as expected in the _get_procedure_name method. ''' - kernel_psyir = fortran_reader.psyir_from_source(PROGRAM.replace( - "procedure, nopass :: code => testkern_code", "")) - datatype_symbol = kernel_psyir.children[0].symbol_table.lookup( - "testkern_type") - assert isinstance(datatype_symbol, DataTypeSymbol) - structure_type = datatype_symbol.datatype - assert isinstance(structure_type, StructureType) - assert len(structure_type.procedure_components) == 0 - kernel_psyir = fortran_reader.psyir_from_source(PROGRAM) datatype_symbol = kernel_psyir.children[0].symbol_table.lookup( "testkern_type") datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() assert isinstance(datatype, StructureType) - type_declaration = FortranWriter().gen_typedecl(datatype_symbol) + type_declaration = fortran_writer.gen_typedecl(datatype_symbol) reader = FortranStringReader(type_declaration) spec_part = Fortran2003.Derived_Type_Def(reader) binding = spec_part.children[2] @@ -1172,7 +1162,7 @@ def test_get_procedure_name_error(fortran_reader): datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() assert isinstance(datatype, StructureType) - type_declaration = FortranWriter().gen_typedecl(datatype_symbol) + type_declaration = fortran_writer.gen_typedecl(datatype_symbol) reader = FortranStringReader(type_declaration) spec_part = Fortran2003.Derived_Type_Def(reader) with pytest.raises(ParseError) as info: @@ -1182,7 +1172,7 @@ def test_get_procedure_name_error(fortran_reader): in str(info.value)) -def test_get_procedure_name(fortran_reader): +def test_get_procedure_name(fortran_reader, fortran_writer): '''Test utility function that takes metadata in an fparser2 tree and returns the procedure metadata name, or None is there is no procedure name. @@ -1194,7 +1184,7 @@ def test_get_procedure_name(fortran_reader): datatype = datatype_symbol.datatype metadata = LFRicKernelMetadata() assert isinstance(datatype, StructureType) - type_declaration = FortranWriter().gen_typedecl(datatype_symbol) + type_declaration = fortran_writer.gen_typedecl(datatype_symbol) reader = FortranStringReader(type_declaration) spec_part = Fortran2003.Derived_Type_Def(reader) assert metadata._get_procedure_name(spec_part) == \ diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index 6c8a38e945..cee5169108 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -664,7 +664,7 @@ def test_codedkern_lower_to_language_level(monkeypatch): assert csymbol.name == "testkern_mod" -def test_codedkern__rename_psyir(): +def test_codedkern__rename_psyir_unsupported_procedure_datatype(): ''' Check that the CodedKern rename method renames the kernel in the PSyIR tree. ''' # pylint: disable=protected-access, too-many-statements @@ -708,6 +708,35 @@ def test_codedkern__rename_psyir(): in testkern_type.datatype.procedure_components["code"].datatype .declaration) + +def test_codedkern__rename_psyir_supported_procedure_datatype(): + ''' Check that the CodedKern rename method renames the kernel in the PSyIR + tree. ''' + # pylint: disable=protected-access, too-many-statements + _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), + api="lfric") + psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + schedule = psy.invokes.invoke_list[0].schedule + kern = schedule.children[0].loop_body[0] + assert isinstance(kern, CodedKern) + + # Ensure everything is as expected before renaming + kern_schedule = kern.get_kernel_schedule() + assert kern_schedule.name == "testkern_code" + + container = kern_schedule.ancestor(Container) + assert isinstance(container, Container) + assert container.name == "testkern_mod" + container_table = container.symbol_table + testkern_type = container_table.lookup("testkern_type") + assert isinstance(testkern_type.datatype, StructureType) + assert len(testkern_type.datatype.procedure_components) == 1 + assert "code" in testkern_type.datatype.procedure_components + assert testkern_type.datatype.procedure_components["code"].name == "code" + assert isinstance(testkern_type.datatype.procedure_components["code"] + .datatype, + UnsupportedFortranType) + # Make the procedure component of the structure type be of a supported type _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), api="lfric") @@ -722,12 +751,13 @@ def test_codedkern__rename_psyir(): procedure_component = testkern_type.datatype.procedure_components["code"] assert isinstance(procedure_component.datatype, UnsupportedFortranType) # Replace the procedure component + routine_symbol = container_table.lookup("testkern_code") new_procedure_component = StructureType.ComponentType( procedure_component.name, UnresolvedType(), procedure_component.visibility, # This initial_value part should be renamed - Reference(RoutineSymbol("testkern_code"))) + Reference(routine_symbol)) testkern_type.datatype.procedure_components["code"] \ = new_procedure_component # Rename @@ -735,6 +765,35 @@ def test_codedkern__rename_psyir(): assert (testkern_type.datatype.procedure_components["code"] .initial_value.name == "testkern_new2_code") + +def test_codedkern__rename_psyir_unsupported_datatypesymbol_datatype(): + ''' Check that the CodedKern rename method renames the kernel in the PSyIR + tree. ''' + # pylint: disable=protected-access, too-many-statements + _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), + api="lfric") + psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + schedule = psy.invokes.invoke_list[0].schedule + kern = schedule.children[0].loop_body[0] + assert isinstance(kern, CodedKern) + + # Ensure everything is as expected before renaming + kern_schedule = kern.get_kernel_schedule() + assert kern_schedule.name == "testkern_code" + + container = kern_schedule.ancestor(Container) + assert isinstance(container, Container) + assert container.name == "testkern_mod" + container_table = container.symbol_table + testkern_type = container_table.lookup("testkern_type") + assert isinstance(testkern_type.datatype, StructureType) + assert len(testkern_type.datatype.procedure_components) == 1 + assert "code" in testkern_type.datatype.procedure_components + assert testkern_type.datatype.procedure_components["code"].name == "code" + assert isinstance(testkern_type.datatype.procedure_components["code"] + .datatype, + UnsupportedFortranType) + # Make the DataTypeSymbol be of UnsupportedFortranType _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), api="lfric") @@ -747,10 +806,12 @@ def test_codedkern__rename_psyir(): testkern_type = container_table.lookup("testkern_type") assert isinstance(testkern_type.datatype, StructureType) # This is only to test string substitution so use a fake type string - testkern_type._datatype = UnsupportedFortranType("testkerntype [...] code " - "=> testkern_code [...]") + testkern_type._datatype = UnsupportedFortranType( + "type, extends(kernel_type) :: testkern_type [...] code " + "=> testkern_code [...]") kern._rename_psyir(suffix="_new") - assert (testkern_type.datatype.declaration == "testkerntype [...] code => " + assert (testkern_type.datatype.declaration == + "type, extends(kernel_type) :: testkern_type [...] code => " "testkern_new_code [...]") diff --git a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py index d1c9f3adc5..2183309139 100644 --- a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py +++ b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint.py @@ -276,8 +276,8 @@ def test_generate_lfric_adjoint_multi_precision( test_type_symbol = sym_table.lookup("test_type") datatype = test_type_symbol.datatype assert isinstance(datatype, StructureType) - datatype.procedure_components.clear() # Remove procedure metadata + datatype.procedure_components.clear() ad_psyir = generate_lfric_adjoint(psyir, ["field_1_w0", "field_2_w0"]) result = fortran_writer(ad_psyir) # Check that the metadata type name is updated. diff --git a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py index 88a98d4a93..e4e921327b 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_derived_type_test.py @@ -286,7 +286,7 @@ def test_derived_type_contains(): fparser2spec = Fortran2003.Specification_Part(reader) processor.process_declarations(fake_parent, fparser2spec.content, []) sym = symtab.lookup("my_type") - # It should still be a DataTypeSymbol but its type is unknown. + # It should still be a DataTypeSymbol and its type is known. assert isinstance(sym, DataTypeSymbol) assert isinstance(sym.datatype, StructureType) assert len(sym.datatype.components) == 2 diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 5c0a230881..44d41ebdd0 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -581,6 +581,17 @@ def test_process_declarations(): assert isinstance(ptr_sym.datatype, UnsupportedFortranType) assert isinstance(ptr_sym.initial_value, IntrinsicCall) + +@pytest.mark.usefixtures("f2008_parser") +def test_process_declarations_class_keyword(): + '''Test that process_declarations method of Fparser2Reader + converts the fparser2 declarations with the class keyword to symbols + in the provided parent Kernel Schedule. + + ''' + fake_parent = KernelSchedule.create("dummy_schedule") + processor = Fparser2Reader() + # Class declaration reader = FortranStringReader("class(my_type), intent(in) :: carg") # Set reader to free format (otherwise this is a comment in fixed format) @@ -596,6 +607,20 @@ def test_process_declarations(): assert isinstance(sym.interface, ArgumentInterface) assert sym.interface.access == ArgumentInterface.Access.READ + # class(*) case + reader = FortranStringReader("class(*), intent(in) :: urgh") + reader.set_format(FortranFormat(True, True)) + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + sym = fake_parent.symbol_table.lookup("urgh") + assert isinstance(sym, DataSymbol) + assert isinstance(sym.datatype, DataTypeSymbol) + assert sym.datatype.is_class is True + assert sym.name == "urgh" + assert sym.datatype.name == "*" + assert isinstance(sym.interface, ArgumentInterface) + assert sym.interface.access == ArgumentInterface.Access.READ + @pytest.mark.usefixtures("f2008_parser") @pytest.mark.parametrize("decln_text", @@ -2678,7 +2703,7 @@ def test_structures(fortran_reader, fortran_writer): " PROCEDURE, NOPASS :: test_code\n" "END TYPE test_type\n" in result) - # type that contains procedures + # public type that contains procedures test_code = ( "module test_mod\n" " use kernel_mod, only : parent_type\n" @@ -2713,6 +2738,42 @@ def test_structures(fortran_reader, fortran_writer): " procedure, public :: test_code_public\n" " end type test_type\n" in result) + # private type that contains procedures + # the `test_code` procedure should thus become private + test_code = ( + "module test_mod\n" + " use kernel_mod, only : parent_type\n" + " type, extends(parent_type), private :: test_type\n" + " integer :: i = 1\n" + " contains\n" + " procedure :: test_code\n" + " procedure, private :: test_code_private\n" + " procedure, public :: test_code_public\n" + " end type test_type\n" + " contains\n" + " subroutine test_code()\n" + " end subroutine\n" + " subroutine test_code_private()\n" + " end subroutine\n" + " subroutine test_code_public()\n" + " end subroutine\n" + "end module test_mod\n") + psyir = fortran_reader.psyir_from_source(test_code) + sym_table = psyir.children[0].symbol_table + symbol = sym_table.lookup("test_type") + assert isinstance(symbol, DataTypeSymbol) + assert isinstance(symbol.datatype, StructureType) + assert len(symbol.datatype.procedure_components) == 3 + result = fortran_writer(psyir) + assert ( + " type, extends(parent_type), private :: test_type\n" + " integer, public :: i = 1\n" + " contains\n" + " procedure, private :: test_code\n" + " procedure, private :: test_code_private\n" + " procedure, public :: test_code_public\n" + " end type test_type\n" in result) + # type that contains a procedure with an interface name test_code = ( "module test_mod\n" diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index cc9652faaf..f0be95a88f 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -945,10 +945,6 @@ def test_structure_type(): stype.add_procedure_component("proc2", None, None, None) assert ("The type of a procedure component of a StructureType must " "be a 'DataType' but got 'NoneType'" in str(err.value)) - with pytest.raises(TypeError) as err: - stype.add_procedure_component("proc3", StructureType(), None, None) - assert ("The type of a procedure component of a StructureType cannot " - "be a 'StructureType' but got 'StructureType'" in str(err.value)) with pytest.raises(TypeError) as err: stype.add_procedure_component("proc3", INTEGER_TYPE, None, None) assert ("The visibility of a procedure component of a StructureType must " @@ -958,8 +954,16 @@ def test_structure_type(): stype.add_procedure_component("proc4", INTEGER_TYPE, Symbol.Visibility.PUBLIC, "Hello") assert ("The initial value of a procedure component of a StructureType " - "must be None or an instance of 'DataNode', but got 'str'." + "must be None or an instance of 'Reference' but got 'str'." in str(err.value)) + with pytest.raises(TypeError) as err: + stype.add_procedure_component("proc5", INTEGER_TYPE, + Symbol.Visibility.PUBLIC, + Reference(DataSymbol("not_a_routine", + INTEGER_TYPE))) + assert ("The initial value of a procedure component of a StructureType " + "must be None or a Reference to a RoutineSymbol but got a " + "Reference to a 'DataSymbol'." in str(err.value)) def test_create_structuretype(): From 809ebf2458a39c1399ce82bb03ec7242ef6c48ae Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Wed, 11 Dec 2024 17:57:08 +0100 Subject: [PATCH 19/23] #2642 codecov --- src/psyclone/tests/psyGen_test.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index cee5169108..bcf1cf0c02 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -762,6 +762,17 @@ def test_codedkern__rename_psyir_supported_procedure_datatype(): = new_procedure_component # Rename kern._rename_psyir(suffix="_new2") + assert (testkern_type.datatype.procedure_components["code"] + .name == procedure_component.name) + assert isinstance(testkern_type.datatype.procedure_components["code"] + .datatype, UnresolvedType) + assert (testkern_type.datatype.procedure_components["code"] + .visibility == procedure_component.visibility) + assert isinstance(testkern_type.datatype.procedure_components["code"] + .initial_value, Reference) + assert (testkern_type.datatype.procedure_components["code"] + .initial_value.symbol + == container_table.lookup("testkern_new2_code")) assert (testkern_type.datatype.procedure_components["code"] .initial_value.name == "testkern_new2_code") From 6f1871086ff0eefd95a2921d1fecf8a620f0cc2b Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Wed, 11 Dec 2024 18:00:07 +0100 Subject: [PATCH 20/23] #2642 flake8... --- src/psyclone/tests/psyGen_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index bcf1cf0c02..e33768b2a4 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -765,7 +765,7 @@ def test_codedkern__rename_psyir_supported_procedure_datatype(): assert (testkern_type.datatype.procedure_components["code"] .name == procedure_component.name) assert isinstance(testkern_type.datatype.procedure_components["code"] - .datatype, UnresolvedType) + .datatype, UnresolvedType) assert (testkern_type.datatype.procedure_components["code"] .visibility == procedure_component.visibility) assert isinstance(testkern_type.datatype.procedure_components["code"] From 9419a05adb50ac75be60315e64ca539ea5a320a4 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Wed, 11 Dec 2024 19:55:20 +0100 Subject: [PATCH 21/23] #2642 Refactor, codecov --- src/psyclone/psyGen.py | 46 ++---------- src/psyclone/psyir/symbols/datatypes.py | 70 ++++++++++++++++++- src/psyclone/tests/psyGen_test.py | 5 ++ .../tests/psyir/symbols/datatype_test.py | 47 +++++++++++++ 4 files changed, 128 insertions(+), 40 deletions(-) diff --git a/src/psyclone/psyGen.py b/src/psyclone/psyGen.py index 6a7d068c6a..7e561bba95 100644 --- a/src/psyclone/psyGen.py +++ b/src/psyclone/psyGen.py @@ -1765,46 +1765,14 @@ def _rename_psyir(self, suffix): partial_datatype=sym.datatype.partial_datatype) # pylint: enable=protected-access # Or the DataTypeSymbol is a StructureType, in which case we - # go through its procedure components. + # replace the "code" procedure component initial value. elif isinstance(sym.datatype, StructureType): - for procedure_name, procedure_component \ - in sym.datatype.procedure_components.items(): - # Either the procedure component is of - # UnsupportedFortranType, in which case we replace in its - # whole declaration. - if isinstance(procedure_component.datatype, - UnsupportedFortranType): - new_declaration = \ - procedure_component.datatype.declaration.replace( - orig_kern_name, new_kern_name) - new_procedure_component = StructureType.ComponentType( - procedure_component.name, - UnsupportedFortranType(new_declaration, - procedure_component. - datatype.partial_datatype), - procedure_component.visibility, - procedure_component.initial_value) - sym.datatype.procedure_components[procedure_name] = \ - new_procedure_component - # Or the procedure component has an initial value that is - # a Reference to the original kernel name, in which case - # we replace it with a Reference to the new kernel name. - elif (procedure_component.initial_value is not None - and (procedure_component.initial_value.name.lower() - == orig_kern_name.lower())): - new_kernel_symbol = container_table.lookup( - new_kern_name) - new_procedure_component = \ - StructureType.ComponentType(procedure_component - .name, - procedure_component - .datatype, - procedure_component - .visibility, - Reference( - new_kernel_symbol)) - sym.datatype.procedure_components[procedure_name] = \ - new_procedure_component + new_kernel_symbol = container_table.lookup(new_kern_name) + new_initial_value = Reference(new_kernel_symbol) + sym.datatype.replace_procedure_component_initial_value( + orig_kern_name, + new_initial_value + ) @property def modified(self): diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index 20465ceef3..557354a006 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -1087,7 +1087,7 @@ def lookup_component(self, name): return self._components[name] def add_procedure_component(self, name, datatype, visibility, - initial_value): + initial_value=None): ''' Create a procedure component with the supplied attributes and add it to this StructureType. @@ -1147,6 +1147,74 @@ def lookup_procedure_component(self, name): ''' return self._procedure_components[name] + def replace_procedure_component_initial_value(self, old_value_name, + new_value): + ''' + Replace the initial value of the procedure component with + "old_value_name" as initial value with the supplied new value. + + :param str old_value_name: the name of the initial value to replace. + :param new_value: the new initial value for the procedure component. + :type new_value: :py:class:`psyclone.psyir.nodes.Reference` + + :raises TypeError: if the name is not a string. + :raises TypeError: if the new value is not a Reference to a + RoutineSymbol. + + :returns: None + + ''' + # pylint: disable=import-outside-toplevel + # These imports must be placed here to avoid circular dependencies. + from psyclone.psyir.symbols import RoutineSymbol + from psyclone.psyir.nodes import Reference + + if not isinstance(old_value_name, str): + raise TypeError( + "The name of the procedure component to replace must be a " + f"string but got '{type(old_value_name).__name__}'.") + if not isinstance(new_value, Reference): + raise TypeError( + "The new value for the procedure component must be a " + f"Reference but got '{type(new_value).__name__}'.") + if not isinstance(new_value.symbol, RoutineSymbol): + raise TypeError( + "The new value for the procedure component must be a " + f"Reference to a RoutineSymbol but got a Reference to a " + f"'{type(new_value.symbol).__name__}'.") + + for name, procedure_component in self._procedure_components.items(): + if isinstance(procedure_component.datatype, + UnsupportedFortranType): + # Either the procedure component is of UnsupportedFortranType, + # in which case we replace in its whole string declaration. + if old_value_name.lower() not in procedure_component.datatype\ + .declaration.lower(): + continue + new_declaration = procedure_component.datatype\ + .declaration.replace(old_value_name, new_value.name) + new_datatype = UnsupportedFortranType( + new_declaration, + procedure_component.datatype.partial_datatype) + self._procedure_components[name] = self.ComponentType( + name, + new_datatype, + procedure_component.visibility, + new_value) + return + + # Or it is enough to replace the initial value. + if (not procedure_component.initial_value + or procedure_component.initial_value.name.lower() + != old_value_name.lower()): + continue + self._procedure_components[name] = self.ComponentType( + name, + procedure_component.datatype, + procedure_component.visibility, + new_value) + return + def __eq__(self, other): ''' :param Any other: the object to check equality to. diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index e33768b2a4..f9fb313aa6 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -760,6 +760,11 @@ def test_codedkern__rename_psyir_supported_procedure_datatype(): Reference(routine_symbol)) testkern_type.datatype.procedure_components["code"] \ = new_procedure_component + # Check that the procedure component has been replaced + assert (testkern_type.datatype.procedure_components["code"] + .initial_value is not None) + assert (testkern_type.datatype.procedure_components["code"] + .initial_value.name.lower() == "testkern_code") # Rename kern._rename_psyir(suffix="_new2") assert (testkern_type.datatype.procedure_components["code"] diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index f0be95a88f..21fdbefae2 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -1113,3 +1113,50 @@ def test_structuretype_extends(): structure_type.extends = "invalid" assert ("The type that a StructureType extends must be a " "DataTypeSymbol but got 'str'." in str(err.value)) + + +def test_replace_procedure_component_initial_value(): + '''Test the replace_procedure_component_initial_value method of + StructureType.''' + stype = StructureType() + with pytest.raises(TypeError) as excinfo: + stype.replace_procedure_component_initial_value(123, None) + assert ("The name of the procedure component to replace must be a string" + in str(excinfo.value)) + + stype = StructureType() + with pytest.raises(TypeError) as excinfo: + stype.replace_procedure_component_initial_value("old_value", 123) + assert ("The new value for the procedure component must be a Reference" + in str(excinfo.value)) + + stype = StructureType() + symbol = Symbol("dummy") + ref = Reference(symbol) + with pytest.raises(TypeError) as excinfo: + stype.replace_procedure_component_initial_value("old_value", ref) + assert ("The new value for the procedure component must be a Reference to " + "a RoutineSymbol" in str(excinfo.value)) + + # UnsupportedFortranType + stype = StructureType() + unsupported_type = UnsupportedFortranType("procedure, code => routine", + None) + stype.add_procedure_component("proc", unsupported_type, + Symbol.Visibility.PUBLIC) + new_routine_symbol = RoutineSymbol("new_routine") + new_ref = Reference(new_routine_symbol) + stype.replace_procedure_component_initial_value("routine", new_ref) + assert (stype.lookup_procedure_component("proc").datatype.declaration + == "procedure, code => new_routine") + + # Other datatype + stype = StructureType() + routine_symbol = RoutineSymbol("routine") + old_ref = Reference(routine_symbol) + stype.add_procedure_component("proc", UnresolvedType(), + Symbol.Visibility.PUBLIC, old_ref) + new_routine_symbol = RoutineSymbol("new_routine") + new_ref = Reference(new_routine_symbol) + stype.replace_procedure_component_initial_value("routine", new_ref) + assert stype.lookup_procedure_component("proc").initial_value == new_ref From a862a218da9bffa7d915f467f3366d856cf8dbbb Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Wed, 18 Dec 2024 16:06:05 +0100 Subject: [PATCH 22/23] #2642 Fix parent type to UnresolvedInterface and add to symbol table. --- src/psyclone/psyir/frontend/fparser2.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 58185e40da..efcdeee182 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -2025,13 +2025,17 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): if type(extends_symbol) is Symbol: extends_symbol.specialise(DataTypeSymbol) extends_symbol.datatype = StructureType() - # If it is not in the symbol table, create a new - # DataTypeSymbol for it. - # NOTE: this should *not* be added to the symbol table - # as it might be defined somewhere else if it was imported. else: - extends_symbol = DataTypeSymbol(extends_name, - StructureType()) + # If it is not in the symbol table, create a new + # DataTypeSymbol with an UnresolvedInterface for it, + # meaning that we know it exists somewhere but we don't + # know how it is brought into scope, and add it to the + # symbol table. + extends_symbol = DataTypeSymbol( + extends_name, + StructureType(), + interface=UnresolvedInterface()) + parent.symbol_table.add(extends_symbol) # Set it as the extended type of the new type. dtype.extends = extends_symbol else: From cbc5857fbf8844cc2bb0a215a7e91104a71f2bd7 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Thu, 19 Dec 2024 13:55:59 +0100 Subject: [PATCH 23/23] #2642 Edits wrt Andy's second review (all except `class(*)`) --- src/psyclone/psyir/frontend/fparser2.py | 128 ++++++++--------- src/psyclone/psyir/symbols/datatypes.py | 6 +- src/psyclone/tests/psyGen_test.py | 25 ++-- .../lfric/test_lfric_adjoint_harness.py | 3 +- .../psyir/backend/fortran_gen_decls_test.py | 70 +++++++++- .../tests/psyir/backend/fortran_test.py | 68 --------- .../tests/psyir/frontend/fparser2_test.py | 130 ++++++++++++++++-- .../tests/psyir/symbols/datatype_test.py | 38 ++++- 8 files changed, 303 insertions(+), 165 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index efcdeee182..94a306808d 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -1957,6 +1957,7 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): # Look for any private-components-stmt (R447) within the type # decln. In the absence of this, the default visibility of type # components is public. + # This only concerns data components, i.e. not procedure ones. private_stmts = walk(decl, Fortran2003.Private_Components_Stmt) if private_stmts: default_compt_visibility = Symbol.Visibility.PRIVATE @@ -2090,69 +2091,73 @@ def _process_derived_type_contains_block(self, parent, decl, tsymbol): :param tsymbol: the DataTypeSymbol representing the derived-type. :type tsymbol: :py:class:`psyclone.psyir.symbols.DataTypeSymbol` - :returns: None - ''' - contains_blocks = walk(decl, Fortran2003.Type_Bound_Procedure_Part) - if contains_blocks: - # Get it. - contains = contains_blocks[0] - # Get all procedures in the CONTAINS section. - procedures = walk(contains, Fortran2003.Specific_Binding) - - # Process each procedure. - for procedure in procedures: - supported = True - # We do not support interfaces. - if procedure.items[0] is not None: - supported = False - # We do not support 'pass', 'nopass', 'deferred', etc. - if procedure.items[1] is not None: - supported = False - - # Get the name, look it up in the symbol table and - # get its datatype or create it if it does not exist. - procedure_name = procedure.items[3].string - procedure_symbol = parent.symbol_table.lookup( - procedure_name, - otherwise=None) - if procedure_symbol and supported: - procedure_datatype = procedure_symbol.datatype - else: - procedure_datatype = UnsupportedFortranType( - procedure.string, - None) - - # Get the visibility of the procedure. - procedure_vis = tsymbol.visibility - if procedure.items[1] is not None: - access_spec = walk(procedure.items[1], - Fortran2003.Access_Spec) - if access_spec: - procedure_vis = _process_access_spec( - access_spec[0]) - - # Deal with the optional initial value. - # This could be, e.g., `null` in `procedure, name => null()` or - # `testkern_code` in `procedure :: code => testkern_code` - if procedure.items[4] is not None: - initial_value_name = procedure.items[4].string - initial_value_symbol = parent.symbol_table.lookup( - initial_value_name, otherwise=None) - if not initial_value_symbol: - initial_value_symbol = RoutineSymbol( - initial_value_name, - UnresolvedType()) - initial_value = Reference(initial_value_symbol) - else: - initial_value = None + if not contains_blocks: + return + + # Get it. + contains = contains_blocks[0] - # Add this procedure as a component of the derived type - tsymbol.datatype.add_procedure_component(procedure_name, - procedure_datatype, - procedure_vis, - initial_value) + # Look for any binding-private-stmt (R449) within the + # Specific_Binding part of decln. In the absence of this, the default + # visibility of procedure components is public. + # This only concerns procedure components, i.e. not data ones. + private_stmts = walk(contains, Fortran2003.Binding_Private_Stmt) + if private_stmts: + default_proc_visibility = Symbol.Visibility.PRIVATE + else: + default_proc_visibility = Symbol.Visibility.PUBLIC + + # Get all procedures in the CONTAINS section. + procedures = walk(contains, Fortran2003.Specific_Binding) + + # Process each procedure. + for procedure in procedures: + supported = True + # We do not support interfaces. + if procedure.items[0] is not None: + supported = False + # We do not support 'pass', 'nopass', 'deferred', etc. + if procedure.items[1] is not None: + supported = False + + # Get the name, look it up in the symbol table and + # get its datatype or create it if it does not exist. + procedure_name = procedure.items[3].string + procedure_symbol = parent.symbol_table.lookup(procedure_name, + otherwise=None) + if procedure_symbol and supported: + procedure_datatype = procedure_symbol.datatype + else: + procedure_datatype = UnsupportedFortranType(procedure.string, + None) + + # Get the visibility of the procedure. + procedure_vis = default_proc_visibility + if procedure.items[1] is not None: + access_spec = walk(procedure.items[1], + Fortran2003.Access_Spec) + if access_spec: + procedure_vis = _process_access_spec(access_spec[0]) + + # Deal with the optional initial value. + # This could be, e.g., `null` in `procedure, name => null()` or + # `testkern_code` in `procedure :: code => testkern_code` + if procedure.items[4] is not None: + initial_value_name = procedure.items[4].string + initial_value_symbol = parent.symbol_table.find_or_create( + initial_value_name, allow_renaming=False, + symbol_type=RoutineSymbol, datatype=UnresolvedType()) + initial_value = Reference(initial_value_symbol) + else: + initial_value = None + + # Add this procedure as a component of the derived type + tsymbol.datatype.add_procedure_component(procedure_name, + procedure_datatype, + procedure_vis, + initial_value) def _get_partial_datatype(self, node, scope, visibility_map): '''Try to obtain partial datatype information from node by removing @@ -3395,8 +3400,6 @@ def _add_target_attribute(var_name, table): (e.g. a routine argument or an imported symbol). ''' - # pylint: disable=import-outside-toplevel - try: symbol = table.lookup(var_name) except KeyError as err: @@ -3426,6 +3429,7 @@ def _add_target_attribute(var_name, table): # in order to add the 'TARGET' attribute to the declaration. # Import here to avoid circular dependencies. + # pylint: disable-next=import-outside-toplevel from psyclone.psyir.backend.fortran import FortranWriter dummy_code = ( f"subroutine dummy()\n" diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index 557354a006..3ffdfb026c 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -1150,7 +1150,7 @@ def lookup_procedure_component(self, name): def replace_procedure_component_initial_value(self, old_value_name, new_value): ''' - Replace the initial value of the procedure component with + Replace the initial values of the procedure components with "old_value_name" as initial value with the supplied new value. :param str old_value_name: the name of the initial value to replace. @@ -1161,8 +1161,6 @@ def replace_procedure_component_initial_value(self, old_value_name, :raises TypeError: if the new value is not a Reference to a RoutineSymbol. - :returns: None - ''' # pylint: disable=import-outside-toplevel # These imports must be placed here to avoid circular dependencies. @@ -1201,7 +1199,6 @@ def replace_procedure_component_initial_value(self, old_value_name, new_datatype, procedure_component.visibility, new_value) - return # Or it is enough to replace the initial value. if (not procedure_component.initial_value @@ -1213,7 +1210,6 @@ def replace_procedure_component_initial_value(self, old_value_name, procedure_component.datatype, procedure_component.visibility, new_value) - return def __eq__(self, other): ''' diff --git a/src/psyclone/tests/psyGen_test.py b/src/psyclone/tests/psyGen_test.py index f9fb313aa6..4cb9206ac0 100644 --- a/src/psyclone/tests/psyGen_test.py +++ b/src/psyclone/tests/psyGen_test.py @@ -668,9 +668,8 @@ def test_codedkern__rename_psyir_unsupported_procedure_datatype(): ''' Check that the CodedKern rename method renames the kernel in the PSyIR tree. ''' # pylint: disable=protected-access, too-many-statements - _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), - api="lfric") - psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + psy, _ = get_invoke("1_single_invoke.f90", "lfric", + idx=0, dist_mem=False) schedule = psy.invokes.invoke_list[0].schedule kern = schedule.children[0].loop_body[0] assert isinstance(kern, CodedKern) @@ -713,9 +712,8 @@ def test_codedkern__rename_psyir_supported_procedure_datatype(): ''' Check that the CodedKern rename method renames the kernel in the PSyIR tree. ''' # pylint: disable=protected-access, too-many-statements - _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), - api="lfric") - psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + psy, _ = get_invoke("1_single_invoke.f90", "lfric", + idx=0, dist_mem=False) schedule = psy.invokes.invoke_list[0].schedule kern = schedule.children[0].loop_body[0] assert isinstance(kern, CodedKern) @@ -738,9 +736,8 @@ def test_codedkern__rename_psyir_supported_procedure_datatype(): UnsupportedFortranType) # Make the procedure component of the structure type be of a supported type - _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), - api="lfric") - psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + psy, _ = get_invoke("1_single_invoke.f90", "lfric", + idx=0, dist_mem=False) schedule = psy.invokes.invoke_list[0].schedule kern = schedule.children[0].loop_body[0] assert isinstance(kern, CodedKern) @@ -786,9 +783,8 @@ def test_codedkern__rename_psyir_unsupported_datatypesymbol_datatype(): ''' Check that the CodedKern rename method renames the kernel in the PSyIR tree. ''' # pylint: disable=protected-access, too-many-statements - _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), - api="lfric") - psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + psy, _ = get_invoke("1_single_invoke.f90", "lfric", + idx=0, dist_mem=False) schedule = psy.invokes.invoke_list[0].schedule kern = schedule.children[0].loop_body[0] assert isinstance(kern, CodedKern) @@ -811,9 +807,8 @@ def test_codedkern__rename_psyir_unsupported_datatypesymbol_datatype(): UnsupportedFortranType) # Make the DataTypeSymbol be of UnsupportedFortranType - _, invoke_info = parse(os.path.join(BASE_PATH, "1_single_invoke.f90"), - api="lfric") - psy = PSyFactory("lfric", distributed_memory=False).create(invoke_info) + psy, _ = get_invoke("1_single_invoke.f90", "lfric", + idx=0, dist_mem=False) schedule = psy.invokes.invoke_list[0].schedule kern = schedule.children[0].loop_body[0] assert isinstance(kern, CodedKern) diff --git a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py index dc284ac859..90e986ecfb 100644 --- a/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py +++ b/src/psyclone/tests/psyad/domain/lfric/test_lfric_adjoint_harness.py @@ -546,6 +546,7 @@ def test_generate_lfric_adjoint_harness_invalid_code(fortran_reader): " use kinds_mod, only: i_def, r_def\n" " use kernel_mod, only: kernel_type, arg_type, gh_field, gh_real, " "gh_write, w3, cell_column\n" + " use argument_mod, only: func_type, gh_quadrature_xyoz\n" " type, extends(kernel_type) :: testkern_type\n" " type(arg_type), dimension(2) :: meta_args = & \n" " (/ arg_type(gh_scalar, gh_real, gh_read), & \n" @@ -619,8 +620,6 @@ def test_generate_lfric_adjoint_harness(fortran_reader, fortran_writer): " inner2 = inner2 + field_field_input_inner_prod\n" in gen) -@pytest.mark.xfail(reason="func_type and gh_quadrature_xyoz are neither " - "declared nor imported in the TL code.") def test_generate_lfric_adj_test_quadrature(fortran_reader): '''Check that input copies of quadrature arguments are not created.''' # Change the metadata so that it requires quadrature. 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 3961dd42ba..0d597b8a32 100644 --- a/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_gen_decls_test.py @@ -47,7 +47,7 @@ RoutineSymbol, ScalarType, Symbol, SymbolTable, UnresolvedType, StructureType, ImportInterface, UnresolvedInterface, ArgumentInterface, INTEGER_TYPE, REAL_TYPE, StaticInterface, PreprocessorInterface, - CHARACTER_TYPE) + CHARACTER_TYPE, UnsupportedFortranType) def test_gen_param_decls_dependencies(fortran_writer): @@ -364,3 +364,71 @@ def test_gen_interfacedecl(fortran_writer): procedure :: sub1 end interface subx ''') + + +def test_fw_gen_proceduredecl(fortran_writer): + '''Test the FortranWriter class gen_proceduredecl method produces the + expected declarations and raises the expected exceptions. + ''' + with pytest.raises(VisitorError) as err: + fortran_writer.gen_proceduredecl(None) + assert ("gen_proceduredecl() expects a 'DataSymbol' or " + "'StructureType.ComponentType' as its first " + "argument but got 'NoneType'" in str(err.value)) + + # A DataSymbol of UnupportedFortranType + symbol = DataSymbol("my_sub", UnsupportedFortranType( + "procedure, private :: my_unsupported_procedure")) + assert (fortran_writer.gen_proceduredecl(symbol) == + "procedure, private :: my_unsupported_procedure\n") + + # A StructureType.ComponentType with 'public' visibility and no initial + # value + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PUBLIC, None) + assert (fortran_writer.gen_proceduredecl(dtype) == + "procedure, public :: my_procedure\n") + + # A StructureType.ComponentType with 'public' visibility and an initial + # value + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PUBLIC, + Reference(RoutineSymbol("other", + REAL_TYPE))) + assert (fortran_writer.gen_proceduredecl(dtype) == + "procedure, public :: my_procedure => other\n") + + # A StructureType.ComponentType with 'private' visibility and no initial + # value + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PRIVATE, None) + assert fortran_writer.gen_proceduredecl(dtype) == ( + "procedure, private :: my_procedure\n") + + # A StructureType.ComponentType with 'private' visibility and an initial + # value + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PRIVATE, + Reference(RoutineSymbol("other", + REAL_TYPE))) + assert fortran_writer.gen_proceduredecl(dtype) == ( + "procedure, private :: my_procedure => other\n") + + # Check that visibility is not included in the output if include_visibility + # is False + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PUBLIC, None) + assert (fortran_writer.gen_proceduredecl(dtype, include_visibility=False) + == ("procedure :: my_procedure\n")) + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + Symbol.Visibility.PRIVATE, None) + assert (fortran_writer.gen_proceduredecl(dtype, include_visibility=False) + == ("procedure :: my_procedure\n")) + + # Check exception is raised if visibility is not 'public' or 'private' + dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, + "wrong", None) + with pytest.raises(InternalError) as err: + fortran_writer.gen_proceduredecl(dtype) + assert ("A Symbol must be either public or private but symbol " + "'my_procedure' has visibility 'wrong'" in str(err.value)) diff --git a/src/psyclone/tests/psyir/backend/fortran_test.py b/src/psyclone/tests/psyir/backend/fortran_test.py index 52a8b45202..d4214a6f5e 100644 --- a/src/psyclone/tests/psyir/backend/fortran_test.py +++ b/src/psyclone/tests/psyir/backend/fortran_test.py @@ -760,74 +760,6 @@ def test_fw_gen_vardecl_visibility(fortran_writer): "end type var\n") -def test_fw_gen_proceduredecl(fortran_writer): - '''Test the FortranWriter class gen_proceduredecl method produces the - expected declarations and raises the expected exceptions. - ''' - with pytest.raises(VisitorError) as err: - fortran_writer.gen_proceduredecl(None) - assert ("gen_proceduredecl() expects a 'DataSymbol' or " - "'StructureType.ComponentType' as its first " - "argument but got 'NoneType'" in str(err.value)) - - # A DataSymbol of UnupportedFortranType - symbol = DataSymbol("my_sub", UnsupportedFortranType( - "procedure, private :: my_unsupported_procedure")) - assert (fortran_writer.gen_proceduredecl(symbol) == - "procedure, private :: my_unsupported_procedure\n") - - # A StructureType.ComponentType with 'public' visibility and no initial - # value - dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, - Symbol.Visibility.PUBLIC, None) - assert (fortran_writer.gen_proceduredecl(dtype) == - "procedure, public :: my_procedure\n") - - # A StructureType.ComponentType with 'public' visibility and an initial - # value - dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, - Symbol.Visibility.PUBLIC, - Reference(RoutineSymbol("other", - REAL_TYPE))) - assert (fortran_writer.gen_proceduredecl(dtype) == - "procedure, public :: my_procedure => other\n") - - # A StructureType.ComponentType with 'private' visibility and no initial - # value - dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, - Symbol.Visibility.PRIVATE, None) - assert fortran_writer.gen_proceduredecl(dtype) == ( - "procedure, private :: my_procedure\n") - - # A StructureType.ComponentType with 'private' visibility and an initial - # value - dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, - Symbol.Visibility.PRIVATE, - Reference(RoutineSymbol("other", - REAL_TYPE))) - assert fortran_writer.gen_proceduredecl(dtype) == ( - "procedure, private :: my_procedure => other\n") - - # Check that visibility is not included in the output if include_visibility - # is False - dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, - Symbol.Visibility.PUBLIC, None) - assert (fortran_writer.gen_proceduredecl(dtype, include_visibility=False) - == ("procedure :: my_procedure\n")) - dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, - Symbol.Visibility.PRIVATE, None) - assert (fortran_writer.gen_proceduredecl(dtype, include_visibility=False) - == ("procedure :: my_procedure\n")) - - # Check exception is raised if visibility is not 'public' or 'private' - dtype = StructureType.ComponentType("my_procedure", REAL_TYPE, - "wrong", None) - with pytest.raises(InternalError) as err: - fortran_writer.gen_proceduredecl(dtype) - assert ("A Symbol must be either public or private but symbol " - "'my_procedure' has visibility 'wrong'" in str(err.value)) - - def test_gen_default_access_stmt(fortran_writer): ''' Tests for the gen_default_access_stmt method of FortranWriter. diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 452e6c6d46..925dd659f4 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -2661,14 +2661,11 @@ def raise_value_error(_1, _2): assert "error to propagate" in str(err.value) -def test_structures(fortran_reader, fortran_writer): +def test_structures_component_init(fortran_reader, + fortran_writer): '''Test that Fparser2Reader parses Fortran types correctly when there - is a type declaration with one of the members being initialised, - when there is a type declaration with an additional attribute - (e.g. one that extends an existing type), when there is a type - declaration that contains procedures and when there is a type - declaration that both has an additional attribute and contains - procedures. + is a type declaration with one of the members being initialised, with + visibility of the derived type either inline or separate. ''' # derived-type with initial value (StructureType) and in-line visibility @@ -2712,6 +2709,13 @@ def test_structures(fortran_reader, fortran_writer): " integer, public :: j\n" " end type my_type\n" in result) + +def test_structures_extends(fortran_reader, fortran_writer): + '''Test that Fparser2Reader parses Fortran types correctly when there + is a type declaration with one of the members being initialised, + and a type declaration with an additional extends attribute, either + know or unknown.''' + # type that extends another type, known in the symbol table # (StructureType) test_code = ( @@ -2757,6 +2761,12 @@ def test_structures(fortran_reader, fortran_writer): # Check that kernel_type is not declared in the module assert ":: kernel_type" not in result + +def test_structures_with_procedure(fortran_reader, fortran_writer): + '''Test that Fparser2Reader parses Fortran types correctly when there + is a type declaration with one of the members being initialised, + and that contains procedures.''' + # type that contains a procedure (StructureType) test_code = ( "module test_mod\n" @@ -2782,6 +2792,10 @@ def test_structures(fortran_reader, fortran_writer): " procedure, nopass :: test_code\n" " end type test_type\n" in result) + +def test_structures_abstract(fortran_reader, fortran_writer): + '''Test that Fparser2Reader parses Fortran types correctly when there + is a type declaration with an abstract keyword.''' # abstract type (UnsupportedFortranType) test_code = ( "module test_mod\n" @@ -2826,6 +2840,11 @@ def test_structures(fortran_reader, fortran_writer): " PROCEDURE, NOPASS :: test_code\n" "END TYPE test_type\n" in result) + +def test_structures_type_with_inline_procedures_visibility(fortran_reader, + fortran_writer): + '''Test that Fparser2Reader parses Fortran types correctly when there + is a type declaration with inline visibilities on procedure components.''' # public type that contains procedures test_code = ( "module test_mod\n" @@ -2862,7 +2881,7 @@ def test_structures(fortran_reader, fortran_writer): " end type test_type\n" in result) # private type that contains procedures - # the `test_code` procedure should thus become private + # the `test_code` procedure and `i` component should stay public test_code = ( "module test_mod\n" " use kernel_mod, only : parent_type\n" @@ -2892,11 +2911,100 @@ def test_structures(fortran_reader, fortran_writer): " type, extends(parent_type), private :: test_type\n" " integer, public :: i = 1\n" " contains\n" + " procedure, public :: test_code\n" + " procedure, private :: test_code_private\n" + " procedure, public :: test_code_public\n" + " end type test_type\n" in result) + + +def test_structures_type_with_standalone_procedures_visibility(fortran_reader, + fortran_writer): + '''Test that Fparser2Reader parses Fortran types correctly when there + is a type declaration with standalone visibilities on procedure or data + components.''' + # type that contains procedures and a private statement in the contains + # section + # the `test_code` procedure should thus become private + # but `i` should stay public + test_code = ( + "module test_mod\n" + " use kernel_mod, only : parent_type\n" + " type, extends(parent_type) :: test_type\n" + " integer :: i = 1\n" + " contains\n" + " private\n" + " procedure :: test_code\n" + " procedure, private :: test_code_private\n" + " procedure, public :: test_code_public\n" + " end type test_type\n" + " contains\n" + " subroutine test_code()\n" + " end subroutine\n" + " subroutine test_code_private()\n" + " end subroutine\n" + " subroutine test_code_public()\n" + " end subroutine\n" + "end module test_mod\n") + psyir = fortran_reader.psyir_from_source(test_code) + sym_table = psyir.children[0].symbol_table + symbol = sym_table.lookup("test_type") + assert isinstance(symbol, DataTypeSymbol) + assert isinstance(symbol.datatype, StructureType) + assert len(symbol.datatype.procedure_components) == 3 + result = fortran_writer(psyir) + assert ( + " type, extends(parent_type), public :: test_type\n" + " integer, public :: i = 1\n" + " contains\n" " procedure, private :: test_code\n" " procedure, private :: test_code_private\n" " procedure, public :: test_code_public\n" " end type test_type\n" in result) + # type that contains procedures and a private statement outside of the + # contains section + # the `i` component should become private + # but the `test_code` procedure should stay public + test_code = ( + "module test_mod\n" + " use kernel_mod, only : parent_type\n" + " type, extends(parent_type) :: test_type\n" + " private\n" + " integer :: i = 1\n" + " contains\n" + " procedure :: test_code\n" + " procedure, private :: test_code_private\n" + " procedure, public :: test_code_public\n" + " end type test_type\n" + " contains\n" + " subroutine test_code()\n" + " end subroutine\n" + " subroutine test_code_private()\n" + " end subroutine\n" + " subroutine test_code_public()\n" + " end subroutine\n" + "end module test_mod\n") + psyir = fortran_reader.psyir_from_source(test_code) + sym_table = psyir.children[0].symbol_table + symbol = sym_table.lookup("test_type") + assert isinstance(symbol, DataTypeSymbol) + assert isinstance(symbol.datatype, StructureType) + assert len(symbol.datatype.procedure_components) == 3 + result = fortran_writer(psyir) + assert ( + " type, extends(parent_type), public :: test_type\n" + " integer, private :: i = 1\n" + " contains\n" + " procedure, public :: test_code\n" + " procedure, private :: test_code_private\n" + " procedure, public :: test_code_public\n" + " end type test_type\n" in result) + + +def test_structures_type_with_procedure_interface(fortran_reader, + fortran_writer): + '''Test that Fparser2Reader parses Fortran types correctly when there + is a type declaration with a procedure component having an interface.''' # type that contains a procedure with an interface name test_code = ( "module test_mod\n" @@ -2926,6 +3034,12 @@ def test_structures(fortran_reader, fortran_writer): " procedure (my_interface) :: test_code\n" " end type test_type\n" in result) + +def test_structures_type_with_procedure_unsupported_attributes(fortran_reader, + fortran_writer): + '''Test that Fparser2Reader parses Fortran types correctly when there + is a type declaration with procedure components having unsupported + attributes.''' # type that contains procedures with pass, nopass and deferred test_code = ( "module test_mod\n" diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index 21fdbefae2..56cb469ff9 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -1142,21 +1142,51 @@ def test_replace_procedure_component_initial_value(): stype = StructureType() unsupported_type = UnsupportedFortranType("procedure, code => routine", None) - stype.add_procedure_component("proc", unsupported_type, + stype.add_procedure_component("code", unsupported_type, Symbol.Visibility.PUBLIC) new_routine_symbol = RoutineSymbol("new_routine") new_ref = Reference(new_routine_symbol) stype.replace_procedure_component_initial_value("routine", new_ref) - assert (stype.lookup_procedure_component("proc").datatype.declaration + assert (stype.lookup_procedure_component("code").datatype.declaration == "procedure, code => new_routine") # Other datatype stype = StructureType() routine_symbol = RoutineSymbol("routine") old_ref = Reference(routine_symbol) - stype.add_procedure_component("proc", UnresolvedType(), + stype.add_procedure_component("code", UnresolvedType(), Symbol.Visibility.PUBLIC, old_ref) new_routine_symbol = RoutineSymbol("new_routine") new_ref = Reference(new_routine_symbol) stype.replace_procedure_component_initial_value("routine", new_ref) - assert stype.lookup_procedure_component("proc").initial_value == new_ref + assert stype.lookup_procedure_component("code").initial_value == new_ref + + # Multiple procedure components of any type with same initial value + stype = StructureType() + unsupported_type_0 = UnsupportedFortranType("procedure, code_0 => routine", + None) + stype.add_procedure_component("code_0", unsupported_type_0, + Symbol.Visibility.PUBLIC) + unsupported_type_1 = UnsupportedFortranType("procedure, code_1 => routine", + None) + stype.add_procedure_component("code_1", unsupported_type_1, + Symbol.Visibility.PUBLIC) + routine_symbol = RoutineSymbol("routine") + stype.add_procedure_component("code_2", UnresolvedType(), + Symbol.Visibility.PUBLIC, + Reference(routine_symbol)) + stype.add_procedure_component("code_3", UnresolvedType(), + Symbol.Visibility.PUBLIC, + Reference(routine_symbol)) + new_routine_symbol = RoutineSymbol("new_routine") + stype.replace_procedure_component_initial_value( + "routine", + Reference(new_routine_symbol)) + assert (stype.lookup_procedure_component("code_0").datatype.declaration + == "procedure, code_0 => new_routine") + assert (stype.lookup_procedure_component("code_1").datatype.declaration + == "procedure, code_1 => new_routine") + assert (stype.lookup_procedure_component("code_2").initial_value.symbol + == new_routine_symbol) + assert (stype.lookup_procedure_component("code_3").initial_value.symbol + == new_routine_symbol)