AI22-0100-1

!standard 4.2(10/5)                                        24-04-03  AI22-0100-1/02

!standard 4.2(11/5)

!standard 4.3.3(26)

!standard 4.3.3(26.1/5)

!standard 4.3.3(28)

!class binding interpretation 24-03-28

!status No Action  14-0-0   24-05-02

!status work item 24-03-28

!status received 24-03-28

!priority Low

!difficulty Easy

!qualifier Omission

!subject Bounds of null and positional array aggregates

!summary

!summary

The upper bound of a null_array_aggregate or positional_array_aggregate is determined by using the 'Pred and 'Succ operations.

For a null_array_aggregate or positional_array_aggregate with a modular index type, a check is needed to ensure that the length represented by the determined bounds is the one appropriate for the given aggregate.

!issue

There is a check defined for a null string literal that the bounds make sense (see 4.2(11/5)). This is needed when the lower bound is Index_Subtype'Base'First. It would seem that a similar check is needed for a null_array_aggregate, but none is defined. Additionally, the upper bound of a null string literal is defined explicitly. The duplication of the rules is uncomfortable.

This brings up a more general problem. There is no rule anywhere that explains what happens when the upper bound of a positional_array_aggregate is not representable in the index type. A related problem is that the upper bound might not make sense for a modular type, which has wrapping semantics. Should we add rules? (Yes.)

!recommendation

(See Summary.)

!wording

Modify 4.2(10/5):

The evaluation of a string_literal that is a primary and has an expected type that is a string type, yields an array value containing the value of each character of the sequence of characters of the string_literal, as defined in 2.6. The bounds of this array value are determined according to the rules for positional_array_aggregates {without an others choice} (see 4.3.3), except that for a null string literal, the {rules of null_array_aggregates are used instead}[upper bound is the predecessor of the lower bound]. [a]In other cases, the effect of evaluating a string_literal is determined by the String_Literal aspect that applies (see 4.2.1).

{AARM Ramification: Determining the bounds of a string_literal includes all of the checks associated with determining the bounds as well as the checks on the bounds themselves (such as checking that the determined range is compatible with the appropriate index subtype).}

Modify 4.2(11/5):

For the evaluation of a string_literal of a string type T, a check is made that the value of each character of the string_literal belongs to the component subtype of T. [For the evaluation of a null string literal of a string type, a check is made that its lower bound is greater than the lower bound of the base range of the index type.] The exception Constraint_Error is raised if {this check}[either of these checks] fails.

Modify 4.3.3(26):

{AARM Ramification: This is the entire S'Succ operation; if there is no successor to the bound at any point, the result wraps for modular types or Constraint_Error is raised otherwise.

AARM Implementation Note: It's not actually necessary to iterate the S'Succ operation. Implementations can generally add one less than the length of the aggregate to the position number of the lower bound, so long as overflow is detected and raises Constraint_Error. The following range compatibility and range length checks will detect any other problems. But care is needed to ensure that overflow doesn't happen when the array length lands exactly on Index'Base'Last - the one has to be subtracted from the length first. Additionally, some other method needs to be used if the position number of the lower bound could be not representable in Root_Integer.}

Modify 4.3.3(26.1/5):

{AARM Ramification: This is the entire S'Pred operation; if there is no predecessor to the bound, the result wraps for modular types or Constraint_Error is raised otherwise.}

   

[Editor's note: Recall that the null_array_aggregate case is more complex than the positional_array_aggregate case as the null_array_aggregate has to be able to handle multiple dimensions, while for a positional_array_aggregate each dimension is handled by a separate invocation of the rule. There already is an AARM note to this effect, so we don't need to explain that for the AARM, but it seems valuable to point out the difference here -- it confused me for a while.]    

Add after 4.3.3(28):

For a null_array_aggregate or positional_array_aggregate without an others choice, a check is made that the length represented by the determined bounds for each dimension matches the number of elements defined by the aggregate. Constraint_Error is raised if this check fails.

AARM Reason: This check can only fail for bounds of a modular type. Modular type operations (including the predecessor and successor operations) wrap around at the ends of the range rather than raising an exception. Therefore, it is possible that the calculated upper bound represents a length different from the number of elements actually in the aggregate. We do not want to make implementations support such objects, so we have defined this check.

!discussion

For cases already handled by the wording and/or tested by the ACATS, no semantic change is intended. This proposal is intended to clarify the handling of corner cases, and define some missing ones.


 

Until the introduction of the null_array_aggregate in Ada 2022, it was not possible for an array aggregate to have no elements unless the bounds were determined elsewhere (either from an applicable index constraint for an aggregate containing only an others choice, or from explicit bounds in a named_array_aggregate). Thus the case of the upper bound not being representable could only occur for a string literal, and the rules for dealing with that case were placed there.

With the introduction of the null_array_aggregate, it makes more sense to put all of the rules with null_array_aggregates and associate null string literals with those rules.


 

The ACVC tested (for Ada 83) cases of aggregates with unrepresentable upper bounds extensively, as part of the checks associated with what is now paragraph 28 (the range compatibility check). This can easily be justified for integer types, as the underlying range of integers is considered infinite, and implementations are expected to detect when those are not representable (after all, that's what an overflow check is, and that how the overflow check is defined).

It's harder to justify this for enumeration types. One can imagine that this check is actually on the position numbers associated with the enumeration, and then one can apply the explanation associated with integers. But that is a reach, enumeration types are primarily defined by their literals and the operations provided.

The problem here is that we do not describe how the upper bound is determined other than by handwaving. It is better to explicitly describe how that is done.

An alternative to the proposed rewording of the upper bound calculation would be to define an explicit check, perhaps as:

 For a positional_array_aggregate, a check is made that the determined upper bound for each dimension is a value of the associated index type. Constraint_Error is raised if this check fails.

AARM Reason: This rule is mainly necessary for enumeration types, where an aggregate could easily have more elements than there are enumeration literals. In earlier versions of Ada, this check was presumed to be covered by the following rule, but that is a stretch.

   

But this leaves the problem of how the bound is actually calculated for an enumeration value; it makes more sense to describe it as repeated invocations of the successor operation, and then the needed checks for going past the end of the type come along.

However, either of these solutions does nothing for modular types, as the predecessor and successor operations wrap around rather than raising Constraint_Error. Thus we need the additional check that follows the compatibility check. We write this as a separate check, as it was for null string literals, but generalize it to work for all array aggregates where the bounds are determined from the length of the aggregate rather than context or bounds explicitly given in the aggregate.

To see the need for this check, consider the following declarations:

   type Small is mod 8;

   type Small_Array is (Small range <>) of Integer;

   Null_Array : Small_Array := [];

   Big_Array  : Small_Array := [1, 2, 3, 4, 5, 6, 7, 8, 9, 0];

 

For the null_array_aggregate initializing Null_Array, the determined lower bound is 0. Small’Pred(0) is 7 (since the type wraps around). This gives bounds of 0 .. 7, which represents a length of 8. Of course, the length of a null_array_aggregate is 0, so we have a mismatch and Constraint_Error should be raised.

Similarly, for the positional_array_aggregate initializing Big_Array, the determined lower bound is also 0. We then apply Small’Succ to that value 9 times (one less than the number of expressions), giving an upper bound of 1. That of course represents a range of 0 .. 1, with a represented length of 2; since the aggregate has ten elements, Constraint_Error should be raised.

Note that in both of these cases, the resulting bounds are compatible with the index subtype. So that check does not (always) help us in this situation; we need a specific check.


 

An alternative to the solution of this AI would be to define the calculation of the upper bound solely in terms of operations on the position numbers. Position numbers are of type universal integer, so in theory they are exact. If the resulting upper bound position number cannot be converted to the index type (because the type doesn't have a value with the needed position number), then Constraint_Error is raised. (This is how S'Val works.)

This formulation eliminates the quirks of enumeration and modular types from consideration. However, there is a problem if the lower bound is not known statically. When a universal integer value is evaluated at runtime, Root_Integer math and rules are used. This means that not all of the position numbers associated with modular types can be represented (Root_Integer is signed, while the largest modular type is unsigned); Constraint_Error could be raised for correct aggregates.

To see how this could happen, consider:

   type Largest_Modular is mod System.Max_Binary_Modulus;

   function Dyn (A : Largest_Modular) return (A); -- Force to non-static.

   subtype Big_Range is Largest_Modular range

               Dyn(Largest_Modular'Last-2) .. Dyn(Largest_Modular'Last-1);

   type Mod_Array is (Big_Range range <>) of Integer;

   OK_Array  : Mod_Array := [1, 2];

   Bad_Array : Mod_Array := [1, 2, 3];

   

The position numbers of Big_Range are most likely outside of the range of Root_Integer. The combination of 4.4(10.1/5) and 4.4(11) says that such evaluation can raise Constraint_Error. Thus, if we use position numbers to determine the bounds of array aggregates, any positional aggregate of type Mod_Array could raise Constraint_Error. That includes OK_Array in this example.

We could declare such cases a pathology and simply not worry about them, but that seems uncomfortable when a solution (admittedly more complex) that works in all cases is available.


 

Editor's question:

These problems also apply to concatenation operations. (Imagine concatenating two arrays for type Small_Array above to get something longer than 8 elements, similarly for an enumeration type.) Do we need to add some similar rules in 4.5.3?? Should that be a separate AI or part of this one??

Aside from this question: 4.5.3(8) claims that the sentence "The upper bound is determined by the lower bound and the length." is redundant. But there is no other definition of how the upper bound is calculated; 4.5.3(5) only defines the length. I cannot imagine how it could be redundant other than via the Dewar rule based on the idea that the upper bound and length have to be consistent. But that's not an explicit thing - we always define the upper bound such that it is the case. And there is no "Proof" given here.

Since that sentence would need to be replaced by something involving Succ/Pred which surely is not redundant, it's somewhat moot. But enquiring minds want to know. :-)

!example

(See Discussion.)

!ACATS test

ACATS tests exist that check an unrepresentable upper bound for integer and enumeration index types (from the ACVC in the mid-1980's; existing compilers clearly get that right). Modular type examples of those tests should be constructed, especially as they now have a specific rule.

!appendix

This AI arose from a private question submitted to the Editor by Steve Baird.


 

[a]The "except for" seems a bit odd now.  Perhaps:

The bounds of this array value are determined according to the rules for positional_array_aggregates or, for a null string literal, null_array_aggregates (see 4.3.3).

[b]I'm not sure this works if you substitute the parenthesized parts associated with string literals into the sentence.  That is, this doesn't seem quite right:

For an equivalent string_literal, the lower bound is that of the corresponding index range in the applicable index constraint, if defined, or that of the corresponding index subtype, if not; in either case, the upper bound is determined from the lower bound by starting with the lower bound and applying S'Succ (where S is the appropriate index subtype) one less than the length of the string_literal;

Perhaps: "once for each expression (or character) other than the first"