Reference Guide  2.5.0
array_mixin.py
1 # -----------------------------------------------------------------------------
2 # BSD 3-Clause License
3 #
4 # Copyright (c) 2021-2024, Science and Technology Facilities Council.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are met:
9 #
10 # * Redistributions of source code must retain the above copyright notice, this
11 # list of conditions and the following disclaimer.
12 #
13 # * Redistributions in binary form must reproduce the above copyright notice,
14 # this list of conditions and the following disclaimer in the documentation
15 # and/or other materials provided with the distribution.
16 #
17 # * Neither the name of the copyright holder nor the names of its
18 # contributors may be used to endorse or promote products derived from
19 # this software without specific prior written permission.
20 #
21 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
22 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
23 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
24 # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
25 # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
26 # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
27 # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
28 # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
30 # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
31 # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
32 # POSSIBILITY OF SUCH DAMAGE.
33 # -----------------------------------------------------------------------------
34 # Authors R. W. Ford, A. R. Porter, S. Siso and N. Nobre, STFC Daresbury Lab
35 # I. Kavcic, Met Office
36 # J. Henrichs, Bureau of Meteorology
37 # Modified: A. B. G. Chalk, STFC Daresbury Lab
38 # -----------------------------------------------------------------------------
39 
40 ''' This module contains the implementation of the abstract ArrayMixin. '''
41 
42 import abc
43 
44 from psyclone.core import SymbolicMaths
45 from psyclone.errors import InternalError
46 from psyclone.psyir.nodes.call import Call
47 from psyclone.psyir.nodes.codeblock import CodeBlock
48 from psyclone.psyir.nodes.datanode import DataNode
49 from psyclone.psyir.nodes.intrinsic_call import IntrinsicCall
50 from psyclone.psyir.nodes.literal import Literal
51 from psyclone.psyir.nodes.member import Member
52 from psyclone.psyir.nodes.operation import Operation, BinaryOperation
53 from psyclone.psyir.nodes.ranges import Range
54 from psyclone.psyir.nodes.reference import Reference
55 from psyclone.psyir.symbols import DataSymbol, DataTypeSymbol
57  ScalarType, ArrayType, UnresolvedType, UnsupportedType, INTEGER_TYPE)
58 
59 
60 class ArrayMixin(metaclass=abc.ABCMeta):
61  '''
62  Abstract class used to add functionality common to Nodes that represent
63  Array accesses.
64 
65  '''
66  @staticmethod
67  def _validate_child(position, child):
68  '''
69  :param int position: the position to be validated.
70  :param child: a child to be validated.
71  :type child: :py:class:`psyclone.psyir.nodes.Node`
72 
73  :return: whether the given child and position are valid for this node.
74  :rtype: bool
75 
76  '''
77  # pylint: disable=unused-argument
78  return isinstance(child, (DataNode, Range))
79 
80  @property
81  def is_array(self):
82  ''':returns: if this instance indicates an array access.
83  :rtype: bool
84 
85  '''
86  return True
87 
89  '''
90  Constructs the Signature of this array access and a list of the
91  indices used.
92 
93  :returns: the Signature of this array reference, and \
94  a list of the indices used for each component (empty list \
95  if an access is not an array). In this base class there is \
96  no other component, so it just returns a list with a list \
97  of all indices.
98  :rtype: tuple(:py:class:`psyclone.core.Signature`, list of \
99  lists of indices)
100  '''
101  sig, _ = super().get_signature_and_indices()
102  return (sig, [self.indicesindices[:]])
103 
104  def _validate_index(self, index):
105  '''Utility function that checks that the supplied index is an integer
106  and is less than the number of array dimensions.
107 
108  :param int index: the array index to check.
109 
110  :raises TypeError: if the index argument is not an integer.
111  :raises ValueError: if the index value is greater than the \
112  number of dimensions in the array (-1).
113 
114  '''
115  if not isinstance(index, int):
116  raise TypeError(
117  f"The index argument should be an integer but found "
118  f"'{type(index).__name__}'.")
119  if index > len(self.indicesindices)-1:
120  raise ValueError(
121  f"In '{type(self).__name__}' '{self.name}' the specified "
122  f"index '{index}' must be less than the number of dimensions "
123  f"'{len(self.indices)}'.")
124 
125  def _is_bound_op(self, expr, bound_operator, index):
126  '''Utility function that checks that the provided 'expr' argument is
127  in the form '[UL]BOUND(array_name, index)', where the type of
128  bound operation is determined by the 'bound_operator'
129  argument, array_name is the name of this array and the 'index'
130  argument provides the index value.
131 
132  :param expr: a PSyIR expression.
133  :type expr: :py:class:`psyclone.psyir.nodes.Node`
134  :param bound_operator: the particular bound operation.
135  :type bound_operator:
136  :py:class:`psyclone.psyir.nodes.IntrinsicCall.Intrinsic.LBOUND` |
137  :py:class:`psyclone.psyir.nodes.IntrinsicCall.Intrinsic.UBOUND`
138  :param int index: the bounds index.
139 
140  :returns: True if the expr is in the expected form and False otherwise.
141  :rtype: bool
142 
143  '''
144  if (isinstance(expr, IntrinsicCall) and
145  expr.intrinsic == bound_operator):
146  # This is the expected bound
147  if self.is_same_arrayis_same_array(expr.arguments[0]):
148  # The arrays match
149  if (isinstance(expr.arguments[1], Literal) and
150  expr.arguments[1].datatype.intrinsic ==
151  ScalarType.Intrinsic.INTEGER
152  and expr.arguments[1].value == str(index+1)):
153  # This is the correct index
154  return True
155  return False
156 
157  def is_lower_bound(self, index):
158  '''Returns whether this array access includes the lower bound of the
159  array for the specified index. Returns True if it is and False
160  if it is not or if it could not be determined.
161 
162  :param int index: the array index to check.
163 
164  :returns: True if it can be determined that the lower bound of \
165  the array is accessed in this array reference for the \
166  specified index.
167  :rtype: bool
168 
169  '''
170  return self._is_bound_is_bound(index, "lower")
171 
172  def _get_bound_expression(self, pos, bound):
173  '''
174  Lookup the upper or lower bound of this ArrayMixin.
175 
176  :param int pos: the dimension of the array for which to lookup the
177  lower bound.
178  :param str bound: "upper" or "lower" - the bound which to lookup.
179 
180  :returns: the declared bound for the specified dimension of this array
181  or a call to the {U/L}BOUND intrinsic if it is unknown.
182  :rtype: :py:class:`psyclone.psyir.nodes.Node`
183 
184  :raises InternalError: if bound is neither upper or lower.
185  '''
186  if bound not in ("upper", "lower"):
187  raise InternalError(f"'bound' argument must be 'lower' or 'upper. "
188  f"Found '{bound}'.")
189 
190  # First, walk up to the parent reference and get its type. For a simple
191  # ArrayReference this will just be self.
192  root_ref = self.ancestor(Reference, include_self=True)
193  cursor_type = root_ref.symbol.datatype
194 
195  # Walk back down the structure, looking up the type information as we
196  # go. We also collect the necessary information for creating a new
197  # Reference as argument to the {U/L}BOUND intrinsic in case the type
198  # information is not available.
199  cnames = []
200  cursor = root_ref
201  while cursor is not self:
202  cursor = cursor.member
203  # Collect member information.
204  if isinstance(cursor, ArrayMixin):
205  new_indices = [idx.copy() for idx in cursor.indices]
206  cnames.append((cursor.name.lower(), new_indices))
207  else:
208  cnames.append(cursor.name.lower())
209  # Continue to resolve datatype unless we hit an
210  # UnsupportedType or UnresolvedType.
211  if isinstance(cursor_type, ArrayType):
212  cursor_type = cursor_type.intrinsic
213  if isinstance(cursor_type, DataTypeSymbol):
214  cursor_type = cursor_type.datatype
215  if isinstance(cursor_type, (UnsupportedType, UnresolvedType)):
216  continue
217  cursor_type = cursor_type.components[cursor.name.lower()].datatype
218 
219  if (isinstance(cursor_type, ArrayType) and
220  cursor_type.shape[pos] not in [ArrayType.Extent.DEFERRED,
221  ArrayType.Extent.ATTRIBUTE]):
222  # We have the full type information and the bound is known.
223  if bound == "lower":
224  return cursor_type.shape[pos].lower.copy()
225  return cursor_type.shape[pos].upper.copy()
226 
227  # We've either failed to resolve the type or we don't know the extent
228  # of the array dimension so construct a call to the BOUND intrinsic.
229  if cnames:
230  # We have some sort of structure access - remove any indexing
231  # information from the ultimate member of the structure access.
232  if len(cnames[-1]) == 2:
233  cnames[-1] = cnames[-1][0]
234  # Have to import here to avoid circular dependencies.
235  # pylint: disable=import-outside-toplevel
236  from psyclone.psyir.nodes import (ArrayOfStructuresReference,
237  StructureReference)
238  if isinstance(root_ref, ArrayMixin):
239  new_indices = [idx.copy() for idx in root_ref.indices]
240  ref = ArrayOfStructuresReference.create(
241  root_ref.symbol, new_indices, cnames)
242  else:
243  ref = StructureReference.create(root_ref.symbol, cnames)
244  else:
245  # A simple Reference.
246  ref = Reference(root_ref.symbol)
247 
248  if bound == "lower":
249  return IntrinsicCall.create(
250  IntrinsicCall.Intrinsic.LBOUND,
251  [ref, ("dim", Literal(str(pos+1), INTEGER_TYPE))])
252  return IntrinsicCall.create(
253  IntrinsicCall.Intrinsic.UBOUND,
254  [ref, ("dim", Literal(str(pos+1), INTEGER_TYPE))])
255 
256  def get_lbound_expression(self, pos):
257  '''
258  Lookup the lower bound of this ArrayMixin. If we don't have the
259  necessary type information then a call to the LBOUND intrinsic is
260  constructed and returned.
261 
262  :param int pos: the dimension of the array for which to lookup the
263  lower bound.
264 
265  :returns: the declared lower bound for the specified dimension of
266  the array accesed or a call to the LBOUND intrinsic if it is
267  unknown.
268  :rtype: :py:class:`psyclone.psyir.nodes.Node`
269 
270  '''
271  self._validate_index_validate_index(pos)
272  # Call the helper function
273  return self._get_bound_expression_get_bound_expression(pos, "lower")
274 
275  def get_ubound_expression(self, pos):
276  '''
277  Lookup the upper bound of this ArrayMixin. If we don't have the
278  necessary type information then a call to the UBOUND intrinsic is
279  constructed and returned.
280 
281  :param int pos: the dimension of the array for which to lookup the
282  upper bound.
283 
284  :returns: the declared upper bound for the specified dimension of
285  the array accesed or a call to the UBOUND intrinsic if it is
286  unknown.
287  :rtype: :py:class:`psyclone.psyir.nodes.Node`
288 
289  '''
290  self._validate_index_validate_index(pos)
291  # Call the helper function
292  return self._get_bound_expression_get_bound_expression(pos, "upper")
293 
294  def get_full_range(self, pos):
295  '''
296  Returns a Range object that covers the full indexing of the dimension
297  specified by pos for this ArrayMixin object.
298 
299  :param int pos: the dimension of the array for which to lookup the
300  upper bound.
301 
302  :returns: A Range representing the full range for the dimension of
303  pos for this ArrayMixin.
304  :rtype: :py:class:`psyclone.psyir.nodes.Range`
305  '''
306  self._validate_index_validate_index(pos)
307 
308  lbound = self.get_lbound_expressionget_lbound_expression(pos)
309  ubound = self.get_ubound_expressionget_ubound_expression(pos)
310 
311  return Range.create(lbound, ubound)
312 
313  def is_upper_bound(self, index):
314  '''Returns whether this array access includes the upper bound of
315  the array for the specified index. Returns True if it is and False
316  if it is not or if it could not be determined.
317 
318  :param int index: the array index to check.
319 
320  :returns: True if it can be determined that the upper bound of \
321  the array is accessed in this array reference for the \
322  specified index.
323  :rtype: bool
324 
325  '''
326  return self._is_bound_is_bound(index, "upper")
327 
328  def _is_bound(self, index, bound_type):
329  '''Attempts to determines whether this array access includes the lower
330  or upper bound (as specified by the bound_type argument).
331 
332  Checks whether the specified array index contains a Range node
333  which has a starting/stopping value given by the
334  '{LU}BOUND(name,index)' intrinsic where 'name' is the name of
335  the current ArrayReference and 'index' matches the specified
336  array index. Also checks if the starting/stopping value of the
337  access matches the lower/upper value of the declaration.
338 
339  For example, if a Fortran array A was declared as A(n) then
340  the stopping value is n and A(:UBOUND(A,1)), A(:n) or A(n)
341  would access that value. The starting value is 1 and
342  A(LBOUND(A,1):), A(1:) or A(1) would access that value.
343 
344  :param int index: the array index to check.
345  :param str bound_type: the type of bound ("lower" or "upper")
346 
347  :returns: True if the array index access includes the \
348  lower/upper bound of the array declaration and False if it \
349  does not or if it can't be determined.
350  :rtype: bool
351 
352  '''
353  self._validate_index_validate_index(index)
354 
355  access_shape = self.indicesindices[index]
356 
357  # Determine the appropriate (lower or upper) bound and check
358  # for a bounds intrinsic.
359  if isinstance(access_shape, Range):
360  if bound_type == "upper":
361  intrinsic = IntrinsicCall.Intrinsic.UBOUND
362  access_bound = access_shape.stop
363  else:
364  intrinsic = IntrinsicCall.Intrinsic.LBOUND
365  access_bound = access_shape.start
366 
367  # Is this array access in the form of {UL}BOUND(array, index)?
368  if self._is_bound_op_is_bound_op(access_bound, intrinsic, index):
369  return True
370  else:
371  access_bound = access_shape
372 
373  # Try to compare the upper/lower bound of the array access
374  # with the upper/lower bound of the array declaration.
375 
376  # Finding the array declaration is only supported for an
377  # ArrayReference at the moment.
378  # Import here to avoid circular dependence.
379  # pylint: disable=import-outside-toplevel
380  from psyclone.psyir.nodes.array_reference import ArrayReference
381  if not isinstance(self, ArrayReference):
382  return False
383  # pylint: enable=import-outside-toplevel
384 
385  symbol = self.symbol
386  if not isinstance(symbol, DataSymbol):
387  # There is no type information for this symbol
388  # (probably because it originates from a wildcard import).
389  return False
390  datatype = symbol.datatype
391 
392  if not isinstance(datatype, ArrayType):
393  # The declaration datatype could be of UnsupportedFortranType
394  # if the symbol is of e.g. character type.
395  return False
396 
397  # The bound of the declaration is available.
398 
399  if isinstance(datatype.shape[index], ArrayType.Extent):
400  # The size is unspecified at compile-time (but is
401  # available at run-time e.g. when the size is allocated by
402  # an allocate statement.
403  return False
404 
405  # The size of the bound is available.
406  if bound_type == "upper":
407  declaration_bound = datatype.shape[index].upper
408  else:
409  declaration_bound = datatype.shape[index].lower
410 
411  # Do the bounds match?
412  sym_maths = SymbolicMaths.get()
413  return sym_maths.equal(declaration_bound, access_bound)
414 
415  def is_same_array(self, node):
416  '''
417  Checks that the provided array is the same as this node (including the
418  chain of parent accessor expressions if the array is part of a
419  Structure). If the array is part of a structure then any indices on
420  the innermost member access are ignored, e.g.
421  A(3)%B%C(1) will match with A(3)%B%C but not with A(2)%B%C(1)
422 
423  :param node: the node representing the access that is to be compared \
424  with this node.
425  :type node: :py:class:`psyclone.psyir.nodes.Reference` or \
426  :py:class:`psyclone.psyir.nodes.Member`
427 
428  :returns: True if the array accesses match, False otherwise.
429  :rtype: bool
430 
431  '''
432  if not isinstance(node, (Member, Reference)):
433  return False
434 
435  if isinstance(self, Member):
436  # This node is somewhere within a structure access so we need to
437  # get the parent Reference and keep a record of how deep this node
438  # is within the structure access. e.g. if this node was the
439  # StructureMember 'b' in a%c%b%d then its depth would be 2.
440  depth = 1
441  current = self
442  while current.parent and not isinstance(current.parent, Reference):
443  depth += 1
444  current = current.parent
445  parent_ref = current.parent
446  if not parent_ref:
447  return False
448  else:
449  depth = 0
450  parent_ref = self
451 
452  # Now we have the parent Reference and the depth, we can construct the
453  # Signatures and compare them to the required depth.
454  self_sig, self_indices = parent_ref.get_signature_and_indices()
455  node_sig, node_indices = node.get_signature_and_indices()
456  if self_sig[:depth+1] != node_sig[:]:
457  return False
458 
459  # Examine the indices, ignoring any on the innermost accesses (hence
460  # the slice to `depth` rather than `depth + 1` below).
461  for idx1, idx2 in zip(self_indices[:depth], node_indices[:depth]):
462  if idx1 != idx2:
463  return False
464  return True
465 
466  def is_full_range(self, index):
467  '''Returns True if the specified array index is a Range Node that
468  specifies all elements in this index. In the PSyIR this is
469  specified by using LBOUND(name,index) for the lower bound of
470  the range, UBOUND(name,index) for the upper bound of the range
471  and "1" for the range step.
472 
473  :param int index: the array index to check.
474 
475  :returns: True if the access to this array index is a range \
476  that specifies all index elements. Otherwise returns \
477  False.
478  :rtype: bool
479 
480  '''
481  self._validate_index_validate_index(index)
482 
483  array_dimension = self.indicesindices[index]
484  if isinstance(array_dimension, Range):
485  if self.is_lower_boundis_lower_bound(index) and self.is_upper_boundis_upper_bound(index):
486  step = array_dimension.children[2]
487  if (isinstance(step, Literal) and
488  step.datatype.intrinsic == ScalarType.Intrinsic.INTEGER
489  and str(step.value) == "1"):
490  return True
491  return False
492 
493  @property
494  def indices(self):
495  '''
496  Supports semantic-navigation by returning the list of nodes
497  representing the index expressions for this array reference.
498 
499  :returns: the PSyIR nodes representing the array-index expressions.
500  :rtype: list of :py:class:`psyclone.psyir.nodes.Node`
501 
502  :raises InternalError: if this node has no children or if they are \
503  not valid array-index expressions.
504 
505  '''
506  if not self._children:
507  raise InternalError(
508  f"{type(self).__name__} malformed or incomplete: must have "
509  f"one or more children representing array-index expressions "
510  f"but array '{self.name}' has none.")
511  for idx, child in enumerate(self._children):
512  if not self._validate_child_validate_child(idx, child):
513  raise InternalError(
514  f"{type(self).__name__} malformed or incomplete: child "
515  f"{idx} of array '{self.name}' must be a psyir.nodes."
516  f"DataNode or Range representing an array-index "
517  f"expression but found '{type(child).__name__}'")
518  return self.children
519 
520  def _extent(self, idx):
521  '''
522  Create PSyIR for the number of elements in dimension `idx` of this
523  array access. It is given by (stop - start)/step + 1 or, if it is for
524  the full range, by the SIZE intrinsic.
525 
526  :param int idx: the array index for which to compute the number of
527  elements.
528 
529  :returns: the PSyIR expression for the number of elements in the
530  specified array index.
531  :rtype: :py:class:`psyclone.psyir.nodes.BinaryOperation` |
532  :py:class:`psyclone.psyir.nodes.IntrinsicCall`
533  '''
534  expr = self.indicesindices[idx]
535  one = Literal("1", INTEGER_TYPE)
536 
537  if isinstance(expr, Range):
538  start = expr.start
539  stop = expr.stop
540  step = expr.step
541  else:
542  # No range so just a single element is accessed.
543  return one
544 
545  if (isinstance(start, IntrinsicCall) and
546  isinstance(stop, IntrinsicCall) and self.is_full_rangeis_full_range(idx)):
547  # Access is to full range and start and stop are expressed in terms
548  # of LBOUND and UBOUND. Therefore, it's simpler to use SIZE.
549  return IntrinsicCall.create(
550  IntrinsicCall.Intrinsic.SIZE,
551  [start.arguments[0].copy(),
552  ("dim", Literal(str(idx+1), INTEGER_TYPE))])
553 
554  if start == one and step == one:
555  # The range starts at 1 and the step is 1 so the extent is just
556  # the upper bound.
557  return stop.copy()
558 
559  extent = BinaryOperation.create(BinaryOperation.Operator.SUB,
560  stop.copy(), start.copy())
561  if step != one:
562  # Step is not unity so have to divide range by it.
563  result = BinaryOperation.create(BinaryOperation.Operator.DIV,
564  extent, step.copy())
565  else:
566  result = extent
567  # Extent is currently 'stop-start' or '(stop-start)/step' so we have
568  # to add a '+ 1'
569  return BinaryOperation.create(BinaryOperation.Operator.ADD,
570  result, one.copy())
571 
572  def _get_effective_shape(self):
573  '''
574  :returns: the shape of the array access represented by this node.
575  :rtype: list[:py:class:`psyclone.psyir.nodes.DataNode`]
576 
577  :raises NotImplementedError: if any of the array-indices involve a
578  function call or an expression.
579  '''
580  shape = []
581  for idx, idx_expr in enumerate(self.indicesindices):
582  if isinstance(idx_expr, Range):
583  shape.append(self._extent_extent(idx))
584 
585  elif isinstance(idx_expr, Reference):
586  dtype = idx_expr.datatype
587  if isinstance(dtype, ArrayType):
588  # An array slice can be defined by a 1D slice of another
589  # array, e.g. `a(b(1:4))`.
590  indirect_array_shape = dtype.shape
591  if len(indirect_array_shape) > 1:
592  raise InternalError(
593  f"An array defining a slice of a dimension of "
594  f"another array must be 1D but '{idx_expr.name}' "
595  f"used to index into '{self.name}' has "
596  f"{len(indirect_array_shape)} dimensions.")
597  # pylint: disable=protected-access
598  shape.append(idx_expr._extent(idx))
599 
600  elif isinstance(idx_expr, (Call, Operation, CodeBlock)):
601  # We can't yet straightforwardly query the type of a function
602  # call or Operation - TODO #1799.
603  raise NotImplementedError(
604  f"The array index expressions for access "
605  f"'{self.debug_string()}' include a function call or "
606  f"expression. Querying the return type of "
607  f"such things is yet to be implemented.")
608 
609  return shape
610 
612  ''' Return the index of the child that represents the outermost
613  array dimension with a Range construct.
614 
615  :returns: the outermost index of the children that is a Range node.
616  :rtype: int
617 
618  :raises IndexError: if the array does not contain a Range node.
619 
620  '''
621  for child in reversed(self.indicesindices):
622  if isinstance(child, Range):
623  return child.position
624  raise IndexError
625 
626  def same_range(self, index: int, array2, index2: int) -> bool:
627  ''' This method compares the range of this array node at a given index
628  with the range of a second array at a second index. This is useful to
629  verify if array operations are valid, e.g.: A(3,:,5) + B(:,2,2).
630 
631  Note that this check supports symbolic comparisons, e.g.:
632  A(3:4) has the same range as B(2+1:5-1),
633  and will consider compile-time unknown dimensions as equal, e.g.:
634  A(:) has the same range as B(:).
635 
636  TODO #2485. This method has false negatives: cases when the range
637  is the same but it can not be proved, so we return False.
638 
639  TODO #2004. This method currently compares exact ranges, not just the
640  length of them, which could be done with "(upper-lower)/step" symbolic
641  comparisons. This is because arrayrange2loop does not account for
642  arrays declared with different lbounds, but this could be improved.
643 
644  :param index: the index indicating the location of a range node in
645  this array.
646  :param array2: the array accessor that we want to compare it to.
647  :param index2: the index indicating the location of a range node in
648  array2.
649 
650  :returns: True if the ranges are the same and False if they are not
651  the same, or if it is not possible to determine.
652 
653  :raises: TypeError if any of the arguments are of the wrong type.
654 
655  '''
656  # pylint: disable=too-many-branches
657  if not isinstance(index, int):
658  raise TypeError(
659  f"The 'index' argument of the same_range() method should be an"
660  f" int but found '{type(index).__name__}'.")
661  if not isinstance(array2, ArrayMixin):
662  raise TypeError(
663  f"The 'array2' argument of the same_range() method should be "
664  f"an ArrayMixin but found '{type(array2).__name__}'.")
665  if not isinstance(index2, int):
666  raise TypeError(
667  f"The 'index2' argument of the same_range() method should be "
668  f"an int but found '{type(index2).__name__}'.")
669  if not index < len(self.children):
670  raise IndexError(
671  f"The value of the 'index' argument of the same_range() method"
672  f" is '{index}', but it should be less than the number of "
673  f"dimensions in the associated array, which is "
674  f"'{len(self.children)}'.")
675  if not index2 < len(array2.children):
676  raise IndexError(
677  f"The value of the 'index2' argument of the same_range() "
678  f"method is '{index2}', but it should be less than the number"
679  f" of dimensions in the associated array 'array2', which is "
680  f"'{len(array2.children)}'.")
681  if not isinstance(self.children[index], Range):
682  raise TypeError(
683  f"The child of the first array argument at the specified index"
684  f" '{index}' should be a Range node, but found "
685  f"'{type(self.children[index]).__name__}'.")
686  if not isinstance(array2.children[index2], Range):
687  raise TypeError(
688  f"The child of the second array argument at the specified "
689  f"index '{index2}' should be a Range node, but found "
690  f"'{type(array2.children[index2]).__name__}'.")
691 
692  range1 = self.children[index]
693  range2 = array2.children[index2]
694 
695  sym_maths = SymbolicMaths.get()
696  # compare lower bounds
697  if self.is_lower_boundis_lower_bound(index) and array2.is_lower_bound(index2):
698  # Both self and array2 use the lbound() intrinsic to
699  # specify the lower bound of the array dimension. We may
700  # not be able to determine what the lower bounds of these
701  # arrays are statically but at runtime the code will fail
702  # if the ranges do not match so we assume that the lower
703  # bounds are consistent.
704  pass
705  elif self.is_lower_boundis_lower_bound(index) or array2.is_lower_bound(index2):
706  # One and only one of self and array2 use the lbound()
707  # intrinsic to specify the lower bound of the array
708  # dimension. In this case assume that the ranges are
709  # different (although they could potentially be the same).
710  return False
711  elif not sym_maths.equal(range1.start, range2.start):
712  # Neither self nor array2 use the lbound() intrinsic to
713  # specify the lower bound of the array dimension. Try to
714  # determine if they are the same by matching the
715  # text. Use symbolic maths to do the comparison.
716  return False
717 
718  # compare upper bounds
719  if self.is_upper_boundis_upper_bound(index) and array2.is_upper_bound(index2):
720  # Both self and array2 use the ubound() intrinsic to
721  # specify the upper bound of the array dimension. We may
722  # not be able to determine what the upper bounds of these
723  # arrays are statically but at runtime the code will fail
724  # if the ranges do not match so we assume that the upper
725  # bounds are consistent.
726  pass
727  elif self.is_upper_boundis_upper_bound(index) or array2.is_upper_bound(index2):
728  # One and only one of self and array2 use the ubound()
729  # intrinsic to specify the upper bound of the array
730  # dimension. In this case assume that the ranges are
731  # different (although they could potentially be the same).
732  return False
733  elif not sym_maths.equal(range1.stop, range2.stop):
734  # Neither self nor array2 use the ubound() intrinsic to
735  # specify the upper bound of the array dimension. Use
736  # symbolic maths to check if they are equal.
737  return False
738 
739  # compare steps
740  if not sym_maths.equal(range1.step, range2.step):
741  return False
742 
743  # Everything matches.
744  return True
745 
746 
747 # For AutoAPI documentation generation
748 __all__ = ['ArrayMixin']
bool same_range(self, int index, array2, int index2)
Definition: array_mixin.py:626
def _is_bound_op(self, expr, bound_operator, index)
Definition: array_mixin.py:125
def _is_bound(self, index, bound_type)
Definition: array_mixin.py:328