AI22-0075-1

!standard 3.3(20.1/5)                                    26-05-20  AI22-0075-1/13

!standard 3.3(23.7/3)

!standard 3.10.2(10.1/3)

!standard 3.10.2(10.5/7)

!standard 3.10.2(10.6/3)

!standard 4.1.6(3/5)

!standard 4.4(9.7/5)

!standard 6(1)

!standard 6.1(13/2)

!standard 6.1(23.1/3)

!standard 6.3.1(16.2/3)

!standard 6.5(5.6/5)

!standard 6.5(23/2)

!standard 7.5(2.9/5)

!standard 7.6(17.1/5)

!standard 7.6.1(13/3)

!class Amendment 23-06-04

!status Revision-202Y  25-07-30

!status work item 26-03-30

!status ARG Approved  16-0-0  25-07-30

!status work item 23-06-04

!status received 23-06-04

!assigned author Tucker Taft

!assigned author Niklas Holsti

!submitter Tucker Taft

!priority Medium

!difficulty Medium

!subject Explicitly Aliased Results

!summary

A function may be defined to have an explicitly aliased result, which can be used to implement user-defined indexing and other capabilities that currently require access discriminants or access results, thereby reducing complexity, and the need for dynamic accessibility checking.

!issue

The current mechanism for supporting user-defined indexing depends on a couple of complex features of the Ada language, in particular access discriminants and user-defined references.  With the addition of explicitly aliased formal parameters to the language, it seems there might be a corresponding notion of explicitly aliased results that could be a simpler approach to user-defined indexing, and might also replace other uses of access discriminants, access results, and user-defined references.  Should we investigate this possibility? (Yes.)

!recommendation

We propose allowing the result of a function to be declared as explicitly "aliased" or "aliased constant" with the meaning that the result is a reference to an aliased part of some object that is known (statically) to outlive the function call.  The result is required to refer to a variable if the result is declared simply "aliased", and can be used as the left-hand side of an assignment or as the actual parameter to an [in] out parameter.

If the result is declared to be "aliased constant" then it is considered a constant view of a part of some object known to outlive the function call.

If the function has one or more explicitly aliased formal parameters, an explicitly aliased result is known to either refer to a part of one of those formal parameters, or to an object that lives at least as long as the function itself (not just the function call).

For example, the following would be legal uses of the aliased result capability:

type R is record
    A, B : aliased T;
end record;

G : R;

 

Here we return a (writable) reference to an aliased part of a global variable:

function F1 return aliased T is
begin
    return G.A;
end F1;

 

Here we return a (writable) reference to an aliased part of an explicitly aliased in out formal parameter:

function F2(X : aliased in out R) return aliased T is
begin
    return X.B;
end F2;

 

Here we return a (read-only) reference to an aliased part of one of the explicitly aliased in formal parameters:

function F3(X, Y : aliased R) return aliased constant T is
begin
    if M(X) > N(Y) then
        return X.A;
    else
        return Y.B;
    end if;
end F3;

 

The user-defined indexing aspect Variable_Indexing would allow an aliased variable result as an alternative to the current restriction to reference types. The aspect Constant_Indexing already allows arbitrary return types, so there is no change there.

As an example of using Variable_Indexing with an aliased variable result, imagine a container type with Variable_Indexing => Element, where the Element function has an aliased result without the reserved word constant:

   type Container is private
      with Variable_Indexing => Element;

   function Element (C : aliased in out Container; Index : Index_Type)
     return aliased Element_Type;

   A : Container := ...;
begin
   A (1) := ...; -- Calls Element (A, 1) which returns ref to 1st element
   A (2) := ...; -- Calls Element (A, 2) which returns ref to 2nd element
   A (3) := A (1) + A (2);
      -- Calls Element (A, {1,2,3}) which return refs to first three
      -- elements of A.
      -- Caller knows result refers to object that lives at least as long
      -- as the Container A passed to Element, or the Element function,
      -- whichever life is shorter.
      -- A check is performed as part of return statement to ensure that.

 

Note that if a function is declared inside a generic, and the function is returning a reference to part of a generic in-out formal object, the formal object clearly lives at least as long as the function declared inside the generic:

generic
   FO : in out T;
package GP is
   function Part_Of_FO return aliased Elem;
end GP;

package body GP is
   function Part_Of_FO return aliased Elem is
   begin
      return FO.Part;  -- OK because FO lives as long as the

                       -- Part_Of_FO function.
   end Part_Of_FO;
end GP;

!wording

Add after 3.3(20.1/5):

Modify 3.3(23.7/3):

Modify 3.7.2(3.a/3):

Implementation Note: This attribute is primarily used on parameters{ and explicitly aliased results (see 6.1)}, 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{ and explicitly aliased results} 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, the attribute is statically True, so 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 3.10.2(10.1/3):

Modify 3.10.2(10.5/7[a]):

Add after 3.10.2(10.6/3):

For a call on a function with an explicitly aliased result (see 6.1), the master of the call is the innermost master that evaluates the call, and the accessibility level of the result of the call is the deeper of:

Modify 4.1.6(3/5):

 Variable_Indexing

This aspect shall be specified by a name that denotes one or more functions declared immediately within the same declaration list in which T, or the declaration completed by T, is declared. All such functions shall have at least two parameters, the first of which is of type T or T'Class, or is an access parameter with designated type T or T'Class. All such functions shall have {an explicitly aliased variable result, or }a return type that is a reference type (see 4.1.5), whose reference discriminant is of an access-to-variable type.

Modify 4.4(9.7/5):

In certain contexts, we specify that an operative constituent shall (or shall not) be newly constructed. This means the operative constituent shall (or shall not) be an aggregate{,} or a function_call{ whose result is not explicitly aliased}; in either case, a raise_expression is permitted.

Modify 6(1):

A subprogram is a program unit or intrinsic operation whose execution is invoked by a subprogram call. There are two forms of subprogram: procedures and functions. A procedure call is a statement; a function call is [an expression and returns a value]{a name which denotes the object returned by the function}. The definition of a subprogram can be given in two parts: a subprogram declaration defining its interface, and a subprogram_body defining its execution. [Redundant: Operators and enumeration literals are functions.]

Replace 6.1(13/2):

parameter_and_result_profile ::=

    [formal_part] return [null_exclusion] subtype_mark

  | [formal_part] return access_definition

with:

parameter_and_result_profile ::=

    [formal_part] return result_profile

result_profile ::=

    [null_exclusion][ aliased[ constant]] subtype_mark

  | access_definition

Modify 6.1(23.1/3):

An explicitly aliased parameter is a formal parameter whose parameter_specification includes the reserved word aliased. {An explicitly aliased result is the result of a function whose result_profile has the reserved word aliased. An explicitly aliased constant result is the result of a function whose result_profile has the reserved words aliased constant. An explicitly aliased variable result is an explicitly aliased result that is not an explicitly aliased constant result.}

Add after 6.3.1(16.2/3):

Modify 6.4(12/2):

A function_call denotes a [constant]{return object}, as defined in 6.5; the nominal subtype of the [constant]{object} is given by the nominal subtype of the function result.

Modify 6.5(5.6/5);

For any return statement that applies to [a]{the body of a} function [body]{without an explicitly aliased result}:

Add after 6.5(5.10/5):

For a function with an explicitly aliased result:

Modify 6.5(6/2-8/5):

For the execution of a simple_return_statement{ for a function without an explicitly aliased result}, the expression (if any) is first evaluated, converted to the result subtype, and then is assigned to the anonymous return object. {For the execution of a simple_return_statement for a function with an explicitly aliased result, the expression is a name, which is evaluated to determine the object denoted by the name, which is the return object.} 

{In the case of a function without an explicitly aliased result:

Modify 6.5(21/3-22/5):

{In the case of a function without an explicitly aliased result:

Delete 6.5(23/2):

In the case of a function, the function_call denotes a constant view of the return object.

Add before  6.5(25):

{         Usage

In the case of a function with a variable aliased result, the function_call denotes a variable view of the return object; otherwise, the function_call denotes a constant view of the return object.}

Modify 7.5(2.9/5):

Modify 7.6(17.1/5):

When {an aggregate, or} a function call {that produces a newly constructed object (see 4.4),} [or aggregate] is used to initialize an object, the result of the{ aggregate or} function call [or aggregate] is an anonymous object, which is assigned into the newly-created object. For such an assignment, the anonymous object may be built in place, in which case the assignment does not involve any copying. Under certain circumstances, the anonymous object is required to be built in place. In particular:

Modify 7.6.1(13/3):

The master of an object is the master enclosing its creation whose accessibility level (see 3.10.2) is equal to that of the object, except in the case of an anonymous{ newly constructed} object representing the result of an aggregate or function call. If such an anonymous object is part of the result of evaluating the actual parameter expression for an explicitly aliased parameter of a function call, the master of the object is the innermost master enclosing the evaluation of the aggregate or function call, excluding the aggregate or function call itself. Otherwise, the master of such an anonymous object is the innermost master enclosing the evaluation of the aggregate or function call, which may be the aggregate or function call itself.

Modify 9.2(4/2):

For tasks that are part or coextensions of a single object that is not a stand-alone object, activations are initiated after completing any initialization of the outermost object enclosing these tasks, prior to performing any other operation on the outermost object. In particular, for tasks that are part or coextensions of the object created by the evaluation of an allocator, the activations are initiated as the last step of evaluating the allocator, prior to returning the new access value. For tasks that are part or coextensions of [an]{a newly constructed} object that is the result of a function call, the activations are not initiated until after the function returns.

Modify 13.1(10/5):

A by-reference primitive is a user-defined primitive subprogram for a type T that has an access result designating type T{, an aliased result of type T}, or that has a formal parameter that is an access parameter designating type T or is aliased and of type T. It is illegal to specify a nonconfirming type-related representation aspect for an untagged type T if it is derived from a by-reference type or inherits one or more by-reference primitives, or if one or more types have been derived from T prior to the specification of the aspect and type T is a by-reference type or defines one or more by-reference primitives that are inherited by these descendants.

![h][i]discussion

The current aspect for Variable_Indexing requires the use of reference types which are based on access discriminants, two features that are complex and not widely needed in most other Ada programming. The proposed aliased result feature, when combined with explicitly aliased formal parameters, provides a simple, direct way to implement user-defined indexing.

As another use case, it is not uncommon to have a large global object that is declared within a package body, where parts of the object are to be made available, either read-only or for read-write, outside of the package. Currently an access result is the simplest way to provide that, but that gets into the area of potentially dynamic accessibility checks, and requires ".all" at the call site. Alternatively, user-defined references can be used, but that adds even more work and more complexity. A function with an aliased result provides a direct and natural way to return a reference to such a private global object, and read-write or read-only references can be provided simply by the absence or presence of the constant reserved word.

To provide a measure of safety, an aliased result is presumed to denote a part of one of the aliased formal parameters, if any (or of some hidden global object if there are no aliased formals), so in that sense an aliased result is like a renaming of this unspecified part. Issues associated with renaming are therefore relevant, such as disallowing renaming a discriminant-dependent part of something unless the encloser is known to be constrained.  Similarly, an Unchecked_Deallocation of the overall object of which the aliased result denotes a part would not be allowed. If Conflict_Checking is being performed, then the aliased result must be recognized as denoting a part of one of the aliased formals or the hidden global, so possible concurrent updates need to be noticed.

These safety concerns led us to associate run-time tampering checks with user-defined indexing of containers, and making the reference types used for container indexing controlled, but it would be nice if we could define compile-time checks which could take their place. Certainly for indexing into Stable containers, these aliased results should work well. For indexing into unstable containers, it might be possible to define some compile-time rules that would disallow unsafe concurrent morphing of the container while the aliased result exists.

These compile-time rules might be based on a variant of the notion of ownership which was proposed in AI12-0240-*, where the declaration of a named entity that is effectively an alias of some element of a data structure would effectively "lock down" the data structure, since this element might disappear if a change were made to the data structure as a whole. As long as the potential alias exists, the enclosing data structure could not be modified. This notion could also be used to allow renaming of discriminant-dependent components (currently disallowed in RM 8.5.1), as well as eliminate the current possibility of erroneous execution associated with passing such components by reference (described in RM 6.4.1(18/3)).

One possibility would be to have an aspect specification on a function returning an aliased result to indicate that some parameter is to be treated specially until the last use of the result. For example:

function Var_Indexing (X : aliased in out Container; Y : Index_Type)
   return aliased Elem_Type
   with No_Access => X;

 

indicating there is no other access to X permitted until the last use of the function result; or

function Const_Indexing (X : aliased Container; Y : Index_Type)
   return aliased constant Elem_Type
   with Read_Only => X;

 

indicating there is only read access to X permitted until the last use of the function result. In the absence of full information on the uses of globals, only calls on declared-pure subprograms, or subprograms that have no way of referencing Container (because neither is it not global to the called subprogram nor is it passed as a parameter) would be permitted in an expression where a No_Access or Read_Only aspect is in force.

Conceivably a similar aspect clause could be applied on an object renaming, to prevent any changes to the discriminant of the object whose discriminant-dependent component is being renamed. This idea is handled in a separate AI, AI22-0131-1.

Note that the Read_Only aspect seems adequate in almost all cases, so perhaps no need to talk about a No_Access aspect.

Note that the 'Constrained attribute is defined for an explicitly aliased result object if the type has discriminants (see RM 3.7.2). If the discriminants have defaults and the type is not immutably limited, then this attribute will need to be passed back along with the reference to the object as an implicit out parameter, much the way it is passed as an implicit in parameter for an [in] out parameter of an unconstrained-with-defaults discriminated type. The run-time overhead of such a 'Constrained flag could be eliminated by using the mechanism suggested in ARG GitHub issue #12 and addressed in AI22-0140-1 to label a discriminated subtype as either always constrained by its initial value, or always unconstrained.

We require the subtype of the object denoted by the return expression for a function with an explicitly aliased nonconstant result to match statically the function result subtype. Because the result might be passed as an out parameter when it has a variable aliased result, we cannot presume the object has been initialized, so performing any checks on the object might be a bounded error. We do not require static subtype matching for an aliased constant result, as it could be overly limiting. This implies constraint checking upon returning an aliased constant result, if the function result subtype does not statically match the subtype of the aliased object named in the return statement.

Note that the existing mechanism for returning a reference to an existing object is based on returning an object with an access discriminant, which is then implicitly dereferenced. With this existing mechanism, there is no guarantee that the designated object is initialized. Furthermore, calling a function with an aliased result is intended to be equivalent to a selected component or an indexed component, and there is no expectation that these operations check that the denoted component has been initialized. Therefore, it seems reasonable that calling a function with an aliased nonconstant result makes no guarantee that the return object denoted by the function call has been initialized. When returning an aliased constant result without statically matching subtypes, constraint checking and predicate checks would need to be performed.

The alternative is to perform constraint checks, null checks, and any enabled predicate checks on the constant aliased return. Presuming we require that the subtypes match statically, the only way any of these checks would fail would be because the element is uninitialized. It seems odd to do constraint checks, null checks, and predicate checks just to verify whether or not the object is initialized, and in fact, there is really no way to reliably check that. And if any of the checks do fail, the right reaction would probably be Program_Error. In general, one would presume that the elements of a container are initialized. Having a container with uninitialized elements would be a relatively bad situation, because how would one know which elements are uninitialized? If in fact there are some uninitialized elements, probably only higher-level logic could know which ones are uninitialized, so it would seem to be a waste to always perform potentially expensive checks just on the off chance that a container has some uninitialized elements.

The change to 3.10.2(10.5/5) generalizes the exemption of elementary types from checking the accessibility level of the result. It is somewhat silly to carry the master of the call into a function returning a scalar type, when we have exempted such functions in paragraph 10.2 from having the master of the call defined by the target.

!example

The examples in the !recommendation show what can be done with explicitly aliased results.  Here is one that is specifically designed for the Variable_Indexing aspect:

type Hash_Table_Entry;
type Hash_Table_Entry_Ptr is access Hash_Table_Entry;
type Hash_Table_Entry is record
     Key : Key_Type;
     Value : aliased Element_Type;
     Next : Hash_Table_Entry_Ptr;
end record;

type HTE_Array is array (Natural range <>) of Hash_Table_Entry_Ptr;

type Container is record
     Table : access HTE_Array := new HTE_Array(0..0); -- Will grow
end record
  with Variable_Indexing => Element;

function Element(C : aliased in out Container; K : Key_Type)
  return aliased Element_Type is
    Entry_Ptr : Hash_Table_Entry_Ptr :=

      C.Table(Hash(K) mod C.Table'Length);
begin
    --  Search for Entry with Key field matching K
    while Entry_Ptr /= null loop
       if Entry_Ptr.Key = K then
          --  Found it, return reference to Value field
          return Entry_Ptr.Value;
       end if;
       Entry_Ptr := Entry_Ptr.Next;
    end loop;
    raise Constraint_Error;
end Element;

...

C : aliased Container := ...
E : Element_Type := C(K);
...
C(L) := C(L) + 1;

 

This example is legal by the second bullet in the legality rule added after 6.5(5.8/5) because the accessibility level of the object named in the return statement is that of the access type Hash_Table_Entry_Ptr, which is not statically deeper than that of the master that elaborated the function body for Element.

!corrigendum 3.3(20.1/5)

@dinsa

@xbullet{the return object declared by an @fa{extended_return_statement} without the reserved word @b{constant};}

@dinst

@xbullet{the result of a function call with an explicitly aliased variable result (see @ref{6.1});}

!corrigendum 3.3(23.7/3)

@drepl

@xbullet{it is part of the object denoted by a @fa{function_call} or @fa{aggregate}; or}

@dby

@xbullet{it is part of a newly constructed object (see @ref{4.4}) denoted by a @fa{function_call} or @fa{aggregate}; or}

!comment Still need to do the 3.10.2 changes here.

!corrigendum 4.1.6(3/5)

@drepl

@xhang{@xterm{Variable_Indexing}This aspect shall be specified by a @fa{name} that denotes one or more functions declared immediately within the same declaration list in which @i{T}, or the declaration completed by @i{T}, is declared. All such functions shall have at least two parameters, the first of which is of type @i{T} or @i{T}'Class, or is an access parameter with designated type @i{T} or @i{T}'Class. All such functions shall have a return type that is a reference type (see @ref{4.1.5}), whose reference discriminant is of an access-to-variable type.}

@dby

@xhang{@xterm{Variable_Indexing}This aspect shall be specified by a @fa{name} that denotes one or more functions declared immediately within the same declaration list in which @i{T}, or the declaration completed by @i{T}, is declared. All such functions shall have at least two parameters, the first of which is of type @i{T} or @i{T}'Class, or is an access parameter with designated type @i{T} or @i{T}'Class. All such functions shall have an explicitly aliased variable result, or a return type that is a reference type (see @ref{4.1.5}), whose reference discriminant is of an access-to-variable type.}

!corrigendum 4.4(9.7/5)

@drepl

In certain contexts, we specify that an operative constituent shall (or shall not) be @i{newly constructed}. This means the operative constituent shall (or shall not) be an @fa{aggregate} or a

@fa{function_call}; in either case, a @fa{raise_expression} is permitted.

@dby

In certain contexts, we specify that an operative constituent shall (or shall not) be @i{newly constructed}. This means the operative constituent shall (or shall not) be an @fa{aggregate}, or a @fa{function_call} whose result is not explicitly aliased; in either case, a @fa{raise_expression} is permitted.

!corrigendum 6.1(13/2)

@drepl

@xindent{@fa{parameter_and_result_profile}@fa{@ ::=@ }@hr

@ @ @ @ [@fa{formal_part}]@ @b{return}@ [@fa{null_exclusion}]@ @fa{subtype_mark}@hr

@ @ |@ [@fa{formal_part}]@ @b{return} @fa{access_definition}}

@dby

@xindent{@fa{parameter_and_result_profile}@fa{@ ::=@ }@hr

@ @ @ @ [@fa{formal_part}]@ @b{return}@ @fa{result_profile}}

@xindent{@fa{result_profile}@fa{@ ::=@ }@hr

@ @ @ @ [@fa{null_exclusion}][@ @b{aliased}[ @b{constant}]]@ @fa{subtype_mark}@hr

@ @ |@ @fa{access_definition}}

!corrigendum 6.1(23.1/3)

@drepl

An @i{explicitly aliased parameter} is a formal parameter whose @fa{parameter_specification} includes the reserved word @b{aliased}.

@dby

An @i{explicitly aliased parameter} is a formal parameter whose @fa{parameter_specification} includes the reserved word @b{aliased}. An @i{explicitly aliased result} is the result of a function whose @fa{result_profile} has the reserved word @b{aliased}. An @i{explicitly aliased constant result} is the result of a function whose @fa{result_profile} has the reserved words @b{aliased constant}. An @i{explicitly aliased variable result} is an explicitly aliased result that is not an explicitly aliased constant result.

!comment the remaining changes (6.3.1, 6.5, 7.6) still need to be done.

!ACATS test

Test cases like the examples given above should be included.

!appendix

This AI is associated with Github Issue #32 (https://github.com/Ada-Rapporteur-Group/User-Community-Input/issues/32).


 

From: Randy Brukardt, ARG Editor

Sent: Wednesday, September 3, 2025

In his editorial review of AI22-0075-1, Niklas Holsti pointed out that there was no rule associated with the keyword constant in the result_profile for an aliased result. There has to be some rule, since if the return is declared to be a variable, the object returned had also better be a variable.

Tucker added the phrase ““the denoted view shall be a variable unless the result_profile has the reserved word constant,” to the wording of 6.5(5.8) to deal with this.

Since this is a simple and obvious fix, we’ll treat this as an Editorial Review change in the absence of an objection from someone here. Please speak up if you want this AI revoted at the next ARG meeting.

Niklas didn’t submit his reviews until the day before the deadline. Since he had identified a significant problem, and we were already at the deadline for submissions to WG 9, AI22-0075-1 was not submitted to WG 9 for approval at their next meeting; it will have to wait for a meeting in 2026.


 

From: Randy Brukardt, ARG Editor

Sent: Wednesday, March 4, 2026

The wording change of 4.1.6(3/3) is less redundant and thus reads much better if the new part is directly after the existing “shall have”. I’ve made this change as part of my editorial review.

Specifically, the wording was:

All such functions shall have a return type that is a reference type (see 4.1.5), whose reference discriminant is of an access-to-variable type{, or shall have an explicitly aliased non-constant result}.

Which repeats the “shall have” part. By moving the change forward, we get:

All such functions shall have {have an explicitly aliased non-constant result, or }a return type that is a reference type (see 4.1.5), whose reference discriminant is of an access-to-variable type.

Which reads better by not repeating itself.


 

From: Randy Brukardt, ARG Editor

Sent: Friday, March 6, 2026

Gary Dismukes requests that the hyphen be deleted from “non-constant” in this wording. Consider that change his Editorial Review of this AI.

However, the term “nonconstant” is never used in the RM, and it would be more consistent to talk about “explicitly aliased variable results”. As such, I’ve changed the text to use that term, and added a definition of that term in 6.2. Consider that my Editorial Review of his Editorial Review.


 

From: Niklas Holsti

Sent: Monday, March 30, 2026 12:37 PM

While reviewing this AI, I wonder if there should be new rules on the dynamic semantics of simple return statements when used in functions with explicitly aliased results. These are the only kind of return statement that can be used in such functions. Rules on simple return statements are left unchanged by this AI.

As I understand this AI, when a function with an explicitly aliased result returns, the thing returned is a view of some object that existed before the function call or, if created within the function execution, continues to exist after the return.

The rule that defines the dynamic semantics of a simple return statement is:

    6.5(6/2): For the execution of a simple_return_statement, the

    expression (if any) is first evaluated, converted to the result

    subtype, and then is assigned to the anonymous return object.

The problems I see with applying this rule to a function with an explicitly aliased result are:

- The expression is evaluated as an expression, although it consists of a single name. This seems to activate 4.4(10) so that the evalution yields the value of the named object, rather than a view of (or reference to) the object. The better alternative would be 4.1(11/2) which "determines the entity denoted by the name". It should be made clear that the expression is evaluated as a name.

- The conversion to the result subtype presumably should not occur, because the model should be that of renaming, and indeed the value of the denoted object should not be evaluated or read at all.

- The concept of a "return object" is problematic, because what would be the (sub)type of that object? "Views" are not (sub)types nor values.

Perhaps the "return object" for this kind of function should not be a new object, but the pre-existing object of which the result is a view.

As I understand the AI, the desired implementation is similar to the corresponding code that uses access-type returns, but without visibly using any access types or "'Access" or ".all". That seems to require a new rule for the dynamic semantics for simple return statements in functions with explicitly aliased results.

Rule 6.5(23/2) also needs some adjustment. The rule now says

    In the case of a function, the function_call denotes a constant view of the return object.

but this AI introduces function calls that are variable (non-constant) views.


 

From: Tucker Taft

Sent: Monday, March 30, 2026 12:57 PM

I agree that this AI seems to have completely missed the Dynamic Semantics implications of an aliased result.  Mea Culpa!  I suggest we re-open the AI for discussion at the next meeting, to give time to update the Dynamic Semantics section with appropriate wording changes.


 

From: Niklas Holsti

Sent: 12:19 PM April 27, 2026

I am a little surprised that [the Element function raises Constraint_Error when the element doesn't yet exist], and also all Variable_Indexing functions that I checked in the RM, do not allow the insertion of new elements in a container by means of Variable_Indexing and an assignment statement. I have not used user-defined indexing much in my own Ada programming, but I believe that languages like Python, where container-like data structures are used extensively, do let one enter new elements in those containers with plain assignment statements.

Perhaps this was discussed and decided when user-defined indexing was added to Ada?

Whatever the reason for not letting Variable_Indexing add elements, will it in general be possible to allocate a new object within a function with an explicitly aliased result, and return that new object? That seems desirable.


 

From: Niklas Holsti

Sent: 12:41 PM April 27, 2026

 

Perhaps the reason for not letting Variable_Indexing add elements is RM 5.2(7), which lets the variable_name and the expression in an assignment statement be evaluated in an arbitrary order?

If the statement has Variable_Indexing in the variable_name, and Constant_Indexing in the expression, and Variable_Indexing could add an element, the result could depend on which side is evaluated first.


 

From: Tucker Taft

Sent: 12:58 PM April 27, 2026

 

Typically there is a separate operation for adding an element to a container, rather than simply using assignment.  But it would be possible to do it as part of a variable indexing, though it might be considered error prone in some contexts.  And you make a good point, that if you use an aliased result for a constant indexing into the same container on the RHS, you could run into trouble.  The "Part_Of" AI is designed to catch such problems at compile-time.


 

From: Niklas Holsti

Sent: 3:32 PM April 27, 2026

 

Yes, I know that there are separate insert/add operations in the standard containers, and some of them could not be implemented by an assignment, for example the Vector operations that insert elements at specified points while shifting old elements to larger indices. But such operations do not exist for Sets and Maps, where adding elements never changes the existence or keys of old elements.

To avoid the mentioned problems with assignment statements, RM 5.2(7) could be changed to specify that if the LHS variable is a function call, that function call is evaluated after the RHS expression has been evaluated. Then Constant_Indexing could continue to raise an exception if the element does not exist, and Variable_Indexing could create new elements if desired.... (EDITED:) except that this order does not work in some cases: if the expression is tag-indeterminate (RM 5.2(9)) or contains the target_name symbol '@' (RM 5.2.1(5/5)), or some constraint from the target must be applied to the expression, for example an index range, as in Foo(A) := (others => 0).


 

From: Randy Brukardt

Sent: April 28, 2026 11:41 PM

The problem with aliased functions not checking predicates and constraints is that doing so prevents any sort of optimization of the predicate and constraint checks. Such optimization is critical in making Ada code performance to be similar to other compiled languages -- checks are very expensive.

The problem here is that in general, one cannot “trust” the subtype of an object. For instance, it is a bounded error (not erroneous) to index an array out of bounds; thus, it is necessary to check all values unless the compiler can prove that the value has to be valid. That depends upon some knowledge of how the object is initialized (and also requires care that the object cannot become “deinitialized”).

For a “normal” function, this is not a problem, as the checks are mandated as part of the return statement, and thus, the initialization state of the returned value can be calculated there. For a specific example, Janus/Ada does not allow uninitialized values as parameters, so if the return expression solely uses parameter values and predefined operators, constraint checks can often be performed at compile-time inside the function (meaning no runtime overhead at all).

However, this new mechanism mandates that no checks are performed. At the same time, any information that would have allowed avoiding the checks is erased by the function (I’m assuming here that the body of the function is not available - full program optimization is possible but not interesting since all information is at least in theory available). Thus, we are requiring full checks for any use of the returned object, both for writing it and (more importantly) for reading it. There is no possible optimization, since the validity status of the returned object is unknown (and thus, assume-the-worst requires making the checks).

This is in contrast to the usual situation for objects, where most objects are in fact “known-to-be-valid” and allow checking optimization. It’s also in contrast to what the subprogram specification appears to say; the “lying” of renames has proven to be a problem for comprehension to the point that we’ve eliminated the need to even write the subtype at all. This situation feels similar (the function here is returning a renaming of an object, but unlike a renaming, nothing about the original object is known).

Predicates (at least those without side-effects, as recommended) act much like and have similar restrictions to evaluation as constraints. But they are often much more expensive to evaluate and thus much more valuable to eliminate. (I personally do not believe in ever turning off checks: either the compiler can eliminate them or they could fail and thus should continue to be evaluated for safety. Separate “proof” tools have little to add here, and they can make mistakes because of differences between the compiler and tool. That is one reason I am in favor of the proposed “Static_Checks” pragma, as proof of the lack of check failure can be provided by the compiler, without needing any separate tools at all.)

I’m not sure about the best way to address this (or if it is even possible to address this). One possibility would be to encourage aliased functions to return subtypes without constraints or predicates. That would avoid lying, but also would require checks to be repeated (especially for predicates) as existing knowledge would be erased.


 

From: Tucker Taft

Sent: April 28, 2026 9:40 AM

 

The predicates are relevant, because they were checked when you assigned to the object.  Checking them on reading the object is generally never done, because you can assume the value was checked "on the way in".

As far as Vectors, I have to presume that if you create any empty elements, you are going to immediately fill them, and certainly not read them before you do fill them.  And again, checking predicates is not an efficient way to determine whether an empty element wasn't filled in.


 

From: Randy Brukardt

Sent: April 30, 2026 11:18 PM

 

>The predicates are relevant, because they were checked when you assigned to the object.  

Not really. Predicates are only trustworthy if you know what happened since they were last checked. (Remember that assigning components does not check predicates.) Function returns were intended to be a place where you can trust predicates; it is unfortunate to lose that.

But I have to admit that I am coming around to (most of) your view: it doesn’t make sense to check anything about aliased returns. Anything the compiler (or user) would have known about the actual object returned is lost -- that’s the nature of abstraction. The problem is that the return subtype is lying about that view (one can never trust dynamic predicates associated with such a function, while you might have been able to for the underlying object).

Steve expressed to me this concern as well. Don’t know if he still feels this way.


 

From: Randy Brukardt

Sent: May 18, 2026 10:32 PM

This does not define "master of the function call", which is used in other rules for functions without qualification. For instance, 6.4.1(6.4/3) requires actuals to aliased parameters to be not statically deeper than the master of the call.  There's a dynamic version of this rule in 6.4.1(15.2/3) as well. (I didn't look for others.) With this wording, what do those checks do???


 

From: Randy Brukardt

Sent: May 18, 2026 10:33 PM

I noticed this in relation to Github #156. You may want to look at the last post from Tucker Taft in that issue and address that here as well.


 

From: Tucker Taft

Sent: May 19, 2026  8:57 PM

Good catch.  I am thinking about the correct fix ...

I believe the term "master of the (function) call" is used in 6.4.1 and 6.5.  If you know of any other places, please let me know!


 

From: Tucker Taft

Sent: May 19, 2026 9:56 PM

I decided to define the master of a call on a function with an explicitly aliased result to always be the innermost master.  This means no checks on the level of the actual parameters.  Their levels are relevant to the level of the aliased result, and presumably there will be checks on how the result is used.

So the checking is effectively reversed.  If the result is not explicitly aliased, then the master of the result comes from context, and that limits the accessibility of the aliased formals.  If the result is explicitly aliased, then the levels of the aliased formals determines the level of the result, which is then checked to be sure the result is appropriately used.


 

[a]AI22-0156-1 made a small editorial change to this wording (its in the working draft of the RM but not the one available to the public), I've added it here.

[b]NOTE: This doesn't prevent an actual parameter from being updated after a call, so it still could lead to erroneous execution.  Part_Of aspect might help here ...

[c]You're still changing these into bullets, which isn't possible with our tools unless the paragraph numbers are changed. These will all end up as inserted paragraphs (unless there happen to be some deleted bullets in the ancient text, I don't think there is but I didn't check).

[d]An alternative is to say something like: "the following rules apply only to a function without an explicitly aliased result" if we are desperate to avoid bullets.  And then end with another such statement saying the opposite.  That doesn't sound as friendly to the reader. 

Perhaps we could say things like "If an unaliased return object has any parts ..." so it is just a one-word addition to limit a rule to "functions without an explicitly aliased result".

[e]I had thought that the best plan was to come up with a simple intro phrase (the one you had is too long). And I thought the best thing to do with the redundant paragraph 7 was to delete it, as it loses value as it gets more complicated. (Then there is no introduction needed.)

[f]Looking at these 4 paragraphs, I don't think any of them make sense with just adding "unaliased", mostly because the return object part is buried in the middle, and this info needs to come first.

The real question here is whether we care much about these paragraph numbers. If not, (perhaps the only use is in the ACATS test objectives documents), then making them all bullets and reordering them appropriately (so there is only one header) is the thing to do. But they will all end up with inserted numbers even though all of the surrounding paragraphs are deleted. The Scribe markup defines paragraph kinds, and there is no markup to change the kind of a paragraph -- the text has to be moved into a new paragraph with the new kind.

[g]Same comment as above.

[h]Perhaps RM H.4(23.7/5) should be reviewed for possible changes: "a function returns a writable reference to V if ..." An explicitly aliased variable result seems like a writable reference.

[i]Interesting point.  I don't think we need to update these definitions, as they are focused on dereferences of access values, which is appropriate to the notion of an "indirect" global.  I agree the terms might be generalized to include the notion of an aliased return, but I don't think we want to think of these as "indirect" globals.  On the other hand, the rules about "direct" references to global variables may need to be refined given aliased returns from functions.