AI22-0147-4

!standard 3.4(17/2)                                    26-07-21  AI22-0147-4/01

!standard 3.4(18/3)

!standard 3.9.2(1/5)

!standard 3.9.2(2/3)

!standard 3.9.2(10/2)

!standard 6.1(21)

!standard 6.1(24/2)

!standard 6.1.1(7/5)

!standard 6.3.1(18/3)

!standard 6.3.1(18.1/3)

!standard 13.1.1(18.4/6)

!class Amendment 26-05-09

!status work item 26-05-09

!status received 26-05-09

!assigned author Tucker Taft

!assigned author Niklas Holsti

!submitter Tucker Taft

!priority Medium

!difficulty Medium

!subject Controlling what is a controlling parameter

!summary

Use the Fixed attribute on a formal parameter or result type to affix it to its current definition, and thereby prevent it from being considered a controlling parameter/result (or the untagged equivalent) and, if the subprogram is inherited upon type derivation, from changing to match the derived type.

!issue

There are situations where a particular subprogram should not be inherited by derived types, or when derived, one or more parameter or result types should not change to match the derived type.

Should we provide a mechanism to indicate that a formal parameter or result of a function is not to be considered a controlling/parameter result (or the untagged equivalent)? (Yes.)

!recommendation

We recommend allowing the use of the Fixed attribute on the subtype_mark in the specification of a formal parameter or the result of a subprogram, to prevent that parameter/result from being considered a "controlling" parameter or result, or in the untagged case, from causing the subprogram to be inherited for derived types.  It is affixed to its current definition and will not change even if the subprogram is inherited because of other formal parameters or the result.

If some other formal parameters, or the result, are of the same type, but are not declared with Fixed, the subprogram is still inherited on derivation from this type, but any affixed parameters, or an affixed result, remain affixed to the original definition in the profile of the inherited subprogram — they do not change to match the derived type.

Adding Fixed to a the subtype of a formal parameter or result does not introduce a new type, nor does it affect how the parameter or result is passed, nor does it change the type or the conformance rules for subprogram profiles. We have proposed to leave the definition of "primitive" subprogram unchanged, but to introduce a new term, "derivable" subprogram (for a given type), which ignores a result or operand that is affixed to its initial definition.  A formal parameter or result of a primitive subprogram for a type T that is of type T or access T, and is not affixed to its initial definition, is called a "derivable" parameter/result (for T).

As an example of where you might want to use an affixed parameter, when computing with physical quantities with dimensions, we could have:

type Real is digits 15;
function Scale (Quantity, Factor : Real) return Real is
 
(Quantity * Factor);
type Length is new Real;

 

Type Length inherits the Scale function, with all occurrences of Real changed to Length, so the profile of the inherited function is:

function Scale (Quantity, Factor : Length) return Length;

 

However, we do not want to scale a Length with another Length, but with a dimensionless number, a Real. With the recommended addition to Ada, we can fix the Factor parameter as type Real:

function Scale (Quantity : Real; Factor : Real'Fixed) return Real …

 

Now the profile of the inherited operation is:

function Scale (Quantity : Length; Factor : Real'Fixed) return Length;

 

Similarly, we could have a Ratio function, where we want the result to be dimensionless although the parameters are type Length. We can then fix the result type:

function Ratio (Num, Den : Real) return Real'Fixed is (Num / Den);

The inherited Ratio function takes two Length operands and returns a Real result (with Fixed on the result).

For an example of the tagged and dispatching case, given a case such as the following:

type Holder_Storage_Pool is new Root_Storage_Pool_With_Subpools ...
type Holder is tagged ...
function Create_Subpool (HSP : Holder_Storage_Pool; H : Holder)
  return Subpool_Handle;

 

we would be violating RM 3.9.2(12) which precludes having a subprogram that is a dispatching operation of two tagged types. To avoid this problem, we could add the Fixed attribute to one of the formal parameter types to indicate that this parameter is not a controlling parameter. In this case, we choose to do that on the storage pool type:

type Holder_Storage_Pool is new Root_Storage_Pool_With_Subpools ...
type Holder is tagged ...
function Create_Subpool (HSP : Holder_Storage_Pool'Fixe; H : Holder)
  return Subpool_Handle;

 

One existing alternative in this case is to use a class-wide type for the parameter that we do not want to be "derivable", but introducing a class-wide type here would mean that inside the subprogram, any use of the operand would be potentially dispatching.  Introducing dispatching into an otherwise statically-bound algorithm is not a good thing, especially in safety-critical applications with rigorous testing requirements.

!wording

Modify 3.4(17/2):

{A formal parameter or result of a user-defined primitive subprogram (for some type T) can be affixed to its initial definition, indicated by the presence of the Fixed attribute on the subtype_mark in its definition. [a]Any other formal or result of the subprogram that is of type T or access T is considered derivable (for T). A user-defined primitive subprogram is considered derivable if it has a derivable result or at least one derivable formal parameter. }For each {derivable }user-defined primitive subprogram (other than a user-defined equality operator — see below) of the parent type or of a progenitor type that already exists at the place of the derived_type_definition, there exists a corresponding inherited primitive subprogram of the derived type with the same defining name. [Primitive]{Derivable} user-defined equality operators of the parent type and any progenitor types are also inherited by the derived type, except when the derived type is a nonlimited record extension, and the inherited operator would have a profile that is type conformant with the profile of the corresponding predefined equality operator; in this case, the user-defined equality operator is not inherited, but is rather incorporated into the implementation of the predefined equality operator of the record extension (see 4.5.2).

Add after 3.4(17/2):

AARM Discussion: If a parent type's primitive equality operator is not derivable, the type must necessarily be untagged, since overriding a dispatching operation requires having the same set of derivable parameters/results (see 3.9.2(10/2) below). When such a situation arises for an untagged type derivation, the user-defined equality operator is not inherited, meaning the predefined equality operator will "reemerge" in the derived type (similar to what happens with untagged formal derived types).

Modify 3.4(18/3):

The profile of an inherited subprogram (including an inherited enumeration literal) is obtained from the profile of the corresponding (user-defined{, derivable}) primitive subprogram of the parent or progenitor type, after systematic replacement [within its profile of each subtype of its profile (see 6.1) that is of the parent or progenitor type, other than those subtypes found in the designated profile of an access_definition]{of each subtype that is of the parent or progenitor type occurring within the specification for a derivable formal parameter or derivable result}, with a corresponding subtype of the derived type. For a given subtype of the parent or progenitor type, the corresponding subtype of the derived type is defined as follows:

Modify 3.9.2(1/5):

The {derivable }primitive subprograms of a tagged type, the subprograms declared by formal_abstract_subprogram_declarations, the Put_Image attribute (see 4.10) of a specific tagged type, and the stream attributes of a specific tagged type that are available (see 13.13.2) at the end of the declaration list where the type is declared are called dispatching operations. [Redundant: {A subprogram can be a primitive subprogram of more than one tagged type, but can be a derivable subprogram for at most one of those types. }A dispatching operation can be called using a statically determined controlling tag, in which case the body to be executed is determined at compile time. Alternatively, the controlling tag can be dynamically determined, in which case the call dispatches to a body that is determined at run time;] such a call is termed a dispatching call. [Redundant: As explained below, the properties of the operands and the context of a particular call on a dispatching operation determine how the controlling tag is determined, and hence whether or not the call is a dispatching call. Run-time polymorphism is achieved when a dispatching operation is called by a dispatching call.]

Modify 3.9.2(2/3):

A call on a dispatching operation is a call whose name or prefix denotes the declaration of a dispatching operation. A controlling operand in a call on a dispatching operation of a tagged type T is one whose corresponding formal parameter is [of type T or is of an anonymous access type with designated type T]{derivable for T (see 3.4)}; the corresponding formal parameter is called a controlling formal parameter. If the controlling formal parameter is an access parameter, the controlling operand is the object designated by the actual parameter, rather than the actual parameter itself. If the call is to a (primitive) function with {a derivable} result{ of} type T (a function with a controlling result), then the call has a controlling result — the context of the call can control the dispatching. Similarly, if the call is to a function with [an]{a derivable} access result type designating T (a function with a controlling access result), then the call has a controlling access result, and the context can similarly control dispatching.

Modify 3.9.2(10/2):

In the declaration of a dispatching operation of a tagged type, everywhere a subtype of the tagged type appears as [a subtype of the profile (see 6.2)]{the subtype or designated subtype of a derivable parameter or result (see 3.4)}, it shall statically match the first subtype of the tagged type. If the dispatching operation overrides an inherited subprogram, it shall be subtype conformant with the inherited subprogram{, and a formal parameter or the result (if any) shall be derivable if and only if it is derivable in the inherited subprogram; if the operation overrides the predefined equality operator of a tagged type, both operands shall be derivable}. The convention of an inherited dispatching operation is the convention of the corresponding primitive operation of the parent or progenitor type. The default convention of a dispatching operation that overrides an inherited primitive operation is the convention of the inherited operation; if the operation overrides multiple inherited operations, then they shall all have the same convention. An explicitly declared dispatching operation shall not be of convention Intrinsic.

[Author's note: The prior wording for the requirement for matching the first subtype talks about the "subtypes of the profile" which includes places where the subtype could occur in the profile of an access-to-subprogram parameter, which is a bit odd.  (Note that the original wording mentions 6.2 but in fact "subtypes of the profile" is actually defined in 6.1.) We have dropped that requirement as it seemed that most compilers never enforced that, as well as dropping the requirement for "affixed" parameters and results, since they don't get substituted on derivation. By talking only about derivable parameters and results we restrict the requirement to the desired places.]

Add after 6.1(21):

If a formal parameter or result is declared with the reserved word at, its type shall not be of an anonymous access-to-subprogram type, and the subprogram shall be a primitive subprogram of some type.

[Author's note: These restrictions are not really necessary. We could allow an affixed parameter of any type, on any subprogram. However, these restrictions might help to catch programmer error or confusion.]

Add after 6.1(24/2):

If a parameter_specification includes the attribute Fixed on the subtype_mark, the formal parameter is affixed to its current definition, and is not derivable (see 3.4). Similarly, if a parameter_and_result_profile includes the Fixed on the subtype_mark of the result subtype, the result of the subprogram is affixed to its current definition, and is not derivable.

Modify 6.1.1(7/5):

Within the expression for a Pre'Class or Post'Class aspect for a [primitive]{derivable} subprogram S of a tagged type T, a name that denotes a {derivable }formal parameter (or {derivable} S'Result) of type T is interpreted as though it had a (notional) nonabstract type NT that is a formal derived type whose ancestor type is T, with directly visible primitive operations. Similarly, a name that denotes a {derivable }formal access parameter (or S'Result for [an]{a derivable} access result) of type access-to-T is interpreted as having type access-to-NT. [Redundant: The result of this interpretation is that the only operations that can be applied to such names are those defined for such a formal derived type.]

Modify 6.3.1(18/3):

Two profiles are fully conformant if they are subtype conformant, if they have access-to-subprogram results whose designated profiles are fully conformant{, if both or neither have affixed results (see 3.4)}, and for corresponding parameters:

Add after 6.3.1(18.1/3):

Modify 12.6(8.5/5):

If a formal parameter of a formal_abstract_subprogram_declaration is of a specific tagged type T or of an anonymous access type designating a specific tagged type T{, and the parameter is not affixed to its initial definition (see 3.4)}, then T is called a controlling type of the formal_abstract_subprogram_declaration. Similarly, if the result of a formal_abstract_subprogram_declaration for a function is of a specific tagged type T or of an anonymous access type designating a specific tagged type T{, and the result is not affixed to its initial definition (see 3.4)}, T is called a controlling type of the formal_abstract_subprogram_declaration. A formal_abstract_subprogram_declaration shall have exactly one controlling type, and that type shall not be incomplete.

Modify 13.1.1(18.4/6):

For a nonoverridable aspect of a type T that denotes one or more subprograms with a parameter or result {required to be }of type T or access T{, such a parameter or result shall not be affixed to its definition (see 3.4)}, and all of the denoted subprograms shall be [primitive]{derivable} for T. The same restriction applies to elements that denote subprograms within a nonoverridable aspect that is in the form of an aggregate.

!discussion

This AI has the same aims as its predecessors, AI22-0147-1 and AI22-0147-2, and some text and examples are copied from those, with modification. Please refer to the !discussion and !appendix in the predecessors.

Note that the special treatment described in 3.4(16.b) for some predefined operations on derivation could be defined with affixed parameters/result (or with the rooted types in AI22-0147-1): in the Static Semantics section of 4.5.2, all the “return Boolean” could be “return Boolean'Fixed”, and 4.5.6(8) could be

function “**” (Left : T; Right : Natural'Fixed) return T;

 

This AI only changes the rules for full conformance of subprogram profiles in 6.3.1. For less strict conformance, the rules (understandably) do not mention Fixed, so two subtype-conformant profiles remain subtype-conformant if Fixed is used in some places, even if Fixed occurs in different places in the two profiles.

For example, the following is illegal because the profiles are type conformant, and all calls would be ambiguous:

function Scale (Quantity, Factor : Real) return Real;

function Scale (Quantity: Real; Factor : Real'Fixed) return Real;

 

Note that upon derivation, an affixed parameter or result remains affixed to its original definition. This is important for the case when the derivation happens immediately within the same declaration list as the original definition, because if the parameter or result becomes unaffixed, it could then become a derivable operand/result again.

Note that if a user-defined equality operation overrides the predefined equality, if the type is tagged, both operands must be derivable (according to modified 3.9.2(10/2))  On the other hand, for an untagged type, if neither operand is derivable, the equality operation is not inherited, and the predefined equality would reemerge in any derivative.  This is similar to the fact that we can declare a predefined equality operation to be "abstract" in an untagged type, thereby making it completely unusable (except in generics where it might reemerge), something not allowed for a tagged type.

The wording for Pre'Class and Post'Class is modified to indicate that the T to NT substitution only happens for derivable parameters/results.  There is no corresponding change needed for Type_Invariant'Class, as the checks performed are based on whether a parameter/result has a part of the associated type, and not whether the parameter/result is "derivable" (in the sense defined in this AI).

!example

Here are some examples of use of the Fixed attribute to prevent parameters or results from being substituted as part of derivation:

type Bit_Set is new Set with ...
function Make_Bit_Set (Num_Bits : Positive) return Bit_Set'Fixed;

 

This constructor function is not to be inherited.  At a minimum, it would probably want to have a different name in any derived type.

type A is tagged ...
type B is new Some_Interface with ...
procedure Process (X : in out A; Y : B'Fixed);

 

In this case, type B is visibly extended from some useful progenitor interface, but other than that, there was no reason for B to be tagged, and making the Process operation dispatching on B and inherited by B's descendants would not make sense.

!ACATS test

This test would probably need both B tests and C tests to verify that inheritance does not happen when it is not supposed to happen and that the types of affixed parameters and results are unchanged in the inherited subprogram.

!appendix

(The start of this text was copied from AI22-0147-1.)

See ARG GitHub issue #125.


 

[a]The actual definition of the attribute (as needed to put it into the attribute annex) is missing here. The style forced on the RM source code by the Ada 9x team requires that to be in the style of the annex, as the text is actually shared (a mistake in several ways, but one that has always been far too expensive to fix).