Ada-Rapporteur-Group / User-Community-Input

Ada User Community Input Working Group - Github Mirror Prototype
26 stars 1 forks source link

When does predicate of first subtype of array type get applied w.r.t aggregates and slices? #72

Closed sttaft closed 7 months ago

sttaft commented 7 months ago

Exactly when should a dynamic subtype on an array subtype be invoked? In particular, if the first subtype of an array type has a predicate such as:

  type A1 is array (Positive range <>) of Integer
     with Dynamic_Predicate => A'First = 1;

is it inevitable that you will get a predicate failure if you write a slice of an array object of such a type with a low bound other than one? For example, should the following succeed or fail:

  X5 : A1 (1 .. 5);
  X2 : A1 (1 .. 2) := X5 (3 .. 4);

I hope it would succeed. I would suggest that a slice is an object of a new subtype of the type, without any predicate, and in any case, any predicate on the first subtype of the array type doesn't apply. Otherwise, it could be very hard to use such an array type.

Similarly, an aggregate of such a type could have something other than a low bound of 1:

X3 : A1 (1 .. 3) := (2 => 2, 3 => 3, 4 => 4);

I believe this should also succeed.

The Ada RM defines subtype predicate checks as being associated with subtype conversion (RM 3.2.4(31/5)), but there is no subtype conversion associated with an aggregate or a slice as far as I can see, unless you explicitly write a qualified expression. Note also that when the first subtype of an array type is constrained, you can nevertheless do slicing and concatenation of objects that do not satisfy the first subtype's constraint, since these operations operate on the underlying type, rather than the first subtype.

CKWG commented 7 months ago

Sliding rules are becoming unintuitive. One would hope this slides, but does not.

procedure P (X: A1) is begin Put_Line (X'First'Image & X'Last'Image); end P;

X: A1 (1 .. 5) := (others => 0);

P (X (2 .. 3));

sttaft commented 7 months ago

Sliding rules are becoming unintuitive. One would hope this slides, but does not.

procedure P (X: A1) is begin Put_Line (X'First'Image & X'Last'Image); end P;

X: A1 (1 .. 5) := (others => 0);

P (X (2 .. 3));

Not sure what you mean by "becoming unintuitive". Sliding does not happen if the target subtype is unconstrained. The presence of a Dynamic_Predicate doesn't change that. Note that GNAT now supports the notion of an array (sub)type with a fixed lower bound, but variable upper bound, declared as:

type AA1 is array (1 .. <>) of Integer;

This will likely be considered for standardization in the next version of Ada. It definitely works better than the use of a Dynamic_Predicate such as the one exemplified here.

However, the Dynamic_Predicate should work in more circumstances than GNAT seems to support currently, based on my understanding of the Ada standard. This issue is looking for confirmation on my understanding

swbaird commented 7 months ago

FWIW, I agree with Tuck's reasoning and conclusions. With many language issues, there are separate questions about what the RM says and what it should say. Let's start with the latter. It has been well established since Ada83 that the constraint (if any) of the first subtype of an array type is not checked for a slice, an aggregate, or an actual parameter in a call to a predefined operator such as "=" or "&". Given that constraint checking works that way (which nobody is disputing), it would be very irregular if predicate checking worked differently. And Tuck points out what the the RM does say (if only by omission) is consistent with this interpretation. So a note clarifying this point would probably be a good thing, but it would only be a clarification (as opposed to a change to the language definition).

sttaft commented 7 months ago

Thanks, Steve, for confirming. I think we might have a GNAT bug here. CodePeer uses such a Dynamic_Predicate and attempts to use slicing with it failed. I'll try to create a minimal reproducer ...

CKWG commented 7 months ago

I've been too long in retirement now - I forget. What I meant is that one would hope that for procedure P (X: A1); you could be sure the first index of actual parameter would be 1 - it's not.

Would this be the case for this proposal for Ada 202y? type AA1 is array (1 .. <>) of Integer; procedure P (X: AA1); P (X (2 .. 3)); Would slices slide to 1 here? This would mean the origin is lost. Or for procedure P (X: in out AA1) would a call like P (X (2 .. 3)) shift lower bound on in to 1 and out back to 2?

sttaft commented 7 months ago

I've been too long in retirement now - I forget. What I meant is that one would hope that for procedure P (X: A1); you could be sure the first index of actual parameter would be 1 - it's not.

There is a subtype conversion on parameter passing, so the Dynamic_Predicate will be checked, so you can be sure that X'First = 1.

Would this be the case for this proposal for Ada 202y? type AA1 is array (1 .. <>) of Integer; procedure P (X: AA1); P (X (2 .. 3)); Would slices slide to 1 here? This would mean the origin is lost.

Yes, sliding is part of the proposal even when the target subtype is unconstrained.

Or for procedure P (X: in out AA1) would a call like P (X (2 .. 3)) shift lower bound on in to 1 and out back to 2?

There is no effect on the bounds of the actual parameter, it is only the formal parameter that shows the effect of any sliding.

CKWG commented 7 months ago

Sigh, I'm getting old, still not what I wanted to say:

procedure P (X: A1) ; P (X (2 .. 3)); Here you might expect the first index of the formal parameter is 1 - it's not, it's 2. For P (X: AA1), I understand it is 1!

Richard-Wai commented 7 months ago

Sigh, I'm getting old, still not what I wanted to say:

procedure P (X: A1) ; P (X (2 .. 3)); Here you might expect the first index of the formal parameter is 1 - it's not, it's 2. For P (X: AA1), I understand it is 1!

I'm not sure its appropriate to assume the bounds of any array. Ada has always been this way, and really the pattern should be to use X'First to find the lower bound, rather than make an assumption.

sttaft commented 7 months ago

Sigh, I'm getting old, still not what I wanted to say:

procedure P (X: A1) ; P (X (2 .. 3)); Here you might expect the first index of the formal parameter is 1 - it's not, it's 2.

Sorry, I was being unclear. The Dynamic_Predicate check is performed, and it will fail so you will never get into P in this case (unless you have suppressed checks, and then you would have entered erroneous execution territory!).

For P (X: AA1), I understand it is 1!

Right, in this hypothetical proposal, it would slide and it would proceed with no run-time-check failure.

sttaft commented 7 months ago

Sigh, I'm getting old, still not what I wanted to say: procedure P (X: A1) ; P (X (2 .. 3)); Here you might expect the first index of the formal parameter is 1 - it's not, it's 2. For P (X: AA1), I understand it is 1!

I'm not sure its appropriate to assume the bounds of any array. Ada has always been this way, and really the pattern should be to use X'First to find the lower bound, rather than make an assumption.

The whole point of the Dynamic_Predicate was to assert that the low bound was always one. Similarly, the whole point of the array(1..<>) notation is to eliminate the need to worry about 'First, and be able to presume all objects of the type have an index starting with one. A true died-in-the-wool Ada programmer would probably never use these features, but alas, not everyone gets to program only in Ada every day.

CKWG commented 7 months ago

Am 06.12.2023 um 16:06 schrieb S. Tucker Taft:

Sorry, I was being unclear. The Dynamic_Predicate check is performed, and it will /fail/ so you will never get into P in this case (unless you have suppressed checks, and then you would have entered /erroneous execution/ territory!)

Ah, I see, I ran the code with code with assertion check off.

Thank you, Tuck, for the clarification.

Christoph

ZhssMessage ID: @.***>

ARG-Editor commented 7 months ago

Steve wrote:

FWIW, I agree with Tuck's reasoning and conclusions. With many language issues, there are separate questions about what the RM says and what it should say. Let's start with the latter. It has been well established since Ada83 that the constraint (if any) of the first subtype of an array type is not checked for a slice, an aggregate, or an actual parameter in a call to a predefined operator such as "=" or "&". Given that constraint checking works that way (which nobody is disputing), it would be very irregular if predicate checking worked differently. And Tuck points out what the the RM does say (if only by omission) is consistent with this interpretation.

The design of predicates was very intentional to be as close to "user-defined constraints" as possible. The "omission" as Steve calls it was an intentional design decision, to make it easier on both users and implementers (the latter because they don't need to look for new places to make checks).

So I don't understand the point of this question. The RM clearly specifies where checks are made (and certainly they're not made elsewhere), those places match one's intuition about what one would want, so nothing seems wrong OR unclear.

I was confused by Tucker's examples:

X3 : A1 (1 .. 3) := (2 => 2, 3 => 3, 4 => 4);

The Ada RM defines subtype predicate checks as being associated with subtype conversion (RM 3.2.4(31/5)), but there is no subtype conversion associated with an aggregate or a slice as far as I can see ...

There is of course a subtype conversion associated with the assignment, and for most purposes that is indistiguishable from one of the slice or aggregate. But I see that's not true for assignments into array objects, as the sliding happens first. Again that is very clear in 4.6.

So, I don't see the need for even a ramification AI on this one. We don't write notes that something not mentioned doesn't happen, in part because there is an infinite number of such things.

Agreed? Can this topic be marked as adequately answered and closed??

swbaird commented 7 months ago

Closing this one with no action is fine with me. OTOH, if Tuck or others feel that there would be value in an AARM note, I wouldn't oppose that.

sttaft commented 7 months ago

I guess I would like to see an AARM note. For example, we could augment AARM 3.6(16.a/3) as follows:

Discussion: Although there is no nameable unconstrained array subtype in this case, the predefined slicing and concatenation operations can operate on and yield values that do not necessarily belong to the first array subtype. This is also true for Ada 83. {Similarly, if a Dynamic_Predicate applies to the first subtype of an array type, it is not checked on a slice or an aggregate of the type, until it is converted to a subtype to which the predicate applies.}

ARG-Editor commented 7 months ago

OK, I've added this AARM note change to AI22-0005-1 (the AARM changes AI), and closed this topic as a confirmation.