AI22-0147-3
!standard 3.4(17/2) 26-07-01 AI22-0147-3/02
!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(13/2)
!standard 6.1(15/5)
!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
Use the at reserved word on a formal parameter or result type to attach 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.
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.)
We recommend allowing the insertion of the reserved word at 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 attached 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 at, the subprogram is still inherited on derivation from this type, but any attached parameters, or an attached result, remain attached to the original definition in the profile of the inherited subprogram — they do not change to match the derived type.
Adding at to 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 attached 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 attached to its initial definition, is called a "derivable" parameter/result (for T).
As an example of where you might want to use an attached 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 : at Real) return Real …
Now the profile of the inherited operation is:
function Scale (Quantity : Length; Factor : at Real) 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 at Real is (Num / Den);
The inherited Ratio function takes two Length operands and returns a Real result (with at 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 at reserved word 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 : at Holder_Storage_Pool; 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.
Modify 3.4(17/2):
{A formal parameter or result of a user-defined primitive subprogram (for some type T) can be attached to its initial definition, indicated by the presence of the reserved word at in its definition. 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]{1}){, other than in the specification for an attached formal 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 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. We have chosen to drop the requirement for matching for "attached" parameters and results, since they don't get substituted on derivation. One could argue that the existing matching requirement should only have talked about derivable parameters and results, and not include the access-to-subprogram parameter/result case, but we have left that as is for now.][a]
Replace 6.1(13/2) with:
parameter_and_result_profile ::=
[formal_part] return [at] [null_exclusion] subtype_mark
| [formal_part] return [at] access_definition
Replace 6.1(15/5) with:
parameter_specification ::=
defining_identifier_list : [at] [aliased] mode [null_exclusion] subtype_mark
[:= default_expression] [aspect_specification]
| defining_identifier_list : [at] access_definition
[:= default_expression] [aspect_specification]
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 attached 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 reserved word at, the formal parameter is attached to its current definition, and is not derivable (see 3.4). Similarly, if a parameter_and_result_profile includes the reserved word at after the reserved word return, the result of the subprogram is attached 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 attached results (see 3.4)}, and for corresponding parameters:
Add after 6.3.1(18.1/3):
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 attached 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.
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.
The difference is that this AI (and AI22-0147-2) implements the aims by marking the formal parameters and results that should have special treatment (here, attached to their original definition) without introducing new types, while AI22-0147-1 introduces a whole new category of types, the “rooted types”, and the attribute T’Rooted to denote the rooted type derived from a specific type T. Formal parameters and results of a rooted type are then (in our terms) not derivable. The effect on operation inheritance and dispatching is achieved by the normal typing rules (with minor additions to implicitly convert between T and T’Rooted). But at the time of writing, AI22-0147-1 does not prevent use of rooted types in other ways, for example for declaring stand-alone objects or components, which seems useless and possibly confusing.
This use of a reserved word on formal parameters and function results was first suggested by Jean-Pierre Rosen, making use of abs. That choice has raised some objections, with use and at proposed as alternatives. To help decide, here are a number of possible parameter specifications with the abs and at, assuming that the chosen keyword is placed at the front, as in the modified syntax above:
|
P : abs T P : abs in out T P : abs aliased T P : abs aliased in out T P : abs access T P : abs not null access T |
P : at T P : at in out T P : at aliased T P : at aliased in out T P : at access T P : at not null access T |
And here are examples of return-type specifications:
|
return abs T return abs not null T return abs access T return abs not null access T |
return at T return at not null T return at access T return at not null access T |
We prefer having the reserved word associated with the formal parameter or result as a whole, rather than associating it directly with the subtype_mark, so we put the reserved word immediately after the ":" or return. If we put it immediately in front of the subtype_mark, we would end up with a reserved word in the middle of an access_definition for an access parameter or result, which would be awkward.
Note that the special treatment described in 3.4(16.b) for some predefined operations on derivation could be defined with attached 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 at Boolean”, and 4.5.6(8) could be
function “**” (Left : T; Right : at Natural) 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 at, so two subtype-conformant profiles remain subtype-conformant if at is inserted in some places, even if at occurs in different places in the two profiles. To achieve the same result in AI22-0147-1 the subtype (and lesser) conformance rules would have to be changed to equate T and T’Rooted.
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 : at Real) return Real;
Note that upon derivation, an attached parameter or result remains attached 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 unattached, 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).
Here are some examples of use of the at reserved word 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 at Bit_Set;
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 : at B);
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.
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 attached parameters and results are unchanged in the inherited subprogram.
(The start of this text was copied from AI22-0147-1.)
From: Randy Brukardt
Sent: June 26, 2026 12:08 AM
The wording (13.1.1(18.4/6)) for “overridable” only requires subprograms to be primitive. The entire point of that wording was to require that the subprograms are those that are properly inherited. So that wording needs some changes.[b]
Just saying that the subprogram is inherited isn’t enough. Let’s look at some examples:
package P1 is
type T1 is tagged ...
with Constant_Indexing => F1;
function F1 (Obj : at aliased in T1; Idx : in Natural)
return Natural;
end P1;
The Constant_Indexing here needs to be illegal because it is not inherited for T1. An extension of T1 would not get an F1 function, and that is something we need to avoid (which was the point of 13.1.1(18.4/6)). I think you see this example and agree (but fail to note that to get this to be illegal requires a wording change to 13.1.1(18.4/6)).
But functions can be inherited for multiple types, so just saying it is inherited isn’t enough:
package P2 is
type My_Int is range ...;
type T2 is tagged ...
with Constant_Indexing => F2;
function F2 (Obj : at aliased in T2; Idx : in My_Int) return Natural;
end P2;
F2 is “inheritable”, but for type My_Int, not T2. An extension of T2 would not get an F2, the problem still exists. So just saying that it can be inherited is not enough. And even saying that it has to be derivable for the appropriate type is not enough:
package P3 is
type T3 is tagged ...
with Constant_Indexing => F3;
function F3 (Obj : at aliased in T3; Idx : in T3) return Natural;
end P3;
Here, F3 is inherited for an extension of T3 (call it NT3). But it still isn’t inherited properly for an indexing for NT3, as what is inherited is:
function F3 (Obj : at aliased in T3; Idx : in NT3) return Natural;
And here the prefix has the wrong type for an indexing of type NT3.
[I admit that this is pretty strange, but it is legal, and nonoverridable is used for many aspects, not just this one, so it might be more realistic for some other aspect, potentially one not defined yet. The wording needs to deal with this and similar cases.]
I have not made any effort to see if there are further problems with private types or interface types. In any case, the easiest thing to do would be to ban “at” in any parameter of the appropriate specific type in any subprogram specified for a nonoverridable aspect. We could try to ban just parameters that are specified in the expected profile (allowing it in wildcard parts), but that sounds a lot harder to word (as we try to talk about the profile as little as possible) and sounds more error-prone as well.
I think we’re OK with the class-wide contracts, assuming that the generic formal derived type model does inheritance “correctly” (and I’m not sure what that means exactly). [c]That at least allows us to kick-the-can to formal type rules.
Your wording changes try to handle the cases of the function with the precondition having at parameters. But I was thinking about the other case (when the called function has at parameters).
I do think that the result can be pretty unexpected (compared to the current situation), but I think it is well-defined. I’m thinking of cases like:
function Is_Equal (Left: at in T4; Right : in T4) return Boolean;
procedure Use_It (Foo, Bar : in T4)
with Pre’Class => not Is_Equal (Foo, Bar);
This precondition will be illegal, even though the types appear correct, as in Pre’Class, T4 is resolved as NT4 (a formal derived type), and Is_Equal has a T4 and an NT4 parameter, so Foo will not match the Left parameter.
I think your rewording of that text is intended to allow:
procedure Use_It_Again (Foo : at in T4; Bar : in T4)
with Pre’Class => not Is_Equal (Foo, Bar);
I didn’t try to figure out if the wording changes actually had that effect.
[a]Do existing compilers even enforce such a restriction? It might be worth writing a test program to check, as if they do not do so, eliminating the nonsense requirement would make more sense. (I'd guess that Janus/Ada does not make such a check on access-to-subprogram parameters, but I can't be certain.)
[b]I have modified the proposed update to 13.1.1(18.4/6) to disallow attached parameters/results if they are required to be of type T or access T. Its seems OK for other parameters to be attached if they are not part of the required profile.
[c]As modified, the T to NT substitution only happens for derivable parameters/results, which effectively ignores attached parameters/results.