!standard 5.2(11/5) 23-03-23 AI22-0071-1/01
!class amendment 23-03-23
!status work item 23-03-23
!status received 22-07-22
!subject Testing assignability and returnability
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.
Specifically, one would like to write something like:
if <<Some test of compatability of RHS to LHS.all>>
LHS.all := RHS;
LHS := new T'(RHS);
This is a specific instance of the general question raised here.
One possible solution to the general issue is to directly address it with
attributes. These could look like:
For a prefix X that denotes an object of a nonlimited type:
X'Assignable denotes a function with the following specification:
function X'Assignable (Arg : target) return Boolean
Target is the type of X. Assignable returns False if any check defined for an assignment of Arg into X would raise any exception, and would return True otherwise.
For a prefix Func that denotes an enclosing function:
Func'Returnable denotes a function with the following specification:
function Func'Returnable (Arg : result) return Boolean
Result is the result subtype of Func (if the result subtype of Func is anonymous, then result is similarly defined).
The Boolean result is False if Arg would raise any exception because of the checks defined in subclause 6.5 if Arg was returned from Func, and returns False otherwise.
However, these have several problems. One is that a number of different checks are wrapped up in these attributes, so it could be hard to determine alternatives if an attribute returned False. Some checks might be nearly impossible to work-around (such as accessibility checks; the only alternative would usually be to allocate an object that could never be recovered), others might have reasonable fixes.
Additionally, in some cases these attributes would be fairly expensive to evaluate, given the accessibility checks associated with them.
Another idea that came up was to use some membership test for this purpose. The existing membership tests for objects test equality. So those can't be used directly. An idea that was suggested was to define some attributes that can only be used as membership tests. That could use attributes similar to those defined above:
if RHS in LHS.all'Assignment then
if Result in Func'Return then
Alternatively, we could decide not to answer the general question and only
solve the use-case from Issue #15. A suggestion was made to define a membership
test for the (runtime) subtype of an object.
That would be defined as an attribute 'Subtype which is a particular kind of
individual membership test.
membership_choice ::= choice_simple_expression choice_expression | range |
subtype_mark | name'subtype
The expected type of prefix of the Subtype attribute of a membership_choice is
the test type of the membership.
The prefix of the Subtype attribute shall denote an object.
-- The lead-in for individual membership tests say that they return True if:
[Note: I included the predicate check as one expects this to work the same as a test against the nominal subtype of the object if the (full) tested type is elementary or constrained. It should only produce different results if the (full) tested type is unconstrained. That is, it should always return False if checking the tested type directly would return False. But it might return True in fewer cases.]
But this is not a complete check for assignability. If the LHS object is of a mutable type, then the assignment will succeed even if discriminants don't match.
We could define a Mutable attribute to deal with this case:
For any type T:
Returns False if the full type of T is an array type or a type that has discriminants without defaults, and returns True otherwise.
[Note: T'Constrained doesn't seem appropriate for this, as types without any constraints are considered constrained, but such types should be considered mutable.]
With these two features, the original example could be written:
if Element_Type'Mutable or else RHS in
LHS.all := RHS;
LHS := new T'(RHS);
This still seems a bit confusing, since it doesn't work for (known) elementary types, as mutability does not mean that the range/exclusion is satisfied.
We could just define Mutable for composite types (it's most useful for private types, otherwise you can tell by inspection what the answer is). Or one could try to fold that into the membership test, but then the name "Subtype" is no longer appropriate. (A mutable type still has a runtime subtype, that is the values of the discriminants, and it would make sense to test them.)
More discussion and brainstorming is needed here. Some questions to answer:
(1) Is the general question worth solving? In particular, are we interested in being able to test for all failures of assignments and returns, or just "interesting ones"?
(2) Do we want a granular solution that can be used piecemeal to check specific failure modes, or just a simple check for all possible failures?
(3) Are there usage cases for avoiding accessibility checks in returns and assignments? For avoiding the tag check on anonymous access-to-specific-tagged? The tag check for assignment to class-wide objects?
C-Tests should be created to test the solution. B-Tests might be needed to test illegal uses of the solution.
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.