AI22-0071-2

!standard 5.2(11/5)                                        23-09-13  AI22-0071-2/01

!standard 6.5(21/3)

!class amendment 23-03-23

!status work item 23-03-23

!status received 22-07-22

!priority Low

!difficulty Easy

!qualifier Omission

!subject Provide Subtype attribute for testing assignability and declaring a copy

!summary

Provide a Subtype attribute that can be used in membership tests and to declare an object with the same constraints (or lack thereof) as an existing object.

!issue

It is important to be able to determine whether some given runtime check would fail without actually performing the check and having to raise and handle the exception. For instance, we can pretest whether an access value is null before we dereference it. A valuable property of a programming language is the ability to make such pretests.

However, Ada has cases where it is not possible to make all of the checks associated with a construct. These include:

The latter case was raised in GitHub Issue #15.

In a generic that takes an indefinite private type as a formal parameter, there is no exception free way to tell if two objects have the same (runtime) subtype.

This would be useful, for example, in implementing the indefinite containers. If such a check was possible, Replace_Element would only need to reallocate an element when it is not possible to assign the new element to the existing one[a][b][c].

Specifically, one would like to write something like:

if <<Some test of compatibility of RHS to LHS.all>> then
    LHS.all := RHS;
else
    Free (LHS);
    LHS := new T'(RHS);
end if;

 

This is a specific instance of the general question raised here.

!recommendation

We propose to define an attribute 'Subtype, which when applied to a prefix denoting an object, represents a subtype mark that can be used for testing for assignability, and for declaring a new object that can hold any value of the object denoted by the prefix.  For example:

   if X in Y'Subtype then
       Y := X;  --  Should not raise Constraint_Error
   end if;

   declare
       Z : Y'Subtype := Y; -- Should not raise Constraint_Error
   begin
       Op(Z);
   end;

 

This Subtype attribute would work for scalar types, producing a subtype whose range matches that of the prefix.  The Subtype attribute would also work for discriminated types, including cases where Y is a formal parameter that might or might not be constrained, based on the value of the Y'Constrained attribute, meaning that Y'Subtype denotes a constrained subtype if and only if Y'Constrained is True.  This also implies that if Y is nominally of an indefinite subtype (such as an unconstrained array subtype), then Y'Subtype will necessarily be constrained, since all objects of an indefinite subtype are constrained.

If the prefix Y denotes an object of a subtype with subtype predicates, then Y'Subtype would have the same predicates, to ensure that if X in Y'Subtype, Y := X will succeed.

If the prefix Y denotes an object of a class-wide type, then Y'Subtype would be class-wide, and incorporate the constraints or predicates that apply to Y.

!wording

** TBD.

!discussion

Note that for arrays, Y'Subtype will produce a subtype whose bounds match those of Y (that is, Y'Type(Y'First .. Y'Last) for a single-dimensional array type), and so the test X in Y'Subtype might return False even though Y := X; would succeed at runtime, thanks to sliding.  This does not violate the rule that says if (as opposed to if-and-only-if) X in Y'Subtype, then Y := X; will succeed.  In the case where it is known that Y is an array, a more precise check can be performed using, for example, if X'Length = Y'Length then ...

One could imagine having a more precise attribute function such as Y'Assignable(X) which would return True if and only if X can be assigned to Y, but the 'Subtype attribute handles all cases safely[d], and is precise for all non-array cases as well as the very common case of arrays where essentially all objects of the given array type have the same lower bounds, and only the upper bounds vary.

Note that when Y is of a formal parameter of a discriminated type with defaults, Y'Subtype denotes a subtype where it is not known whether or not it is constrained, since it depends on the run-time value of Y'Constrained. This accurately reflects the actual subtype of Y, but it is somewhat of a new notion for a named subtype[e]. Formal subtypes are to some extent similar, since whether or not they are constrained can be determined by the actual subtype in a given instantiation.

!ACATS test

C-Tests should be created to test the solution. B-Tests might be needed to test illegal uses of the solution.

!appendix

This AI was promoted from AI12-0358-1 to be reconsidered for post-Ada 2022 work. The !appendix of the original AI has additional motivation and discussion.

Additionally, the topic of Github issue #15 (see https://github.com/Ada-Rapporteur-Group/User-Community-Input/issues/15) is included in this AI, as it is a special case of the general question.

[a]The indefinite containers may be instantiated with a class-wide element type. Given that, would this new feature really solve the Replace_Element problem? If X is an object of a class-wide type, is X'Subtype specific or class-wide? If X also has discriminants, then is X'Subtype subject to a corresponding discriminant constraint? Perhaps all this can be resolved  by saying that (in the class-wide case) X'Subtype is class-wide and not subject to any discriminant constraint, but it is subject to a dynamic predicate that captures the desired properties regarding tag and discriminant values. That would work for membership tests and that's what we really care about. It does seem somewhat inelegant, but it avoids the Constrained_Subtype'Class  quagmire.

[b]Can you clarify a bit?  One problem is clearly the tag.  Since that can't be changed by an assignment, X'Subtype for a class-wide type would have to impose such a limitation.  That would probably need to be defined by an implicit predicate, since there is no notion of a subtype_mark that determines a specific type where which particular type is not known at compile time.  So I think it would need to be class-wide, indefinite (so it needs an initialization), and has a predicate limiting the tag.

On the other hand, whatever constraints or predicates apply to X would apply to X'Subtype.  It is true that some compilers don't properly implement constraints on class-wide subtypes, but that isn't something we should enshrine in these new rules.

[c]I think we agree about the the treatment of the tag. Regarding discriminant constraints (and a tagged discriminated object will, of course, always be constrained), I thought there was a consensus at the ARG level to tiptoe around Constrained_Subtype'Class (going back to the ARG's decision to neither approve nor kill AI05-0057).  I personally would be fine with the approach you suggest.

[d]I don't believe this is true; 'Subtype can't handle accessibility checks (accessibility isn't a property of the subtype, and I'm certain we don't want to make it one -- we certainly don't want local variables that have library-level accessibility!!). I don't think we care much about pre-testing accessibility checks this way, so this is mainly a problem with this discussion rather than one with the proposal.

[e]This seems like a pain to implement if a compiler uses different representations for subtypes that are mutable vs. ones that are not mutable. (Janus/Ada doesn't do that, but I believe most other compilers do.)