AI22-0139-1
!standard 4.3(6/6) 25-07-30 AI22-0139-1/02
!class Binding Interpretation 25-07-16
!status Revision-202Y 25-07-30
!status WG9 Approved 25-10-08
!status ARG Approved 16-0-0 25-07-30
!status work item 25-07-16
!status received 25-02-28
!assigned author Randall Brukardt
!submitter Stephen Baird
!priority Medium
!difficulty Easy
!qualifier Omission
!subject Aggregates and Build-in-place
An aggregate that will raise Constraint_Error can do that as soon as the constraints of the aggregate are determined.
In the case where an aggregate or a function call is built in place, it is better if the check that the constraints of the object being initialized are satisfied is performed earlier. In a case like
T'(Tiny_Limited_Component => (1 .. Some_Too_Big_Value => ...))
we currently have to generate code to discard values that occur outside of the bounds of the component value. Otherwise, we would have overwritten storage outside of the space reserved for the component value. And the current semantics require that all of the expressions of the aggregate are evaluated before the constraint check is made.
We could avoid this complication if we allowed the constraint check to be earlier for aggregates.
For a function call, we have a permission to check the constraints before writing the values, so this complication does not have to arise (although an implementation is still permitted to generate code to skip writing excess components if that is better for some reason).
Some change should occur for aggregates as well.
Add a permission to check constraints of aggregate before evaluating any of the component expressions or <>s.
Add after 4.3(6/6):
Implementation Permissions
If an aggregate has an applicable constraint, then any check that this constraint is satisfied by the aggregate may be performed before beginning the evaluation of any nondiscriminant component expressions (or <>s) of the aggregate.
AARM Ramification: For an array aggregate, the choice expressions/ranges need to be evaluated in order to determine the bounds of the aggregate (rules of array aggregates mean that no more than one such expression or range can be dynamic). Similarly, discriminant values for record aggregates need to be evaluated in order to determine their values before any check. Only the evaluation of expressions or <> representing component values can happen after the check.
While this could change the runtime behavior of some program (if an implementation takes advantage of the permission), such a program seems very unlikely. Such a program would have to (1) have an aggregate that fails a constraint check; (2) have significant side effects in the expressions of the aggregate; (3) handle the exception resulting from the failed aggregate; and (4) depend in later execution on the side effects of the failed aggregate. This seems rather unlikely in production code (it might happen in an ACATS-style test checking evaluation of constituents of an aggregate).
The term “applicable constraint” is defined by AI22-0124-1, and it is defined in 4.3, so no cross-reference is needed.
We considered requiring early checks (rather than just allowing them) for aggregates with applicable constraints. Portable code has to presume that such early checks can happen, and there is no real advantage to requiring all of the expressions that make up an aggregate be evaluated before an exception is raised. Nor is there any advantage to the variation - if an aggregate is going to fail, any values determined before that failure are going to be discarded anyway. Only any side-effects are detectable.
However, that would require modifying the order of the operations associated with the dynamic semantics of each kind of aggregate. That would be much more complicated than a simple permission. Moreover, as a basic normative rule, we would need to be much more careful in defining what constituents are evaluated before the check and which ones are evaluated after. Finally, the function call rule is described as a permission (even though it might be required in some cases). It seems fine to do the same here.
Note that this permission (as well as the existing counterpart for function calls) do not mention build-in-place. The permission can be used for any function or aggregate, whether or not the result is built-in-place. We do this because whether certain types are built-in-place is implementation-defined, and we do not want the visible semantics to depend upon this choice anymore than absolutely necessary (the number of calls to Finalize routines change if something is built-in-place, for instance, but little else should change).
At one point, the question was raised as to why earlier checks are a good idea, if they are mostly about failing aggregates. But checking earlier also allows using build-in-place for many more aggregates. One can't do it now without heroic efforts if there is a possibility that the items won't fit, because the aggregate can't overwrite memory and all of the side-effects have to be generated even if the bounds/discriminant check fails. Forcing worse code just to have consistent side-effects seems silly (depending on side-effects in the case of a failing aggregate seems like a pathology).
(See !issue.)
@dinsa
If an aggregate is of a tagged type, a check is made that its value belongs to the first subtype of the type. Constraint_Error is raised if this check fails. Any discriminant check is performed before the initialization of any nondiscriminant component of the aggregate object.
@dinst
@s8{@i{Implementation Permissions}}@hr
If an @fa{aggregate} has an applicable constraint, then any check that this constraint is satisfied by the @fa{aggregate} may be performed before beginning the evaluation of any nondiscriminant component @fa{expression}s (or <>s) of the @fa{aggregate}.
As a permission, this is not directly testable. But C-Tests of aggregates need to be aware of the possibility, especially any tests that are checking order of evaluation.
This AI was created from the ARG Github Issue #128 (https://github.com/Ada-Rapporteur-Group/User-Community-Input/issues/128).