AI22-0140-1

!standard 3.2.5(0)                                    25-08-19  AI22-0140-1/05

!standard 4.1(9/3)

!standard 4.3(4.3/7)

!standard 4.6(24.19/6)

!standard 4.6(24.27/5)

!standard 4.6(46)

!standard 4.8(6/3)

!standard 4.9.1(2/5)

!class Amendment 25-07-16

!status work item 25-07-16

!status received 22-07-09

!assigned author Randall Brukardt

!submitter Tucker Taft

!priority Medium

!difficulty Medium

!subject Requiring unconstrained subtypes

!summary

A new subtype property “Constrained” is defined and can be specified with an aspect. This property allows declaring that an object of the subtype must be unconstrained (rather than constrained-by-its-initial-value, which does not allow modifying the discriminants).

!issue

If a vector is instantiated with an element type that is an unconstrained-with-defaults discriminated type, the normal way of iterating over such a vector (for E of Vec loop ...) produces elements which are constrained by their current value, unless the type happens to have a partial view. This is a side-effect of the rule that makes dereferences constrained (unless a partial view exists -- see 4.1(9/3)), and the use of dereference as part of the Variable_Indexing aspect (4.1.6(3/5)). On the other hand, Replace_Element and Update_Element can change the discriminant.

If vectors are supposed to be as much as possible like arrays, then we should investigate creating a mechanism that preserves the unconstrained-with-defaults property when using user-defined (variable) indexing.

!recommendation

Create a new subtype property “Constrained”. This property can have the values Always, Never, Never_If_Appropriate_Actual, or Unspecified. For a constrained subtype, the value is Always (and cannot be specified to any other value). Unspecified corresponds to the current Ada rules for unconstrained subtypes, and is the default for such subtypes.

Always says that any object with the nominal subtype is treated as if it is constrained by its initial value (regardless of whether the actual underlying object is so constrained). Never says that any object with the nominal subtype is unconstrained; rules on conversions prevent any constrained objects from being given such a subtype. Never_If_Appropriate_Actual is only used for subtypes descended from a generic formal type (and thus can appear only inside of a generic unit); it says that the value depends on that of the actual subtype. When such a generic is instantiated, Never_If_Appropriate_Actual is replaced with Always or Never as appropriate.

!wording

Add a new clause 3.2.5:

3.2.5 Properties of composite subtypes

[Editor’s note: I can’t find a better place to put this, as it applies to all composite types. The only option would be 3.7.2, but that is about discriminated types; we need this property to apply to at a minimum composite generic formal types (like formal private types) as well as discriminated types. It’s easier to define it for all composite types, which avoids most contract and rechecking issues.]

For a composite subtype, the following language-defined assertion aspect may be specified with an aspect_specification (see 13.1.1):

Constrained

This aspect can be specified by one of the language-defined names Always, Never, Never_If_Appropriate_Actual, or Unspecified.

For a constrained composite subtype, Constrained has the value Always if not specified. For any other type, Constrained has the value Unspecified when not specified.

AARM To Be Honest: We define the value of Constrained for all types (even elementary types), so that the value is well defined for a private type (which is defined to be composite) whose full type is elementary, and for a generic formal private type whose actual type is elementary.

For a constrained composite subtype, the Constrained aspect can only be specified as confirming. For an unconstrained subtype with discriminants that have defaults (including formal derived types whose ancestor is such a type), the Constrained aspect can be specified as Always, Never, or Unspecified. For subtypes that are descendants of formal private types or of formal derived types whose ancestor is a descendant of a formal private type, the Constrained aspect can be specified as Always, Never_If_Appropriate_Actual[a][b][c][d][e][f][g][h][i][j], or Unspecified. For other (unconstrained) composite subtypes, the Constrained aspect can be specified as Always or Unspecified.

[Editor’s note: See the design discussion at the top of the !discussion. There is also a discussion of the name Never_If_Appropriate_Actual there.]

[Redundant: The value of Constrained determines how objects having the nominal subtype are treated. An object of a nominal subtype whose Constrained value is Always is treated as if it is constrained by its initial value, regardless of whether the underlying object is so constrained. An object of a nominal subtype whose Constrained value is Never is treated as unconstrained (allowing its discriminants to be changed if the underlying type allows such changes); checks are made to avoid converting constrained objects to such a subtype. An object of a nominal subtype whose Constrained value is Unspecified is constrained based on the actual object or other rules of the language.]

AARM Proof: The rules of this “Constrained” property are enforced in various other parts of the RM, including in 4.1 and 4.6.

AARM Discussion: “Unspecified” applies the rules of previous Ada versions, which for some operations requires runtime support. The values of “Always” and “Never” are always known at compile-time, and thus which checks are needed are also always known at compile-time. The value “Never_If_Appropriate_Actual” is compile-time rechecked appropriately in an instance specification (after replacement as discussed below), so it requires runtime checks only within a generic body dependent on the actual subtype (for a template implementation, whether an exception will be raised would usually be known at compile-time).

In an instance of a generic unit, if the value of the Constrained aspect is Never_If_Appropriate_Actual for some subtype S [Redundant: (which necessarily is descended from a generic formal type)], it is adjusted as follows:

Modify 3.7.2(3/5):

A'Constrained

{If the nominal subtype of A has the Constrained aspect Always, yields the value True. If the nominal subtype of A has the Constrained aspect Never, yields False. Otherwise, yields}[Yields] the value True if A denotes a constant, a value, a tagged object, or a constrained variable, and False otherwise. The value of this attribute is of the predefined type Boolean.

Modify AARM 3.7.2(3.a/3):

This attribute is primarily used on parameters, to determine whether the discriminants can be changed as part of an assignment. The Constrained attribute is statically True for in parameters. For in out and out parameters of a discriminated type, the value of this attribute needs to be passed as an implicit parameter, in general. However, if the type is tagged{,}[ or] does not have defaults for its discriminants{ or has a subtype with the Constrained aspect Always}, the attribute is statically True, so no implicit parameter is needed. {Similarly, if the parameter has a subtype with the Constrained aspect Never, the attribute is statically False, so again no implicit parameter is needed.} Parameters of a limited untagged type with defaulted discriminants need this implicit parameter, unless there are no nonlimited views, because they might be passed to a subprogram whose body has visibility on a nonlimited view of the type, and hence might be able to assign to the object and change its discriminants.

Modify 4.1(9/3):

If the type of the name in a dereference is some access-to-object type T, then the dereference denotes a view of an object, the nominal subtype of the view being the designated subtype of T. If the designated subtype has unconstrained discriminants, the (actual) subtype of the view is constrained by the values of the discriminants of the designated object, except when {the designated subtype has the Constrained aspect specified as the value Never, or }there is a partial view of the type of the designated subtype that does not have discriminants, in which case the dereference is not constrained by its discriminant values.

Modify 4.3(4.3/7): [As added by AI22-0124-1]

Modify 4.6(24.19/6):

AARM Reason: Unconstrained subtypes that have different Constrained values are not the same; different conversion rules apply to them, for instance.

Add after 4.6(24.27/5):

In any of these cases, if the target subtype has the value Never for its Constrained aspect, then the operand shall be unconstrained[Redundant:; not constrained in any way, including constrained by its initial value].

AARM Discussion: This applies to any type conversion. Since the value Never is only allowed for types that can be fully unconstrained, this is mainly useful for record conversions.

Note that this rule is rechecked in generic instance specifications, including in the case where the value “Never_If_Appropriate_Actual” is replaced by “Never” in the instance. End AARM Discussion.

Add after 4.6(46):

AARM Reason: We cannot use an “assume-the-worst” for the rule associated with subtypes with a Never_If_Appropriate_Actual Constrained aspect, as that would prevent any conversions to such a subtype (whether an object for a formal type is unconstrained cannot be known as the formal may not include a discriminant part and the object may come from outside of the generic). Thus, we use a runtime check. For a macro-expansion implementation of generics, whether an exception needs to be raised will usually be determinable at compile-time.

Modify 4.8(6/3):

If the designated type of the type of the allocator is elementary, then the subtype of the created object is the designated subtype. If the designated type is composite, then the subtype of the created object is the designated subtype when the designated subtype is constrained {or has the Constrained aspect specified as the value Never,} or there is an ancestor of the designated type that has a constrained partial view; otherwise, the created object is constrained by its initial value [(even if the designated subtype is unconstrained with defaults)].

Modify 4.9.1(2/5):

A subtype statically matches another subtype of the same type if they have statically matching constraints, all predicate specifications that apply to them come from the same declarations, {Constrained aspects have the same value, }Nonblocking aspects have the same value, global aspects statically match, Object_Size (see 13.3) has been specified to have a nonconfirming value for either both or neither, and the nonconfirming values, if any, are the same, and, for access subtypes, either both or neither exclude null. Two anonymous access-to-object subtypes statically match if their designated subtypes statically match, and either both or neither exclude null, and either both or neither are access-to-constant. Two anonymous access-to-subprogram subtypes statically match if their designated profiles are subtype conformant, and either both or neither exclude null.

Modify each container’s Reference_Type as follows:

subtype Unconstrained_Element_Type is Element_Type
  with Constrained => Never_If_Appropriate_Actual;

type Reference_Type

   (Element : not null access Unconstrained_Element_Type)

      is limited private ...;

[Editor’s note: Following is the list of paragraphs to be changed: A.18.2(32.2/6), A.18.2(70.11/6), A.18.2(147.2/6), A.18.3(17.2/6), A.18.3(51.11/6), A.18.3(82.2/6), A.18.4(41.2/6), A.18.5(17.2.6), A.18.5(37.12/6), A.18.6(16.1/6), A.18.6(51.13/6), A.18.7(96.1/6), A.18.8(58.1/6), A.18.9(73.1/6), A.18.10(29/6), A.18.10(70.11/6), A.18.10(123/6), A.18.18(17/6), A.18.18(53/6).

In the case where there is a second Reference_Type declared within the Stable subpackage, we do not need a second copy of the subtype Unconstrained_Element_Type (the one from the outer scope is fine). OTOH, the subtype is repeated in the “description” part of each subclause (just as the type declarations are).

I do not show the various aspects defined on Reference_Type above; those will be unchanged.]

!discussion

This solution provides a mechanism to solve two problems: the one outlined in the Issue section, and also the overhead needed for in out parameters of mutable types. (A “mutable” type is one that has discriminants-with-defaults, such that the discriminants can be changed by assignment. This is not an official RM term, but it is defined and used in the AARM annotations.)

The initial problem is not limited to containers; it can occur for any access type. The problem is that 4.1(9/3) defines that all dereferences are constrained by their initial value, except in the case of a type with an unconstrained partial view. Since we already have an exception to this rule, there cannot be an implementation problem with extending it. We use the new aspect to cause an additional exception to the rule (and then use that to fix the existing containers).

The second problem is that in out formal parameters of a mutable type are constrained if and only if the actual object passed is constrained. That provides a “tripping hazard”, in that if the routine intends to change the discriminants of the passed in object, passing in a constrained object will cause Constraint_Error. Moreover, supporting this semantics requires somehow passing in the runtime constrained state for the object, overhead that is rarely useful. The programmer could test ‘Constrained in the precondition of the subprogram, but that is unlikely to remove the overhead as this case is unusual, and in any case a bad call would only be detected at runtime (or at best with a warning).

By using the new aspect on the parameter subtype, we can eliminate the overhead and turn the check on the actual into a compile-time check.

Similarly, if we’re not interested in changing the discriminants of the parameter, we can specify that and avoid the overhead of passing a flag that will never be used. In that case, there would be no restrictions on the actual object.


 

The basic idea here is that we only allow specific specification of Constrained when that is appropriate for the known type. In particular, we only allow specifying Constrained to Never if we have a subtype that is known to be unconstrained and be a discriminated type whose discriminants have defaults. Constrained = Never implies that the discriminants of objects of the subtype can be changed, so we only allow that when it is actually true.

To handle the case of a generic formal private type (and similar cases of generic formal derived types), we have the ability to specify Never_If_Appropriate_Actual. This value is converted on instantiation to the appropriate value Never or Always depending upon the properties of the actual type. Thus, uses outside of the generic can see the correct property, while we have a well-defined value to use within the generic that declares that we want the subtype to be unconstrained if that makes sense.

The name “Never_If_Appropriate_Actual” was chosen as it highlights the most interesting possible result (“Never”, which has an effect on Legality as well as constraint checks). It is always possible to set a subtype to have the Constrained value be Always or Undefined, it is only if one needs a subtype to have the Constrained value be Never that a special mechanism is needed. That’s because we only allow subtypes to have Constrained = Never if the underlying type allows modification of discriminants, and not for types that don’t (or don’t have discriminants). As such, we would like the name to emphasize that Never is a possible (and preferred) result for such a subtype.

We also considered naming this concept “Depends_On_Actual” (which doesn’t say how it depends, nor emphasizes that “Never” is a possible and intended result) and “Only_if_Actual_Constrained” (which is confusing, as it is unclear if we are talking about a constrained subtype or something about the Constrained aspect -- moreover, neither is a perfect match).


 

Following is a Bairdian-style list of thoughts. Future AI versions may answer and remove some or all of these.

Unfortunately, this might not work. The Constrained aspect applies to all uses of a subtype, while the “unconstrained partial view” property only applies to subtypes used as designated subtypes (and only when they are used that way). This suggests that the values might need to be context-dependent.

If S is a subtype of a full type that has discriminants with defaults, if S has/does not have an unconstrained partial view, one would want S to have Constrained be Never/Always when S is used as a designated subtype, and Constrained be Unspecified when used as a parameter (for compatibility). That isn’t going to work.

That’s too bad as it would simplify various other rules.

!example

An example of a subprogram where we want all calls to pass actuals where the discriminants can be changed:

type Mutable (D : Natural := ...) is record ...
subtype Never_Constrained_Mutable is Mutable
   with Constrained => Never;
procedure Change_It (Obj : in out Never_Constrained_Mutable) is
begin
   Obj := ...; -- Should never raise Constraint_Error because
               -- Obj is constrained and the discriminants don’t match.
end Change_It

 

See the changes to Reference_Type for the containers for an example of using this aspect in a generic to specify the behavior of a designated type.

!ACATS test

ACATS C-Tests are needed for the new aspect, and B-Tests are needed for violations of conversion rules.

!appendix

This AI was created from the ARG Github Issue #12 (https://github.com/Ada-Rapporteur-Group/User-Community-Input/issues/12).


 

[a]Not the best name!

[b]True enough. Better ideas? Originally I had False_if_Actual_Constraint_is_False, which is way too long (and now needs two "Always" added to it).

I suppose we could just use "Depends_on_Actual", but I was hoping to give a connotation that the result will be Always_False if that is appropriate for the actual subtype.

[c]"Always_False" is the interesting value, since it triggers legality checks on conversions.

[d]I believe you mentioned "Depends_On_Actual" and I think that would be less confusing.

[e]Or perhaps: "Only_When_Actual_Constrained".

[f]Perhaps could simplify all four to:

Always, Never, Only_When_Actual_Constrained, Unspecified.  This would get away from any temptation to treat the aspect as some kind of 4-valued Boolean value.

[g]The Always and Never values are fine. "Only_When_Actual_Constrained" is wrong, however, because the value ends up Always if the actual type has discriminants without defaults. (Such an actual is unconstrained.) I think there might be a confusion between the Constrained aspect and "constrained subtypes", and I would want to avoid that. I've used "Depends_on_Actual" for now.

[h]_Marked as resolved_

[i]_Re-opened_

I would go back to "Depends_On_Actual", which makes it clear you better not rely on your intuition.

[j]Disagree, see the !discussion.