From b97367b019e6353b19962732d4677040534f0ebf Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 8 Jul 2024 15:12:07 +0200 Subject: [PATCH 1/9] #2577 Add support for `pointer` and `target` in `DataSymbol`, `StructureType.ComponentType`, frontend and backend. NOTE: tests not done. --- src/psyclone/psyir/backend/fortran.py | 10 ++++ src/psyclone/psyir/frontend/fparser2.py | 29 ++++++++-- src/psyclone/psyir/symbols/datasymbol.py | 72 +++++++++++++++++++++++- src/psyclone/psyir/symbols/datatypes.py | 36 +++++++++--- 4 files changed, 132 insertions(+), 15 deletions(-) diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index 7190090c9a..e9471f78e7 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -580,6 +580,16 @@ def gen_vardecl(self, symbol, include_visibility=False): datatype = gen_datatype(symbol.datatype, symbol.name) result = f"{self._nindent}{datatype}" + # if (isinstance(symbol, (DataSymbol, StructureType.ComponentType)) + # and symbol.is_pointer): + if symbol.is_pointer: + result += ", pointer" + + # if (isinstance(symbol, (DataSymbol, StructureType.ComponentType)) + # and symbol.is_target): + if symbol.is_target: + result += ", target" + if ArrayType.Extent.DEFERRED in array_shape: # A 'deferred' array extent means this is an allocatable array result += ", allocatable" diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 4b15ed65b0..5ef8728920 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -1908,9 +1908,20 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, decln_access_spec = None # 6) Whether this declaration has the SAVE attribute. has_save_attr = False + # 7) Whether this declaration has the POINTER or TARGET attribute. + has_pointer_attr = False + has_target_attr = False if attr_specs: for attr in attr_specs.items: - if isinstance(attr, Fortran2003.Attr_Spec): + # NOTE: for a routine declaration, 'POINTER' or 'TARGET' is + # an Attr_Spec, but in a derived type declaration component + # it is not. + normalized_string = str(attr).lower().replace(' ', '') + if normalized_string == "pointer": + has_pointer_attr = True + elif normalized_string == "target": + has_target_attr = True + elif isinstance(attr, Fortran2003.Attr_Spec): normalized_string = str(attr).lower().replace(' ', '') if normalized_string == "save": if interface is not None: @@ -1953,8 +1964,9 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, f"{err.value}") from err else: raise NotImplementedError( - f"Could not process declaration '{decl}'. Unrecognised" - f" attribute type '{type(attr).__name__}'.") + f"Could not process declaration '{decl}'. " + f"Unrecognised attribute type " + f"'{type(attr).__name__}'.") # There are some combinations of attributes that are not valid # Fortran but fparser does not check, so we need to check for them @@ -2084,7 +2096,9 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, visibility=visibility, interface=this_interface, is_constant=has_constant_value, - initial_value=init_expr) + initial_value=init_expr, + is_pointer=has_pointer_attr, + is_target=has_target_attr) else: if sym is symbol_table.lookup_with_tag( "own_routine_symbol"): @@ -2108,7 +2122,9 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, sym = DataSymbol(sym_name, datatype, visibility=visibility, is_constant=has_constant_value, - initial_value=init_expr) + initial_value=init_expr, + is_pointer=has_pointer_attr, + is_target=has_target_attr) except ValueError: # Error setting initial value have to be raised as # NotImplementedError in order to create an UnsupportedType @@ -2230,7 +2246,8 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): # Convert from Symbols to type information for symbol in local_table.symbols: dtype.add(symbol.name, symbol.datatype, symbol.visibility, - symbol.initial_value) + symbol.initial_value, symbol.is_pointer, + symbol.is_target) # Update its type with the definition we've found tsymbol.datatype = dtype diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index 711c342eb1..915462fadf 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -68,12 +68,17 @@ class DataSymbol(TypedSymbol): ''' def __init__(self, name, datatype, is_constant=False, initial_value=None, + is_pointer=False, is_target=False, **kwargs): super().__init__(name, datatype) self._is_constant = False self._initial_value = None + self._is_pointer = False + self._is_target = False self._process_arguments(is_constant=is_constant, initial_value=initial_value, + is_pointer=is_pointer, + is_target=is_target, **kwargs) def _process_arguments(self, **kwargs): @@ -92,8 +97,12 @@ def _process_arguments(self, **kwargs): intrinsic types available in the TYPE_MAP_TO_PYTHON map. By default it is None.\n :type initial_value: Optional[item of TYPE_MAP_TO_PYTHON | - :py:class:`psyclone.psyir.nodes.Node`]\n + :py:class:`psyclone.psyir.nodes.Node`]\n and the arguments in :py:class:`psyclone.psyir.symbols.TypedSymbol` + :param bool is_pointer: whether this DataSymbol is a pointer + (default is False).\n + :param bool is_target: whether this DataSymbol is a target + (default is False).\n :type kwargs: unwrapped dict. :raises ValueError: if the symbol is a run-time constant but is not @@ -126,6 +135,11 @@ def _process_arguments(self, **kwargs): # a Symbol). self._is_constant = False + # Set the 'is_pointer' and 'is_target' attributes if they are provided, + # using the typechecked setters, or to False if they are not provided. + self._is_pointer = kwargs.pop("is_pointer", False) + self._is_target = kwargs.pop("is_target", False) + # Record whether an explicit value has been supplied for 'interface' # (before it is consumed by the super method). interface_supplied = "interface" in kwargs @@ -284,6 +298,52 @@ def initial_value(self, new_value): f"and therefore must have an initial value but got None") self._initial_value = None + @property + def is_pointer(self): + ''' + :returns: Whether the symbol is a pointer (True) or not (False). + :rtype: bool + ''' + return self._is_pointer + + @is_pointer.setter + def is_pointer(self, is_pointer): + ''' + :param bool value: whether or not this symbol is a pointer. + ''' + if not isinstance(is_pointer, bool): + raise TypeError( + f"The 'is_pointer' attribute of a DataSymbol must be a " + f"boolean but got '{type(is_pointer).__name__}'.") + if self.is_target and is_pointer: + raise ValueError( + f"A DataSymbol cannot be both a pointer and a target but " + f"symbol '{self.name}' is being set as both.") + self._is_pointer = is_pointer + + @property + def is_target(self): + ''' + :returns: Whether the symbol is a target (True) or not (False). + :rtype: bool + ''' + return self._is_target + + @is_target.setter + def is_target(self, is_target): + ''' + :param bool value: whether or not this symbol is a target. + ''' + if not isinstance(is_target, bool): + raise TypeError( + f"The 'is_target' attribute of a DataSymbol must be a " + f"boolean but got '{type(is_target).__name__}'.") + if self.is_pointer and is_target: + raise ValueError( + f"A DataSymbol cannot be both a pointer and a target but " + f"symbol '{self.name}' is being set as both.") + self._is_target = is_target + def __str__(self): ret = self.name + ": DataSymbol<" + str(self.datatype) ret += ", " + str(self._interface) @@ -291,6 +351,10 @@ def __str__(self): ret += f", initial_value={self.initial_value}" if self.is_constant: ret += ", constant=True" + if self.is_pointer: + ret += ", pointer=True" + if self.is_target: + ret += ", target=True" return ret + ">" def copy(self): @@ -309,7 +373,9 @@ def copy(self): return DataSymbol(self.name, self.datatype, visibility=self.visibility, interface=self.interface, is_constant=self.is_constant, - initial_value=new_init_value) + initial_value=new_init_value, + is_pointer=self.is_pointer, + is_target=self.is_target) def copy_properties(self, symbol_in): '''Replace all properties in this object with the properties from @@ -327,3 +393,5 @@ def copy_properties(self, symbol_in): super().copy_properties(symbol_in) self._is_constant = symbol_in.is_constant self._initial_value = symbol_in.initial_value + self._is_pointer = symbol_in.is_pointer + self._is_target = symbol_in.is_target diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index c9cc6c1aeb..4456415a1f 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -724,8 +724,11 @@ class StructureType(DataType): ''' # Each member of a StructureType is represented by a ComponentType # (named tuple). + # NOTE: the defaults are set to False for the last two arguments, + # is_pointer and is_target. ComponentType = namedtuple("ComponentType", [ - "name", "datatype", "visibility", "initial_value"]) + "name", "datatype", "visibility", "initial_value", "is_pointer", + "is_target"], defaults=[False, False]) def __init__(self): self._components = OrderedDict() @@ -745,7 +748,9 @@ def create(components): :py:class:`psyclone.psyir.symbols.DataType` | :py:class:`psyclone.psyir.symbols.DataTypeSymbol`, :py:class:`psyclone.psyir.symbols.Symbol.Visibility`, - Optional[:py:class:`psyclone.psyir.symbols.DataNode`] + Optional[:py:class:`psyclone.psyir.symbols.DataNode`], + bool, + bool ]] :returns: the new type object. @@ -754,10 +759,11 @@ def create(components): ''' stype = StructureType() for component in components: - if len(component) != 4: + if len(component) not in (4, 5, 6): raise TypeError( - f"Each component must be specified using a 4-tuple of " - f"(name, type, visibility, initial_value) but found a " + f"Each component must be specified using a 6-tuple of " + f"(name, type, visibility, initial_value, is_pointer, " + f"is_target), the two of which are optional, but found a " f"tuple with {len(component)} members: {component}") stype.add(*component) return stype @@ -770,7 +776,8 @@ def components(self): ''' return self._components - def add(self, name, datatype, visibility, initial_value): + def add(self, name, datatype, visibility, initial_value, is_pointer=False, + is_target=False): ''' Create a component with the supplied attributes and add it to this StructureType. @@ -784,6 +791,8 @@ def add(self, name, datatype, visibility, initial_value): :param initial_value: the initial value of the new component. :type initial_value: Optional[ :py:class:`psyclone.psyir.nodes.DataNode`] + :param bool is_pointer: whether this component is a pointer. + :param bool is_target: whether this component is a target. :raises TypeError: if any of the supplied values are of the wrong type. @@ -818,9 +827,22 @@ def add(self, name, datatype, visibility, initial_value): 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__}'.") + if not isinstance(is_pointer, bool): + raise TypeError( + f"The is_pointer attribute of a component of a StructureType " + f"must be a 'bool' but got '{type(is_pointer).__name__}'.") + if not isinstance(is_target, bool): + raise TypeError( + f"The is_target attribute of a component of a StructureType " + f"must be a 'bool' but got '{type(is_target).__name__}'.") + if is_pointer and is_target: + raise ValueError( + f"A component of a StructureType cannot be both a pointer " + f"and a target but got is_pointer={is_pointer} and " + f"is_target={is_target}.") self._components[name] = self.ComponentType( - name, datatype, visibility, initial_value) + name, datatype, visibility, initial_value, is_pointer, is_target) def lookup(self, name): ''' From 774274ada4921927287f375d373e3a3efcae2dc6 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Tue, 3 Dec 2024 15:43:28 +0100 Subject: [PATCH 2/9] #2577 Reimplement pointer and target attributes in DataType(Symbol) instead of Symbols. Some tests. --- src/psyclone/psyir/backend/fortran.py | 15 +-- src/psyclone/psyir/frontend/fparser2.py | 37 ++++--- .../psyir/symbols/data_type_symbol.py | 41 +++++++- src/psyclone/psyir/symbols/datasymbol.py | 76 ++------------- src/psyclone/psyir/symbols/datatypes.py | 97 ++++++++++++------- src/psyclone/psyir/symbols/typed_symbol.py | 50 ++++++++++ .../intrinsics/matmul2code_trans.py | 11 +++ .../gocean1p0_transformations_test.py | 2 +- .../psyir/backend/fortran_gen_decls_test.py | 61 +++++++++++- .../psyir/frontend/fparser2_pointer_test.py | 95 +++++++++++++++++- .../fparser2_subroutine_handler_test.py | 7 +- .../tests/psyir/frontend/fparser2_test.py | 72 ++++++++++---- .../tests/psyir/symbols/symbol_table_test.py | 12 ++- .../transformations/inline_trans_test.py | 10 +- .../intrinsics/matmul2code_trans_test.py | 7 +- 15 files changed, 428 insertions(+), 165 deletions(-) diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index e9471f78e7..73cbd48839 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -580,16 +580,6 @@ def gen_vardecl(self, symbol, include_visibility=False): datatype = gen_datatype(symbol.datatype, symbol.name) result = f"{self._nindent}{datatype}" - # if (isinstance(symbol, (DataSymbol, StructureType.ComponentType)) - # and symbol.is_pointer): - if symbol.is_pointer: - result += ", pointer" - - # if (isinstance(symbol, (DataSymbol, StructureType.ComponentType)) - # and symbol.is_target): - if symbol.is_target: - result += ", target" - if ArrayType.Extent.DEFERRED in array_shape: # A 'deferred' array extent means this is an allocatable array result += ", allocatable" @@ -622,6 +612,11 @@ def gen_vardecl(self, symbol, include_visibility=False): f"A Symbol must be either public or private but symbol " f"'{symbol.name}' has visibility '{symbol.visibility}'") + if symbol.datatype.is_pointer: + result += ", pointer" + elif symbol.datatype.is_target: + result += ", target" + # Specify name result += f" :: {symbol.name}" diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 5ef8728920..d43b8d6f44 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -1918,9 +1918,9 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, # it is not. normalized_string = str(attr).lower().replace(' ', '') if normalized_string == "pointer": - has_pointer_attr = True + has_pointer_attr = True elif normalized_string == "target": - has_target_attr = True + has_target_attr = True elif isinstance(attr, Fortran2003.Attr_Spec): normalized_string = str(attr).lower().replace(' ', '') if normalized_string == "save": @@ -1977,6 +1977,16 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, f"SAVE and PARAMETER attributes are not compatible but " f"found:\n {decl}") + # TODO check these are needed + if has_pointer_attr and has_target_attr: + raise GenerationError( + f"POINTER and TARGET attributes are not compatible but " + f"found:\n {decl}") + if has_pointer_attr and has_constant_value: + raise GenerationError( + f"POINTER and PARAMETER attributes are not compatible but " + f"found:\n {decl}") + # Now we've checked for save and parameter existing # together, we can allow parameter without save and set it # to the same interface as save. @@ -1988,6 +1998,10 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, raise GenerationError( f"ALLOCATABLE and PARAMETER attributes are not compatible " f"but found:\n {decl}") + if allocatable and has_pointer_attr: + raise GenerationError( + f"ALLOCATABLE and POINTER attributes are not compatible " + f"but found:\n {decl}") if isinstance(interface, ArgumentInterface) and has_constant_value: raise GenerationError( f"INTENT and PARAMETER attributes are not compatible but" @@ -2080,9 +2094,13 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, if entity_shape: # array - datatype = ArrayType(base_type, entity_shape) + datatype = ArrayType(base_type, entity_shape, + is_pointer=has_pointer_attr, + is_target=has_target_attr) else: # scalar + base_type._is_pointer = has_pointer_attr + base_type._is_target = has_target_attr datatype = base_type # Make sure the declared symbol exists in the SymbolTable @@ -2096,9 +2114,7 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, visibility=visibility, interface=this_interface, is_constant=has_constant_value, - initial_value=init_expr, - is_pointer=has_pointer_attr, - is_target=has_target_attr) + initial_value=init_expr) else: if sym is symbol_table.lookup_with_tag( "own_routine_symbol"): @@ -2122,9 +2138,7 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, sym = DataSymbol(sym_name, datatype, visibility=visibility, is_constant=has_constant_value, - initial_value=init_expr, - is_pointer=has_pointer_attr, - is_target=has_target_attr) + initial_value=init_expr) except ValueError: # Error setting initial value have to be raised as # NotImplementedError in order to create an UnsupportedType @@ -2246,8 +2260,7 @@ def _process_derived_type_decln(self, parent, decl, visibility_map): # Convert from Symbols to type information for symbol in local_table.symbols: dtype.add(symbol.name, symbol.datatype, symbol.visibility, - symbol.initial_value, symbol.is_pointer, - symbol.is_target) + symbol.initial_value) # Update its type with the definition we've found tsymbol.datatype = dtype @@ -2291,7 +2304,7 @@ def _get_partial_datatype(self, node, scope, visibility_map): orig_entity_decl_children = list(entity_decl.children[:]) # 2: Remove any unsupported attributes - unsupported_attribute_names = ["pointer", "target"] + unsupported_attribute_names = ["asynchronous", "volatile"] attr_spec_list = node.children[1] orig_node_children = list(node.children[:]) orig_attr_spec_list_children = (list(node.children[1].children[:]) diff --git a/src/psyclone/psyir/symbols/data_type_symbol.py b/src/psyclone/psyir/symbols/data_type_symbol.py index 0f15fdc51d..bd520dd706 100644 --- a/src/psyclone/psyir/symbols/data_type_symbol.py +++ b/src/psyclone/psyir/symbols/data_type_symbol.py @@ -52,17 +52,35 @@ 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 is_pointer: whether this is a pointer symbol. + :type is_pointer: bool + :param is_target: whether this is a target symbol. + :type is_target: bool ''' def __init__(self, name, datatype, visibility=Symbol.DEFAULT_VISIBILITY, - interface=None): + interface=None, + is_pointer=False, + is_target=False): + if not isinstance(is_pointer, bool): + raise TypeError(f"is_pointer should be a boolean but found " + f"'{type(is_pointer).__name__}'.") + if not isinstance(is_target, bool): + raise TypeError(f"is_target should be a boolean but found " + f"'{type(is_target).__name__}'.") + if is_pointer and is_target: + raise ValueError("A symbol cannot be both a pointer and a target.") + super(DataTypeSymbol, self).__init__(name, visibility, interface) # The following attribute has a setter method (with error checking) self._datatype = None self.datatype = datatype + self._is_pointer = is_pointer + self._is_target = is_target + 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 +92,8 @@ def copy(self): ''' return type(self)(self.name, self.datatype, visibility=self.visibility, - interface=self.interface) + interface=self.interface, is_pointer=self.is_pointer, + is_target=self.is_target) def __str__(self): return f"{self.name}: {type(self).__name__}" @@ -123,6 +142,24 @@ 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_pointer = symbol_in.is_pointer + self._is_target = symbol_in.is_target + + @property + def is_pointer(self): + ''' + :returns: True if this symbol is a pointer. + :rtype: bool + ''' + return self._is_pointer + + @property + def is_target(self): + ''' + :returns: True if this symbol is a target. + :rtype: bool + ''' + return self._is_target # For automatic documentation generation diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index 915462fadf..26211e8b58 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -68,17 +68,12 @@ class DataSymbol(TypedSymbol): ''' def __init__(self, name, datatype, is_constant=False, initial_value=None, - is_pointer=False, is_target=False, **kwargs): super().__init__(name, datatype) self._is_constant = False self._initial_value = None - self._is_pointer = False - self._is_target = False self._process_arguments(is_constant=is_constant, initial_value=initial_value, - is_pointer=is_pointer, - is_target=is_target, **kwargs) def _process_arguments(self, **kwargs): @@ -99,10 +94,6 @@ def _process_arguments(self, **kwargs): :type initial_value: Optional[item of TYPE_MAP_TO_PYTHON | :py:class:`psyclone.psyir.nodes.Node`]\n and the arguments in :py:class:`psyclone.psyir.symbols.TypedSymbol` - :param bool is_pointer: whether this DataSymbol is a pointer - (default is False).\n - :param bool is_target: whether this DataSymbol is a target - (default is False).\n :type kwargs: unwrapped dict. :raises ValueError: if the symbol is a run-time constant but is not @@ -135,11 +126,6 @@ def _process_arguments(self, **kwargs): # a Symbol). self._is_constant = False - # Set the 'is_pointer' and 'is_target' attributes if they are provided, - # using the typechecked setters, or to False if they are not provided. - self._is_pointer = kwargs.pop("is_pointer", False) - self._is_target = kwargs.pop("is_target", False) - # Record whether an explicit value has been supplied for 'interface' # (before it is consumed by the super method). interface_supplied = "interface" in kwargs @@ -162,6 +148,12 @@ def _process_arguments(self, **kwargs): # would otherwise default to Automatic. self.interface = StaticInterface() + if self.is_constant and self.datatype.is_pointer: + # PARAMETER and POINTER are mutually exclusive in Fortran. + raise ValueError( + f"DataSymbol '{self.name}' is a constant and therefore cannot " + f"be a pointer.") + @property def is_constant(self): ''' @@ -298,52 +290,6 @@ def initial_value(self, new_value): f"and therefore must have an initial value but got None") self._initial_value = None - @property - def is_pointer(self): - ''' - :returns: Whether the symbol is a pointer (True) or not (False). - :rtype: bool - ''' - return self._is_pointer - - @is_pointer.setter - def is_pointer(self, is_pointer): - ''' - :param bool value: whether or not this symbol is a pointer. - ''' - if not isinstance(is_pointer, bool): - raise TypeError( - f"The 'is_pointer' attribute of a DataSymbol must be a " - f"boolean but got '{type(is_pointer).__name__}'.") - if self.is_target and is_pointer: - raise ValueError( - f"A DataSymbol cannot be both a pointer and a target but " - f"symbol '{self.name}' is being set as both.") - self._is_pointer = is_pointer - - @property - def is_target(self): - ''' - :returns: Whether the symbol is a target (True) or not (False). - :rtype: bool - ''' - return self._is_target - - @is_target.setter - def is_target(self, is_target): - ''' - :param bool value: whether or not this symbol is a target. - ''' - if not isinstance(is_target, bool): - raise TypeError( - f"The 'is_target' attribute of a DataSymbol must be a " - f"boolean but got '{type(is_target).__name__}'.") - if self.is_pointer and is_target: - raise ValueError( - f"A DataSymbol cannot be both a pointer and a target but " - f"symbol '{self.name}' is being set as both.") - self._is_target = is_target - def __str__(self): ret = self.name + ": DataSymbol<" + str(self.datatype) ret += ", " + str(self._interface) @@ -351,10 +297,6 @@ def __str__(self): ret += f", initial_value={self.initial_value}" if self.is_constant: ret += ", constant=True" - if self.is_pointer: - ret += ", pointer=True" - if self.is_target: - ret += ", target=True" return ret + ">" def copy(self): @@ -373,9 +315,7 @@ def copy(self): return DataSymbol(self.name, self.datatype, visibility=self.visibility, interface=self.interface, is_constant=self.is_constant, - initial_value=new_init_value, - is_pointer=self.is_pointer, - is_target=self.is_target) + initial_value=new_init_value) def copy_properties(self, symbol_in): '''Replace all properties in this object with the properties from @@ -393,5 +333,3 @@ def copy_properties(self, symbol_in): super().copy_properties(symbol_in) self._is_constant = symbol_in.is_constant self._initial_value = symbol_in.initial_value - self._is_pointer = symbol_in.is_pointer - self._is_target = symbol_in.is_target diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index 4456415a1f..dd7bd1cd13 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -48,7 +48,26 @@ class DataType(metaclass=abc.ABCMeta): - '''Abstract base class from which all types are derived.''' + '''Abstract base class from which all types are derived. + + :param bool is_pointer: whether this datatype is a pointer. + :param bool is_target: whether this datatype is a target. + + :raises TypeError: if is_pointer or is_target are not of type bool. + :raises ValueError: if is_pointer and is_target are both True. + ''' + def __init__(self, is_pointer=False, is_target=False): + if not isinstance(is_pointer, bool): + raise TypeError(f"Expected 'is_pointer' to be a bool but got " + f"'{type(is_pointer)}'") + if not isinstance(is_target, bool): + raise TypeError(f"Expected 'is_target' to be a bool but got " + f"'{type(is_target)}'") + if is_pointer and is_target: + raise ValueError("A datatype cannot be both a pointer and a " + "target.") + self._is_pointer = is_pointer + self._is_target = is_target @abc.abstractmethod def __str__(self): @@ -65,7 +84,25 @@ def __eq__(self, other): :returns: whether this type is equal to the 'other' type. :rtype: bool ''' - return type(other) is type(self) + return (type(other) is type(self) + and self.is_pointer == other.is_pointer + and self.is_target == other.is_target) + + @property + def is_pointer(self): + ''' + :returns: whether this datatype is a pointer. + :rtype: bool + ''' + return self._is_pointer + + @property + def is_target(self): + ''' + :returns: whether this datatype is a target. + :rtype: bool + ''' + return self._is_target class UnresolvedType(DataType): @@ -97,7 +134,8 @@ class UnsupportedType(DataType, metaclass=abc.ABCMeta): :raises TypeError: if the supplied declaration_txt is not a str. ''' - def __init__(self, declaration_txt): + def __init__(self, declaration_txt, is_pointer=False, is_target=False): + super().__init__(is_pointer, is_target) if not isinstance(declaration_txt, str): raise TypeError( f"UnsupportedType constructor expects the original variable " @@ -135,8 +173,9 @@ class UnsupportedFortranType(UnsupportedType): :py:class:`psyclone.psyir.symbols.DataTypeSymbol`] ''' - def __init__(self, declaration_txt, partial_datatype=None): - super().__init__(declaration_txt) + def __init__(self, declaration_txt, partial_datatype=None, + is_pointer=False, is_target=False): + super().__init__(declaration_txt, is_pointer, is_target) # This will hold the Fortran type specification (as opposed to # the whole declaration). self._type_text = "" @@ -268,7 +307,9 @@ class Precision(Enum): DOUBLE = 2 UNDEFINED = 3 - def __init__(self, intrinsic, precision): + def __init__(self, intrinsic, precision, is_pointer=False, + is_target=False): + super().__init__(is_pointer, is_target) if not isinstance(intrinsic, ScalarType.Intrinsic): raise TypeError( f"ScalarType expected 'intrinsic' argument to be of type " @@ -383,7 +424,8 @@ class Extent(Enum): #: namedtuple used to store lower and upper limits of an array dimension ArrayBounds = namedtuple("ArrayBounds", ["lower", "upper"]) - def __init__(self, datatype, shape): + def __init__(self, datatype, shape, is_pointer=False, is_target=False): + super().__init__(is_pointer, is_target) # This import must be placed here to avoid circular dependencies. # pylint: disable=import-outside-toplevel @@ -620,6 +662,9 @@ def _validate_data_node(dim_node, is_lower_bound=False): _validate_data_node(dimension) if ArrayType.Extent.DEFERRED in extents: + if self.is_pointer: + raise ValueError("An array with a deferred shape cannot be a " + "pointer.") if not all(dim == ArrayType.Extent.DEFERRED for dim in extents): raise TypeError( @@ -724,13 +769,11 @@ class StructureType(DataType): ''' # Each member of a StructureType is represented by a ComponentType # (named tuple). - # NOTE: the defaults are set to False for the last two arguments, - # is_pointer and is_target. ComponentType = namedtuple("ComponentType", [ - "name", "datatype", "visibility", "initial_value", "is_pointer", - "is_target"], defaults=[False, False]) + "name", "datatype", "visibility", "initial_value"]) def __init__(self): + super().__init__(is_pointer=False, is_target=False) self._components = OrderedDict() def __str__(self): @@ -749,8 +792,6 @@ def create(components): :py:class:`psyclone.psyir.symbols.DataTypeSymbol`, :py:class:`psyclone.psyir.symbols.Symbol.Visibility`, Optional[:py:class:`psyclone.psyir.symbols.DataNode`], - bool, - bool ]] :returns: the new type object. @@ -759,11 +800,10 @@ def create(components): ''' stype = StructureType() for component in components: - if len(component) not in (4, 5, 6): + if len(component) != 4: raise TypeError( - f"Each component must be specified using a 6-tuple of " - f"(name, type, visibility, initial_value, is_pointer, " - f"is_target), the two of which are optional, but found a " + 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) return stype @@ -776,13 +816,13 @@ def components(self): ''' return self._components - def add(self, name, datatype, visibility, initial_value, is_pointer=False, - is_target=False): + def add(self, name, datatype, visibility, initial_value): ''' Create a component with the supplied attributes and add it to this StructureType. - :param str name: the name of the new component. + :param str name: the name of the new component., is_pointer=False, + is_target=False :param datatype: the type of the new component. :type datatype: :py:class:`psyclone.psyir.symbols.DataType` | :py:class:`psyclone.psyir.symbols.DataTypeSymbol` @@ -791,8 +831,6 @@ def add(self, name, datatype, visibility, initial_value, is_pointer=False, :param initial_value: the initial value of the new component. :type initial_value: Optional[ :py:class:`psyclone.psyir.nodes.DataNode`] - :param bool is_pointer: whether this component is a pointer. - :param bool is_target: whether this component is a target. :raises TypeError: if any of the supplied values are of the wrong type. @@ -827,22 +865,9 @@ def add(self, name, datatype, visibility, initial_value, is_pointer=False, 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__}'.") - if not isinstance(is_pointer, bool): - raise TypeError( - f"The is_pointer attribute of a component of a StructureType " - f"must be a 'bool' but got '{type(is_pointer).__name__}'.") - if not isinstance(is_target, bool): - raise TypeError( - f"The is_target attribute of a component of a StructureType " - f"must be a 'bool' but got '{type(is_target).__name__}'.") - if is_pointer and is_target: - raise ValueError( - f"A component of a StructureType cannot be both a pointer " - f"and a target but got is_pointer={is_pointer} and " - f"is_target={is_target}.") self._components[name] = self.ComponentType( - name, datatype, visibility, initial_value, is_pointer, is_target) + name, datatype, visibility, initial_value) def lookup(self, name): ''' diff --git a/src/psyclone/psyir/symbols/typed_symbol.py b/src/psyclone/psyir/symbols/typed_symbol.py index 9a328f5d61..d0dd43f355 100644 --- a/src/psyclone/psyir/symbols/typed_symbol.py +++ b/src/psyclone/psyir/symbols/typed_symbol.py @@ -62,6 +62,56 @@ def __init__(self, name, datatype, **kwargs): super(TypedSymbol, self).__init__(name) self._process_arguments(datatype=datatype, **kwargs) + @property + def is_unresolved(self): + ''' + :returns: whether the Symbol has an UnresolvedInterface or its + datatype is an UnresolvedType. + :rtype: bool + ''' + # Import here to avoid circular dependencies + # pylint: disable=import-outside-toplevel + from psyclone.psyir.symbols.datatypes import UnresolvedType + return (super().is_unresolved + or isinstance(self.datatype, UnresolvedType)) + + @property + def is_unsupported(self): + ''' + :returns: whether the datatype of the Symbol is an UnsupportedType. + :rtype: bool + ''' + # Import here to avoid circular dependencies + # pylint: disable=import-outside-toplevel + from psyclone.psyir.symbols.datatypes import UnsupportedType + return isinstance(self.datatype, UnsupportedType) + + @property + def is_potentially_aliased(self): + ''' + :returns: whether the Symbol is potentially aliased, i.e. it is a \ + pointer, target, unresolved or unsupported. + :rtype: bool + ''' + return (self.datatype.is_pointer or self.datatype.is_target + or self.is_unresolved or self.is_unsupported) + + @property + def is_pointer(self): + ''' + :returns: whether the Symbol is a pointer. + :rtype: bool + ''' + return self.datatype.is_pointer + + @property + def is_target(self): + ''' + :returns: whether the Symbol is a target. + :rtype: bool + ''' + return self.datatype.is_target + def _process_arguments(self, **kwargs): ''' Process the arguments for the constructor and the specialise methods. In this case the datatype argument. diff --git a/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py b/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py index a7b1660535..882e940b1e 100644 --- a/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py +++ b/src/psyclone/psyir/transformations/intrinsics/matmul2code_trans.py @@ -189,6 +189,8 @@ def validate(self, node, options=None): operation is not an assignment. :raises TransformationError: if the matmul arguments are not in \ the required form. + :raises TransformationError: if any of the arrays are potentially \ + aliased. :raises TransformationError: if sub-sections of an array are present \ in the arguments. @@ -235,6 +237,15 @@ def validate(self, node, options=None): f"be references to arrays but found '{result.symbol}', " f"'{matrix1.symbol}' and '{matrix2.symbol}'.") + # The arrays must not be potentially aliased + if any(var.symbol.is_potentially_aliased for var in + [matrix1, matrix2, result]): + raise TransformationError( + f"Expected result and operands of MATMUL IntrinsicCall to " + f"be references to arrays that are not potentially aliased " + f"but found '{result.symbol}', '{matrix1.symbol}' and " + f"'{matrix2.symbol}'.") + # The first child (matrix1) should be declared as an array # with at least 2 dimensions. if len(matrix1.symbol.shape) < 2: diff --git a/src/psyclone/tests/domain/gocean/transformations/gocean1p0_transformations_test.py b/src/psyclone/tests/domain/gocean/transformations/gocean1p0_transformations_test.py index b965696b15..b5fc9cea12 100644 --- a/src/psyclone/tests/domain/gocean/transformations/gocean1p0_transformations_test.py +++ b/src/psyclone/tests/domain/gocean/transformations/gocean1p0_transformations_test.py @@ -1328,7 +1328,7 @@ def test_acc_enter_directive_infrastructure_setup(): USE iso_c_binding, ONLY: c_ptr USE kind_params_mod, ONLY: go_wp TYPE(c_ptr), intent(in) :: from - REAL(KIND=go_wp), DIMENSION(:, :), INTENT(INOUT), TARGET :: to + REAL(KIND=go_wp), dimension(:,:), intent(inout), target :: to INTEGER, intent(in) :: startx INTEGER, intent(in) :: starty INTEGER, intent(in) :: nx 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..5eaf99165e 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, ArrayType) def test_gen_param_decls_dependencies(fortran_writer): @@ -209,6 +209,65 @@ def test_gen_decls(fortran_writer): "'unknown'" in str(excinfo.value)) +def test_gen_decls_pointer_target(fortran_writer): + '''Test that the gen_decls method correctly handles symbols with + is_pointer=True xor is_target=True attributes in their datatypes. + ''' + symbol_table = SymbolTable() + + # Test with is_pointer=True on a scalar + int_ptr_type = ScalarType(ScalarType.Intrinsic.INTEGER, + ScalarType.Precision.UNDEFINED, + is_pointer=True) + pointer_symbol = DataSymbol("ptr_var", int_ptr_type) + symbol_table.add(pointer_symbol) + result = fortran_writer.gen_decls(symbol_table) + assert "integer, pointer :: ptr_var" in result + + # Test with is_target=True on a scalar + int_tgt_type = ScalarType(ScalarType.Intrinsic.INTEGER, + ScalarType.Precision.UNDEFINED, + is_target=True) + target_symbol = DataSymbol("tgt_var", int_tgt_type) + symbol_table.add(target_symbol) + result = fortran_writer.gen_decls(symbol_table) + assert "integer, target :: tgt_var" in result + + # Test with is_pointer=True on an array + array_ptr_type = ArrayType(ScalarType(ScalarType.Intrinsic.INTEGER, + ScalarType.Precision.UNDEFINED), + [ArrayType.Extent.ATTRIBUTE], + is_pointer=True) + pointer_symbol = DataSymbol("ptr_array", array_ptr_type) + symbol_table.add(pointer_symbol) + result = fortran_writer.gen_decls(symbol_table) + assert "integer, dimension(:), pointer :: ptr_array" in result + + # Test with is_target=True on an array + array_tgt_type = ArrayType(ScalarType(ScalarType.Intrinsic.INTEGER, + ScalarType.Precision.UNDEFINED), + [ArrayType.Extent.ATTRIBUTE], + is_target=True) + target_symbol = DataSymbol("tgt_array", array_tgt_type) + symbol_table.add(target_symbol) + result = fortran_writer.gen_decls(symbol_table) + assert "integer, dimension(:), target :: tgt_array" in result + + # Test with is_pointer=True on a derived type + dtypesym = DataTypeSymbol("dtype", StructureType(), is_pointer=True) + dtype_ptr_sym = DataSymbol("dtype_ptr", dtypesym) + symbol_table.add(dtype_ptr_sym) + result = fortran_writer.gen_decls(symbol_table) + assert "type(dtype), pointer :: dtype_ptr" in result + + # Test with is_target=True on a derived type + dtypesym = DataTypeSymbol("dtype", StructureType(), is_target=True) + dtype_tgt_sym = DataSymbol("dtype_tgt", dtypesym) + symbol_table.add(dtype_tgt_sym) + result = fortran_writer.gen_decls(symbol_table) + assert "type(dtype), target :: dtype_tgt" in result + + def test_gen_decls_nested_scope(fortran_writer): ''' Test that gen_decls() correctly checks for potential wildcard imports of an unresolved symbol in an outer scope. diff --git a/src/psyclone/tests/psyir/frontend/fparser2_pointer_test.py b/src/psyclone/tests/psyir/frontend/fparser2_pointer_test.py index f796162014..75be8aa98d 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_pointer_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_pointer_test.py @@ -38,7 +38,100 @@ ''' Performs py.test tests on the handling of pointers in the fparser2 PSyIR front-end. ''' -from psyclone.psyir.nodes import CodeBlock, Assignment +from psyclone.psyir.nodes import CodeBlock, Assignment, Routine +from psyclone.psyir.symbols import ScalarType, ArrayType, DataTypeSymbol + + +def test_pointer_declaration(fortran_reader): + ''' Test that pointer declarations are parsed correctly. ''' + test_module = ''' + subroutine mysub() + integer, pointer :: a + integer, pointer :: b(:) + integer, pointer :: c(:,:) + type(my_type), pointer :: d + end subroutine + ''' + file_container = fortran_reader.psyir_from_source(test_module) + assert not file_container.walk(CodeBlock) + routine = file_container.walk(Routine)[0] + symbol_table = routine.symbol_table + + sym_a = symbol_table.lookup("a") + assert isinstance(sym_a.datatype, ScalarType) + assert sym_a.datatype.intrinsic == ScalarType.Intrinsic.INTEGER + assert sym_a.datatype.is_pointer + assert sym_a.is_pointer + assert not sym_a.is_target + + sym_b = symbol_table.lookup("b") + assert isinstance(sym_b.datatype, ArrayType) + assert sym_b.datatype.intrinsic == ScalarType.Intrinsic.INTEGER + assert sym_b.datatype.is_pointer + assert sym_b.is_pointer + assert not sym_b.datatype.datatype.is_pointer + assert not sym_b.is_target + + sym_c = symbol_table.lookup("c") + assert isinstance(sym_c.datatype, ArrayType) + assert sym_c.datatype.intrinsic == ScalarType.Intrinsic.INTEGER + assert sym_c.datatype.is_pointer + assert sym_c.is_pointer + assert not sym_c.datatype.datatype.is_pointer + assert not sym_c.is_target + + sym_d = symbol_table.lookup("d") + assert isinstance(sym_d.datatype, DataTypeSymbol) + assert sym_d.datatype.name == "my_type" + assert sym_d.datatype.is_pointer + assert sym_d.is_pointer + assert not sym_d.is_target + + +def test_target_declaration(fortran_reader): + ''' Test that target declarations are parsed correctly. ''' + test_module = ''' + subroutine mysub() + integer, target :: a + integer, target :: b(:) + integer, target :: c(:,:) + type(my_type), target :: d + end subroutine + ''' + file_container = fortran_reader.psyir_from_source(test_module) + assert not file_container.walk(CodeBlock) + routine = file_container.walk(Routine)[0] + symbol_table = routine.symbol_table + + sym_a = symbol_table.lookup("a") + assert isinstance(sym_a.datatype, ScalarType) + assert sym_a.datatype.intrinsic == ScalarType.Intrinsic.INTEGER + assert sym_a.datatype.is_target + assert sym_a.is_target + assert not sym_a.is_pointer + + sym_b = symbol_table.lookup("b") + assert isinstance(sym_b.datatype, ArrayType) + assert sym_b.datatype.intrinsic == ScalarType.Intrinsic.INTEGER + assert sym_b.datatype.is_target + assert sym_b.is_target + assert not sym_b.datatype.datatype.is_target + assert not sym_b.is_pointer + + sym_c = symbol_table.lookup("c") + assert isinstance(sym_c.datatype, ArrayType) + assert sym_c.datatype.intrinsic == ScalarType.Intrinsic.INTEGER + assert sym_c.datatype.is_target + assert sym_c.is_target + assert not sym_c.datatype.datatype.is_target + assert not sym_c.is_pointer + + sym_d = symbol_table.lookup("d") + assert isinstance(sym_d.datatype, DataTypeSymbol) + assert sym_d.datatype.name == "my_type" + assert sym_d.datatype.is_target + assert sym_d.is_target + assert not sym_d.is_pointer def test_pointer_assignments(fortran_reader): diff --git a/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py b/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py index 5481fbf28c..b6bfe5dc03 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_subroutine_handler_test.py @@ -357,7 +357,7 @@ def test_function_unsupported_derived_type(fortran_reader): " type :: my_type\n" " integer :: flag\n" " end type my_type\n" - " type(my_type), pointer :: my_func, var1\n" + " type(my_type), pointer, asynchronous :: my_func, var1\n" " my_func => null()\n" " end function my_func\n" "end module a\n") @@ -367,10 +367,11 @@ def test_function_unsupported_derived_type(fortran_reader): assert routine.return_symbol.name == "my_func" assert isinstance(routine.return_symbol.datatype, UnsupportedFortranType) assert (routine.return_symbol.datatype.declaration.lower() == - "type(my_type), pointer :: my_func") + "type(my_type), pointer, asynchronous :: my_func") sym = routine.symbol_table.lookup("var1") assert isinstance(sym.datatype, UnsupportedFortranType) - assert sym.datatype.declaration.lower() == "type(my_type), pointer :: var1" + assert (sym.datatype.declaration.lower() + == "type(my_type), pointer, asynchronous :: var1") @pytest.mark.parametrize("fn_prefix", ["elemental", "pure", "impure", diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index f1b4f03940..68e7dda4e5 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -681,7 +681,7 @@ def test_get_partial_datatype(): # Entry in symbol table with partial information. Example has one # unsupported attribute (and no others) and an unsupported assignment. - reader = FortranStringReader("integer, pointer :: l1 => null()") + reader = FortranStringReader("integer, asynchronous :: l1 => null()") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] datatype, init = processor._get_partial_datatype(node, fake_parent, {}) @@ -694,7 +694,7 @@ def test_get_partial_datatype(): # Entry in symbol table with partial information. Example has one # unsupported attribute and one supported attribute. - reader = FortranStringReader("real*4, target, dimension(10,20) :: l1") + reader = FortranStringReader("real*4, asynchronous, dimension(10,20):: l1") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] datatype, init = processor._get_partial_datatype(node, fake_parent, {}) @@ -722,7 +722,7 @@ def test_get_partial_datatype(): # Multiple variables in the declaration are also supported but are # not used by PSyclone at the moment. reader = FortranStringReader( - "integer, pointer :: l1 => null(), l2 => null()") + "integer, asynchronous :: l1 => null(), l2 => null()") node = Specification_Part(reader).content[0] ids = [id(entry) for entry in walk(node)] datatype, init = processor._get_partial_datatype(node, fake_parent, {}) @@ -789,6 +789,30 @@ def test_process_declarations(): assert symtab.lookup("p3").visibility == Symbol.Visibility.PRIVATE assert symtab.lookup("p4").visibility == Symbol.Visibility.PRIVATE + # pointer/target attribute + reader = FortranStringReader("real, pointer :: p5") + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + assert symtab.lookup("p5").is_pointer + assert not symtab.lookup("p5").is_target + assert isinstance(symtab.lookup("p5").datatype, ScalarType) + assert symtab.lookup("p5").datatype.intrinsic == ScalarType.Intrinsic.REAL + reader = FortranStringReader("real, target :: p6") + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + assert symtab.lookup("p6").is_target + assert not symtab.lookup("p6").is_pointer + assert isinstance(symtab.lookup("p6").datatype, ScalarType) + assert symtab.lookup("p6").datatype.intrinsic == ScalarType.Intrinsic.REAL + reader = FortranStringReader("real, dimension(5), pointer :: p7") + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + assert symtab.lookup("p7").is_pointer + assert not symtab.lookup("p7").is_target + assert isinstance(symtab.lookup("p7").datatype, ArrayType) + assert symtab.lookup("p7").datatype.intrinsic == ScalarType.Intrinsic.REAL + assert not symtab.lookup("p7").datatype.datatype.is_pointer + # Initialisations of static constant values (parameters) reader = FortranStringReader("integer, parameter :: i1 = 1") fparser2spec = Specification_Part(reader).content[0] @@ -860,7 +884,9 @@ def test_process_declarations(): processor.process_declarations(fake_parent, [fparser2spec], []) ptr_sym = fake_parent.symbol_table.lookup("dptr") assert isinstance(ptr_sym, DataSymbol) - assert isinstance(ptr_sym.datatype, UnsupportedFortranType) + assert isinstance(ptr_sym.datatype, ArrayType) + assert ptr_sym.datatype.intrinsic is ScalarType.Intrinsic.REAL + assert ptr_sym.datatype.is_pointer assert isinstance(ptr_sym.initial_value, CodeBlock) @@ -875,7 +901,7 @@ def test_process_declarations_unsupportedfortrantype(): symtab = fake_parent.symbol_table processor = Fparser2Reader() reader = FortranStringReader( - "integer, pointer :: l1 => null(), l2 => null()") + "integer, asynchronous :: l1 => null(), l2 => null()") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) for varname in ("l1", "l2"): @@ -1044,29 +1070,29 @@ def test_process_unsupported_declarations(fortran_reader): processor = Fparser2Reader() # Multiple symbols with a single attribute - reader = FortranStringReader("integer, private, pointer :: d, e") + reader = FortranStringReader("integer, private, asynchronous :: d, e") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) dsym = fake_parent.symbol_table.lookup("d") assert isinstance(dsym.datatype, UnsupportedFortranType) - assert dsym.datatype.declaration == "INTEGER, PRIVATE, POINTER :: d" + assert dsym.datatype.declaration == "INTEGER, PRIVATE, ASYNCHRONOUS :: d" esym = fake_parent.symbol_table.lookup("e") assert isinstance(esym.datatype, UnsupportedFortranType) - assert esym.datatype.declaration == "INTEGER, PRIVATE, POINTER :: e" + assert esym.datatype.declaration == "INTEGER, PRIVATE, ASYNCHRONOUS :: e" # Multiple attributes reader = FortranStringReader( - "INTEGER, PRIVATE, DIMENSION(3), POINTER :: f, g") + "INTEGER, PRIVATE, DIMENSION(3), ASYNCHRONOUS :: f, g") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) fsym = fake_parent.symbol_table.lookup("f") assert isinstance(fsym.datatype, UnsupportedFortranType) assert (fsym.datatype.declaration == - "INTEGER, PRIVATE, DIMENSION(3), POINTER :: f") + "INTEGER, PRIVATE, DIMENSION(3), ASYNCHRONOUS :: f") gsym = fake_parent.symbol_table.lookup("g") assert isinstance(gsym.datatype, UnsupportedFortranType) assert (gsym.datatype.declaration == - "INTEGER, PRIVATE, DIMENSION(3), POINTER :: g") + "INTEGER, PRIVATE, DIMENSION(3), ASYNCHRONOUS :: g") # Test with unsupported intrinsic type. Note the space before complex # below which stops the line being treated as a comment. @@ -1519,9 +1545,9 @@ def test_process_save_attribute_declarations(parser): assert isinstance(fake_parent.symbol_table.lookup("var4").interface, StaticInterface) - # Test that when it is part of an UnsupportedType (target attribute in - # this case) it becomes an UnknownInterface. - reader = FortranStringReader("integer, target :: var5") + # Test that when it is part of an UnsupportedType (asynchronous attribute + # in this case) it becomes an UnknownInterface. + reader = FortranStringReader("integer, volatile, save :: var5") fparser2spec = Specification_Part(reader).content[0] processor.process_declarations(fake_parent, [fparser2spec], []) assert isinstance(fake_parent.symbol_table.lookup("var5").datatype, @@ -1529,6 +1555,16 @@ def test_process_save_attribute_declarations(parser): assert isinstance(fake_parent.symbol_table.lookup("var5").interface, UnknownInterface) + # Test that it works in combination with another attribute, here target. + reader = FortranStringReader("integer, target, save :: var6") + fparser2spec = Specification_Part(reader).content[0] + processor.process_declarations(fake_parent, [fparser2spec], []) + assert (fake_parent.symbol_table.lookup("var6").datatype.intrinsic + == ScalarType.Intrinsic.INTEGER) + assert fake_parent.symbol_table.lookup("var6").is_target + assert isinstance(fake_parent.symbol_table.lookup("var6").interface, + StaticInterface) + @pytest.mark.usefixtures("f2008_parser") def test_process_declarations_intent(): @@ -1948,14 +1984,14 @@ def test_process_declarations_unrecognised_attribute(): a symbol with UnsupportedFortranType and the correct visibility. ''' fake_parent = KernelSchedule("dummy") processor = Fparser2Reader() - reader = FortranStringReader("integer, private, target :: idx1\n") + reader = FortranStringReader("integer, private, volatile :: idx1\n") fparser2spec = Specification_Part(reader) processor.process_declarations(fake_parent, fparser2spec.children, []) sym = fake_parent.symbol_table.lookup("idx1") assert isinstance(sym.datatype, UnsupportedFortranType) assert sym.visibility == Symbol.Visibility.PRIVATE # No access statement so should be public (the default in Fortran) - reader = FortranStringReader("integer, target :: idx2\n") + reader = FortranStringReader("integer, volatile :: idx2\n") fparser2spec = Specification_Part(reader) processor.process_declarations(fake_parent, fparser2spec.children, []) sym = fake_parent.symbol_table.lookup("idx2") @@ -1964,7 +2000,7 @@ def test_process_declarations_unrecognised_attribute(): # No access statement so should pick up the default visibility supplied # to the symbol table. fake_parent.symbol_table.default_visibility = Symbol.Visibility.PRIVATE - reader = FortranStringReader("integer, target :: idx3\n") + reader = FortranStringReader("integer, volatile :: idx3\n") fparser2spec = Specification_Part(reader) processor.process_declarations( fake_parent, fparser2spec.children, [], {}) @@ -1973,7 +2009,7 @@ def test_process_declarations_unrecognised_attribute(): assert sym.visibility == Symbol.Visibility.PRIVATE # No access statement but visibility provided in visibility_map argument # to process_declarations() - reader = FortranStringReader("integer, target :: idx4\n") + reader = FortranStringReader("integer, volatile :: idx4\n") fparser2spec = Specification_Part(reader) processor.process_declarations( fake_parent, fparser2spec.children, [], diff --git a/src/psyclone/tests/psyir/symbols/symbol_table_test.py b/src/psyclone/tests/psyir/symbols/symbol_table_test.py index 0df99b9b93..3496373ac4 100644 --- a/src/psyclone/tests/psyir/symbols/symbol_table_test.py +++ b/src/psyclone/tests/psyir/symbols/symbol_table_test.py @@ -2698,7 +2698,9 @@ def test_resolve_imports(fortran_reader, tmpdir, monkeypatch): subroutine.symbol_table.resolve_imports( symbol_target=subroutine.symbol_table.lookup('b_2')) assert isinstance(b_2, symbols.DataSymbol) - assert isinstance(b_2.datatype, symbols.UnsupportedFortranType) + assert isinstance(b_2.datatype, symbols.ScalarType) + assert b_2.datatype.intrinsic == symbols.ScalarType.Intrinsic.INTEGER + assert b_2.datatype.is_pointer assert isinstance(b_2.interface, symbols.ImportInterface) assert b_2.interface.container_symbol == \ subroutine.symbol_table.lookup('b_mod') @@ -3134,7 +3136,9 @@ def test_resolve_imports_from_child_symtab_uft( symbol = mod.symbol_table.lookup("some_var") # pylint: disable=unidiomatic-typecheck assert type(symbol) is symbols.DataSymbol - assert isinstance(symbol.datatype, symbols.UnsupportedFortranType) + assert isinstance(symbol.datatype, symbols.ScalarType) + assert symbol.datatype.intrinsic == symbols.ScalarType.Intrinsic.INTEGER + assert symbol.datatype.is_pointer assert isinstance(symbol.interface, symbols.ImportInterface) assert symbol.interface.container_symbol.name == "a_mod" @@ -3243,7 +3247,9 @@ def test_resolve_imports_from_child_symtabs_utf( symbol = mod.symbol_table.lookup("some_var") # pylint: disable=unidiomatic-typecheck assert type(symbol) is symbols.DataSymbol - assert isinstance(symbol.datatype, symbols.UnsupportedFortranType) + assert isinstance(symbol.datatype, symbols.ScalarType) + assert symbol.datatype.intrinsic == symbols.ScalarType.Intrinsic.INTEGER + assert symbol.datatype.is_pointer assert isinstance(symbol.interface, symbols.ImportInterface) assert symbol.interface.container_symbol.name == "a_mod" diff --git a/src/psyclone/tests/psyir/transformations/inline_trans_test.py b/src/psyclone/tests/psyir/transformations/inline_trans_test.py index cf66587a6d..356eed55de 100644 --- a/src/psyclone/tests/psyir/transformations/inline_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/inline_trans_test.py @@ -1652,7 +1652,7 @@ def test_validate_unsupportedtype_argument(fortran_reader): " call sub(ptr)\n" "end subroutine main\n" "subroutine sub(x)\n" - " real, pointer, intent(inout) :: x\n" + " real, volatile, intent(inout) :: x\n" " x = x + 1.0\n" "end subroutine sub\n" "end module test_mod\n" @@ -1663,7 +1663,7 @@ def test_validate_unsupportedtype_argument(fortran_reader): with pytest.raises(TransformationError) as err: inline_trans.validate(routine) assert ("Routine 'sub' cannot be inlined because it contains a Symbol 'x' " - "which is an Argument of UnsupportedType: 'REAL, POINTER, " + "which is an Argument of UnsupportedType: 'REAL, VOLATILE, " "INTENT(INOUT) :: x'" in str(err.value)) @@ -1680,7 +1680,7 @@ def test_validate_unknowninterface(fortran_reader, fortran_writer, tmpdir): " call sub()\n" "end subroutine main\n" "subroutine sub()\n" - " real, pointer :: x\n" + " real, volatile :: x\n" " x = x + 1.0\n" "end subroutine sub\n" "end module test_mod\n" @@ -1691,7 +1691,7 @@ def test_validate_unknowninterface(fortran_reader, fortran_writer, tmpdir): with pytest.raises(TransformationError) as err: inline_trans.validate(routine) assert (" Routine 'sub' cannot be inlined because it contains a Symbol " - "'x' with an UnknownInterface: 'REAL, POINTER :: x'" + "'x' with an UnknownInterface: 'REAL, VOLATILE :: x'" in str(err.value)) # But if the interface is known, it has no problem inlining it @@ -1700,7 +1700,7 @@ def test_validate_unknowninterface(fortran_reader, fortran_writer, tmpdir): inline_trans.apply(routine) assert fortran_writer(psyir.walk(Routine)[0]) == """\ subroutine main() - REAL, POINTER :: x + REAL, VOLATILE :: x x = x + 1.0 diff --git a/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py b/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py index 76b0d1d8a4..a198f364f3 100644 --- a/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py +++ b/src/psyclone/tests/psyir/transformations/intrinsics/matmul2code_trans_test.py @@ -628,10 +628,9 @@ def test_validate_matmat_with_same_mem(fortran_reader): assign = psyir.walk(Assignment)[0] with pytest.raises(TransformationError) as excinfo: trans.validate(assign.rhs) - assert ("Transformation Error: Must have full type information for result " - "and operands of MATMUL IntrinsicCall but found 'result: " - "DataSymbol Date: Tue, 3 Dec 2024 16:08:06 +0100 Subject: [PATCH 3/9] #2577 Declaration of pointer with initial value. --- src/psyclone/psyir/backend/fortran.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/psyclone/psyir/backend/fortran.py b/src/psyclone/psyir/backend/fortran.py index 73cbd48839..17a6f054ff 100644 --- a/src/psyclone/psyir/backend/fortran.py +++ b/src/psyclone/psyir/backend/fortran.py @@ -631,7 +631,10 @@ def gen_vardecl(self, symbol, include_visibility=False): f"value ({self._visit(symbol.initial_value)}) and " f"therefore (in Fortran) must have a StaticInterface. " f"However it has an interface of '{symbol.interface}'.") - result += " = " + self._visit(symbol.initial_value) + if symbol.is_pointer: + result += " => " + self._visit(symbol.initial_value) + else: + result += " = " + self._visit(symbol.initial_value) return result + "\n" From 45c4b353bb53040c3909f54063eb48ff316b9874 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Thu, 5 Dec 2024 16:49:32 +0100 Subject: [PATCH 4/9] #2577 Edit gocean tests w.r.t. new supported attributes. --- .../gocean/transformations/gocean_opencl_trans_test.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py b/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py index 1215cdb6e7..c2772064fa 100644 --- a/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py +++ b/src/psyclone/tests/domain/gocean/transformations/gocean_opencl_trans_test.py @@ -337,7 +337,7 @@ def test_invoke_opencl_initialisation_grid(): use ocl_utils_mod, only: check_status type(r2d_field), intent(inout), target :: field integer(kind=c_size_t) size_in_bytes - integer(kind=c_intptr_t), pointer :: cmd_queues(:) + integer(kind=c_intptr_t), dimension(:), pointer :: cmd_queues integer(kind=c_intptr_t) cl_mem integer ierr @@ -430,7 +430,7 @@ def test_opencl_routines_initialisation(kernel_outputdir): use clfortran use fortcl, only: get_cmd_queues type(c_ptr), intent(in) :: from - real(kind=go_wp), intent(inout), dimension(:, :), target :: to + real(kind=go_wp), dimension(:,:), intent(inout), target :: to integer, intent(in) :: startx integer, intent(in) :: starty integer, intent(in) :: nx @@ -439,7 +439,7 @@ def test_opencl_routines_initialisation(kernel_outputdir): integer(kind=c_size_t) size_in_bytes integer(kind=c_size_t) offset_in_bytes integer(kind=c_intptr_t) cl_mem - integer(kind=c_intptr_t), pointer :: cmd_queues(:) + integer(kind=c_intptr_t), dimension(:), pointer :: cmd_queues integer ierr integer i @@ -477,7 +477,7 @@ def test_opencl_routines_initialisation(kernel_outputdir): use kind_params_mod, only: go_wp use clfortran use fortcl, only: get_cmd_queues - real(kind=go_wp), intent(in), dimension(:, :), target :: from + real(kind=go_wp), dimension(:,:), intent(in), target :: from type(c_ptr), intent(in) :: to integer, intent(in) :: startx integer, intent(in) :: starty @@ -487,7 +487,7 @@ def test_opencl_routines_initialisation(kernel_outputdir): integer(kind=c_intptr_t) cl_mem integer(kind=c_size_t) size_in_bytes integer(kind=c_size_t) offset_in_bytes - integer(kind=c_intptr_t), pointer :: cmd_queues(:) + integer(kind=c_intptr_t), dimension(:), pointer :: cmd_queues integer ierr integer i From 1b6a426e2d86a7a5738fa6ae11d8e0480dd9b82e Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Thu, 5 Dec 2024 17:46:59 +0100 Subject: [PATCH 5/9] # 2577 Fix ArrayType copy. --- src/psyclone/psyir/symbols/datatypes.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index bbb03ef9f7..110d16d1e3 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -894,7 +894,8 @@ def copy(self): # This dimension is specified with an ArrayType.Extent # so no need to copy. new_shape.append(dim) - return ArrayType(self.datatype, new_shape) + array_copy = ArrayType(self.datatype, new_shape, self.is_pointer, self.is_target) + return array_copy def replace_symbols_using(self, table): ''' From d9f0a67ef234fb4ac5c6632b787c5a76267ec7db Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Thu, 5 Dec 2024 19:14:56 +0100 Subject: [PATCH 6/9] #2577 Fix structures and one test. Breaks get_callee for now. --- src/psyclone/psyir/frontend/fparser2.py | 3 ++- src/psyclone/tests/psyir/symbols/datatype_test.py | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/psyclone/psyir/frontend/fparser2.py b/src/psyclone/psyir/frontend/fparser2.py index 9365dc9faf..f6b9d2d42e 100644 --- a/src/psyclone/psyir/frontend/fparser2.py +++ b/src/psyclone/psyir/frontend/fparser2.py @@ -1697,7 +1697,8 @@ def _process_decln(self, scope, symbol_table, decl, visibility_map=None, has_pointer_attr = True elif normalized_string == "target": has_target_attr = True - elif isinstance(attr, Fortran2003.Attr_Spec): + elif isinstance(attr, (Fortran2003.Attr_Spec, + Fortran2003.Component_Attr_Spec)): normalized_string = str(attr).lower().replace(' ', '') if normalized_string == "save": if interface is not None: diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index 32b9ebe76b..1b96914ac4 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -822,7 +822,7 @@ def test_unsupported_fortran_type_copy(fortran_reader): subroutine test use some_mod, only: some_type, start, stop integer, parameter :: nelem = 4 - type(some_type), pointer :: var(nelem), var2(start:stop) + type(some_type), asynchronous :: var(nelem), var2(start:stop) end subroutine ''' psyir = fortran_reader.psyir_from_source(code) From 8052b645a1e455a790b215727c4eafdde0b1e3bd Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 9 Dec 2024 17:40:04 +0100 Subject: [PATCH 7/9] #2577 Move .is_unresolved w.r.t. Interface AND DataType to DataSymbol. --- src/psyclone/psyir/symbols/datasymbol.py | 13 +++++++++++++ src/psyclone/psyir/symbols/typed_symbol.py | 13 ------------- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index 27830cc5e4..9f57949772 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -363,3 +363,16 @@ def replace_symbols_using(self, table): # Ensure any Symbols referenced in the initial value are updated. if self.initial_value: self.initial_value.replace_symbols_using(table) + + @property + def is_unresolved(self): + ''' + :returns: whether the Symbol has an UnresolvedInterface or its + datatype is an UnresolvedType. + :rtype: bool + ''' + # Import here to avoid circular dependencies + # pylint: disable=import-outside-toplevel + from psyclone.psyir.symbols.datatypes import UnresolvedType + return (super().is_unresolved + or isinstance(self.datatype, UnresolvedType)) diff --git a/src/psyclone/psyir/symbols/typed_symbol.py b/src/psyclone/psyir/symbols/typed_symbol.py index d5fd96dce1..2fdfbbd679 100644 --- a/src/psyclone/psyir/symbols/typed_symbol.py +++ b/src/psyclone/psyir/symbols/typed_symbol.py @@ -62,19 +62,6 @@ def __init__(self, name, datatype, **kwargs): super(TypedSymbol, self).__init__(name) self._process_arguments(datatype=datatype, **kwargs) - @property - def is_unresolved(self): - ''' - :returns: whether the Symbol has an UnresolvedInterface or its - datatype is an UnresolvedType. - :rtype: bool - ''' - # Import here to avoid circular dependencies - # pylint: disable=import-outside-toplevel - from psyclone.psyir.symbols.datatypes import UnresolvedType - return (super().is_unresolved - or isinstance(self.datatype, UnresolvedType)) - @property def is_unsupported(self): ''' From 7c43dcf6a6dcd53e6c272d314af1200257296292 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Mon, 9 Dec 2024 18:23:20 +0100 Subject: [PATCH 8/9] #2577 Flake8 formatting --- src/psyclone/psyir/symbols/datatypes.py | 3 ++- src/psyclone/tests/psyir/frontend/fparser2_test.py | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index 110d16d1e3..1b2e1f400e 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -894,7 +894,8 @@ def copy(self): # This dimension is specified with an ArrayType.Extent # so no need to copy. new_shape.append(dim) - array_copy = ArrayType(self.datatype, new_shape, self.is_pointer, self.is_target) + array_copy = ArrayType(self.datatype, new_shape, self.is_pointer, + self.is_target) return array_copy def replace_symbols_using(self, table): diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index bd7a9b1970..5fa7afe2e6 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -610,7 +610,8 @@ def test_process_declarations(): @pytest.mark.usefixtures("f2008_parser") @pytest.mark.parametrize("decln_text", - ["integer, asynchronous :: l1 => null(), l2 => null()", + ["integer, asynchronous :: l1 => null()," + " l2 => null()", "integer, intent(in), optional :: l1, l2", "integer, asynchronous :: l1, l2"]) def test_process_declarations_unsupportedfortrantype(decln_text): From 32d451b0b8e1c5ae68d64e27794662267dde12a2 Mon Sep 17 00:00:00 2001 From: JulienRemy Date: Tue, 10 Dec 2024 13:52:30 +0100 Subject: [PATCH 9/9] #2577 More test coverage --- src/psyclone/psyir/symbols/datasymbol.py | 6 ---- src/psyclone/psyir/symbols/datatypes.py | 9 ++---- .../tests/psyir/frontend/fparser2_test.py | 21 ++++++++++++++ .../psyir/symbols/data_type_symbol_test.py | 29 +++++++++++++++++++ .../tests/psyir/symbols/datatype_test.py | 26 +++++++++++++++++ 5 files changed, 79 insertions(+), 12 deletions(-) diff --git a/src/psyclone/psyir/symbols/datasymbol.py b/src/psyclone/psyir/symbols/datasymbol.py index 9f57949772..27aff694b7 100644 --- a/src/psyclone/psyir/symbols/datasymbol.py +++ b/src/psyclone/psyir/symbols/datasymbol.py @@ -148,12 +148,6 @@ def _process_arguments(self, **kwargs): # would otherwise default to Automatic. self.interface = StaticInterface() - if self.is_constant and self.datatype.is_pointer: - # PARAMETER and POINTER are mutually exclusive in Fortran. - raise ValueError( - f"DataSymbol '{self.name}' is a constant and therefore cannot " - f"be a pointer.") - @property def is_constant(self): ''' diff --git a/src/psyclone/psyir/symbols/datatypes.py b/src/psyclone/psyir/symbols/datatypes.py index 1b2e1f400e..b677c2ef11 100644 --- a/src/psyclone/psyir/symbols/datatypes.py +++ b/src/psyclone/psyir/symbols/datatypes.py @@ -62,12 +62,12 @@ class DataType(metaclass=abc.ABCMeta): def __init__(self, is_pointer=False, is_target=False): if not isinstance(is_pointer, bool): raise TypeError(f"Expected 'is_pointer' to be a bool but got " - f"'{type(is_pointer)}'") + f"'{type(is_pointer).__name__}'") if not isinstance(is_target, bool): raise TypeError(f"Expected 'is_target' to be a bool but got " - f"'{type(is_target)}'") + f"'{type(is_target).__name__}'") if is_pointer and is_target: - raise ValueError("A datatype cannot be both a pointer and a " + raise ValueError("A DataType cannot be both a pointer and a " "target.") self._is_pointer = is_pointer self._is_target = is_target @@ -780,9 +780,6 @@ def _validate_data_node(dim_node, is_lower_bound=False): _validate_data_node(dimension) if ArrayType.Extent.DEFERRED in extents: - if self.is_pointer: - raise ValueError("An array with a deferred shape cannot be a " - "pointer.") if not all(dim == ArrayType.Extent.DEFERRED for dim in extents): raise TypeError( diff --git a/src/psyclone/tests/psyir/frontend/fparser2_test.py b/src/psyclone/tests/psyir/frontend/fparser2_test.py index 5fa7afe2e6..f3f8478aae 100644 --- a/src/psyclone/tests/psyir/frontend/fparser2_test.py +++ b/src/psyclone/tests/psyir/frontend/fparser2_test.py @@ -653,6 +653,27 @@ def test_process_declarations_errors(): assert ("SAVE and PARAMETER attributes are not compatible but found:\n " "INTEGER, PARAMETER, SAVE :: l1 = 1" in str(error.value)) + reader = FortranStringReader("real, pointer, target :: l1") + fparser2spec = Specification_Part(reader).content[0] + with pytest.raises(GenerationError) as error: + processor.process_declarations(fake_parent, [fparser2spec], []) + assert ("POINTER and TARGET attributes are not compatible but found:\n " + "REAL, POINTER, TARGET :: l1" in str(error.value)) + + reader = FortranStringReader("real, pointer, parameter :: l1 = 1.0") + fparser2spec = Specification_Part(reader).content[0] + with pytest.raises(GenerationError) as error: + processor.process_declarations(fake_parent, [fparser2spec], []) + assert ("POINTER and PARAMETER attributes are not compatible but found:\n " + "REAL, POINTER, PARAMETER :: l1 = 1.0" in str(error.value)) + + reader = FortranStringReader("real, pointer, allocatable :: l1") + fparser2spec = Specification_Part(reader).content[0] + with pytest.raises(GenerationError) as error: + processor.process_declarations(fake_parent, [fparser2spec], []) + assert ("ALLOCATABLE and POINTER attributes are not compatible but found:" + "\n REAL, POINTER, ALLOCATABLE :: l1" in str(error.value)) + reader = FortranStringReader("integer, parameter, intent(in) :: l1 = 1") fparser2spec = Specification_Part(reader).content[0] with pytest.raises(GenerationError) as error: 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..537f483f57 100644 --- a/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py +++ b/src/psyclone/tests/psyir/symbols/data_type_symbol_test.py @@ -51,6 +51,35 @@ def test_create_datatypesymbol(): assert isinstance(sym.datatype, UnresolvedType) assert str(sym) == "my_type: DataTypeSymbol" + with pytest.raises(TypeError) as err: + _ = DataTypeSymbol("my_type", UnresolvedType(), is_pointer="true", + is_target=False) + assert ("is_pointer should be a boolean but found 'str'" in str(err.value)) + + with pytest.raises(TypeError) as err: + _ = DataTypeSymbol("my_type", UnresolvedType(), is_pointer=True, + is_target="false") + assert ("is_target should be a boolean but found 'str'" in str(err.value)) + + # Impossible combination of is_pointer and is_target + with pytest.raises(ValueError) as err: + _ = DataTypeSymbol("my_type", UnresolvedType(), is_pointer=True, + is_target=True) + assert ("A symbol cannot be both a pointer and a target" in str(err.value)) + + # Possible combinations of is_pointer and is_target + _ = DataTypeSymbol("my_type", UnresolvedType(), is_pointer=True, + is_target=False) + _ = DataTypeSymbol("my_type", UnresolvedType(), is_pointer=False, + is_target=True) + _ = DataTypeSymbol("my_type", UnresolvedType(), is_pointer=False, + is_target=False) + + # Default values of is_pointer and is_target + sym = DataTypeSymbol("my_type", UnresolvedType()) + assert not sym.is_pointer + assert not sym.is_target + def test_create_datatypesymbol_wrong_datatype(): ''' Check that attempting to specify the type of a DataTypeSymbol with an diff --git a/src/psyclone/tests/psyir/symbols/datatype_test.py b/src/psyclone/tests/psyir/symbols/datatype_test.py index 1b96914ac4..adda4b10c5 100644 --- a/src/psyclone/tests/psyir/symbols/datatype_test.py +++ b/src/psyclone/tests/psyir/symbols/datatype_test.py @@ -66,6 +66,32 @@ def test_datatype(): assert ("__str__" in msg) +def test_datatype_is_pointer_is_target(): + '''Test that the is_pointer and is_target attributes raise the expected + exceptions when instantiating (a subclass of) DataType.''' + with pytest.raises(TypeError) as excinfo: + _ = UnresolvedType(is_pointer="true", is_target=False) + assert ("Expected 'is_pointer' to be a bool but got 'str'" in + str(excinfo.value)) + with pytest.raises(TypeError) as excinfo: + _ = UnresolvedType(is_pointer=True, is_target="false") + assert ("Expected 'is_target' to be a bool but got 'str'" in + str(excinfo.value)) + # Impossible combination + with pytest.raises(ValueError) as excinfo: + _ = UnresolvedType(is_pointer=True, is_target=True) + assert ("A DataType cannot be both a pointer and a target." in + str(excinfo.value)) + # Possible combinations + _ = UnresolvedType(is_pointer=False, is_target=False) + _ = UnresolvedType(is_pointer=True, is_target=False) + _ = UnresolvedType(is_pointer=False, is_target=True) + # Default values + dtype = UnresolvedType() + assert not dtype.is_pointer + assert not dtype.is_target + + # UnresolvedType class def test_unresolvedtype():