AI22-0024-1

!standard 3.9.3(7/5)                                    22-10-21  AI22-0024-1/04

!standard 4.1.3(9.2/3)

!class binding interpretation 22-01-20

!status Corrigendum 1-2022  22-09-09

!status ARG Approved  8-0-1  22-09-09

!status work item 22-01-20

!status received 21-04-28

!priority Low

!difficulty Hard

!qualifier Clarification

!subject Abstract prefixed views

!summary

If the prefix is class-wide and controlling, all other controlling parameters become class-wide in the profile of the renaming of the prefixed view; the subprogram is not abstract even if the original one was, and a call on the renaming is equivalent to making a dispatching call on the underlying subprogram, implying a Tag_Check if there are multiple controlling parameters.  Otherwise, the renaming of the prefixed view is illegal if the original subprogram is abstract.

When renaming a prefixed view with a tag-indeterminate prefix, the prefix is always interpreted as being statically tagged, and will be illegal if the type is abstract.

!issue

Following is a distillation of another ACATS test that gets different results

in different Ada 2012 compilers:

 

procedure B950001 is
  package Nested is
     type Intf is synchronized interface;
     procedure PEN1 (Param : in out Intf) is abstract
        with Synchronization => By_Entry;
  end Nested;

  generic
     with procedure P;
  package Gen1 is
     Decl : Natural;
  end Gen1;

  procedure Test (Intf_In_Parm : in Nested.Intf'Class;
                  Intf_In_Out_Parm : in out Nested.Intf'Class) is
     package GPE139 is new Gen1 (Intf_In_Parm.PEN1);
                                        -- ERROR: Prefix is constant
     package GPE149 is new Gen1 (Intf_In_Out_Parm.PEN1);
                                        -- OK. Prefix is variable
  begin
     null;
  end Test;
...

 

The test is trying to check legality of prefixes of prefixed views.

However, one of the tested compilers rejected the "OK" instantiation because

the subprogram is abstract.

The actual here is a prefixed view of an abstract subprogram, but this view is

of a dispatching call that is legal for an actual call. But is it legal as the

actual of a generic subprogram? (Yes.)

The test also renames these calls:

procedure RPE139 renames Intf_In_Parm.PEN1;  -- ERROR: Prefix is constant
procedure RPE149 renames Intf_In_Out_Parm.PEN1; -- OK. Prefix is variable

 

Would a call on RPE149 be legal? (Yes)

!recommendation

One situation in Ada that resembles this one is the case where a formal derived (or private) type is instantiated with a class-wide type.  Here are the relevant rules, from RM 12.5.1(23.1/3-23.3/2):

In the case where a formal type has unknown discriminants, and the actual type is a class-wide type T'Class:

This rule introduces an equivalence to a wrapper that performs a dispatching call.

If we look at the current rules relating to prefixed views, we find that the profile of a prefixed view is defined in 4.1.3(9.2/3):

The selected_component denotes a view of this subprogram that omits the first formal parameter. This view is called a prefixed view of the subprogram, and the prefix of the selected_component (after any implicit dereference) is called the prefix of the prefixed view.

 The semantics of a call on a prefixed view are given in RM 6.4(9.1/5):

If the name or prefix of a subprogram call denotes a prefixed view (see 4.1.3), the subprogram call is equivalent to a call on the underlying subprogram, with the first actual parameter being provided by the prefix of the prefixed view (or the Access attribute of this prefix if the first formal parameter is an access parameter), and the remaining actual parameters given by the actual_parameter_part, if any.

This does not clarify the properties of a renaming of a prefixed view when the prefix is a class-wide type and the operation is a primitive operation, since in that case, the call would inevitably be a dispatching call, and any other controlling parameters would be expected to be class-wide.  This is where the analogy with the case for generics becomes relevant.

We would recommend defining the properties of a renaming of a prefixed view (or equivalent rules for matching a prefixed view as an actual for a formal subprogram) by specifying that if the prefix is class-wide and controlling, then any other formal parameters of the renaming that would correspond to controlling parameters become (access to) class-wide, and the subprogram is not abstract even if the original subprogram was abstract.  In all other cases, if the original subprogram is abstract, then the renaming would be considered illegal.  There seems no value in allowing an abstract renaming, and it would only add implementation complexity to decide how to deal with such a renaming.

In the generic formal type case, there is a special situation when the primitive is a function with no controlling parameters.  Fortunately, this case does not arise for our prefixed-view special case, because we have required the first parameter be controlling, so there is no need to raise Program_Error.

We also need to deal with the case where in a renaming the prefix of the renamed prefixed view is tag-indeterminate.

!wording

Modify 4.1.3(9.2/3):

The selected_component denotes a view of this subprogram that omits the first formal parameter. This view is called a prefixed view of the subprogram, and the prefix of the selected_component (after any implicit dereference) is called the prefix of the prefixed view. {In the special case where the prefix is class-wide and controlling, the prefixed view is not abstract, even if the  unprefixed view is abstract; otherwise the prefixed view is abstract if and

only if the unprefixed view is abstract.

In the context of a subprogram renaming (see 8.5.4) or the matching of an actual subprogram with a formal subprogram (see 12.6), a prefixed view whose prefix is class-wide and controlling shall have an expected profile such that each formal parameter or result type corresponding to a controlling parameter (or access parameter) or controlling result (or access result) in the unprefixed view, is (or designates) the same class-wide type as the prefix. In a renaming or matching of a prefixed view whose prefix is of a specific type or is not controlling, the unprefixed view shall not be abstract and the types of the formal parameters or result in the expected profile shall match those of the corresponding formal parameters or result of the unprefixed view. [Redundant: In either case, any constraints, null exclusions, or predicates on the formal parameters or results in the view declared by the renaming or instantiation come from the unprefixed view, as in the normal case.] When renaming or matching a prefixed view whose prefix is tag-indeterminate and controlling, the controlling tag is statically determined to be that of the type of the prefix.}

Modify 6.4(9.1/5):

If the name or prefix of a subprogram call denotes a prefixed view (see 4.1.3), the subprogram call is equivalent to a call on the underlying subprogram, with the first actual parameter being provided by the prefix of the prefixed view (or the Access attribute of this prefix if the first formal parameter is an access parameter), and the remaining actual parameters given by the actual_parameter_part, if any. {[Redundant: In the case where the prefix is class-wide and controlling, if there are multiple controlling parameters, a check is made that they all have the same tag, as defined in 3.9.2. A call on a renaming of a prefixed view results in a dispatching call if the prefix of the prefixed view is class-wide and controlling.]}

!discussion

4.1.3(9.2/3) defines a prefixed view to be a view of a subprogram – it is not itself a call – it is a subprogram that is called in the usual way (with one less parameter).

6.4(9.1/5) makes it clear that a call on a prefixed view is equivalent to the underlying usual call. So a dispatching call on a prefixed view should be allowed.

However, the existing rules do not provide a full description of the properties of the prefixed view itself. This matters when the prefixed view is renamed or used as the actual for a generic formal subprogram. In particular, is the renamed prefixed view still abstract even if the prefixed view represents part of a dispatching call? This AI attempts to answer by adding rules that define the properties of a renaming (and equivalent situation with generic instantiation), distinct from the rules for a "direct" call on a prefixed view.

To remain compatible with the existing rules for a direct call on a prefixed view (rather than through a renaming or generic instantiation), it seems important to continue to define such a call by a simple transposition of the prefix into the first parameter position, and then allow other rules to be applied in the "usual way" (such as dispatching, tag checks, and how tag-indeterminate parameters are handled).  When a prefixed view is renamed, however, we have a new declaration, and this AI proposes that this new declaration is best thought of as defining a wrapper around a body that performs the call (similar to a renaming-as-body), and in that case some of the special rules such as how tag-indeterminate operands are handled might no longer apply.

We can break down this problem into a number of cases:

It might help to look at an example:

package P1 is
  type Intf is interface;
  function F1 (A : in Intf; B : in Natural)
     return Boolean is abstract;
  function F2 (A : in Intf'Class; B : in Intf)
     return Boolean is abstract;
  function F3 (A, B : in Intf) return Boolean is abstract;

      -- Obj.F3(Other_Obj) is equivalent to F3(Obj, Other_Obj).
end P1;

procedure Test (Obj : in P1.Intf'Class; Val : in Natural) is
  function R1a (B : in Natural) return Boolean renames Obj.F1; -- (1a)

   function R1b (B : in Natural) return Boolean -- Illegal      -- (1b)

      renames P1.Intf(Obj).F1; -- because non-disp call on abstract subp
  function R2 (B : in P1.Intf) return Boolean  -- Illegal      -- (2)

      renames Obj.F2;  -- because Obj.F2(B) is call on abstract subp
  function R3 (B : in P1.Intf'Class) return Boolean            -- (3)

      renames Obj.F3;
begin
  if R1a(1) then     -- OK. R1a will dispatch internally based on Obj
     null;
  elsif R2(Obj) then -- Not legal even though P1.F2(Val, Obj) is legal                           
     null;
  elsif R3(Obj) then -- OK. R3 dispatches internally (with Tag_Check)
     null;
  end if;
end Test;

 

 

R1a and R1b correspond to the two alternatives of the first case above. R1a is treated as not being abstract – the dispatching is "hidden" in the wrapper created by the renaming of the prefixed view. R1b is illegal because it is a renaming of an abstract prefixed view, and we deem such abstract renamings to be unduly complex to support.

R2 corresponds to the second case, and R2 is not primitive and is not a dispatching operation, and so cannot legally be called.  Furthermore, the "internal" call with the formal parameter B would not be legal if F2 is abstract, so we deem the renaming as illegal.

Finally, R3 demonstrates the last case. Note that the profile of R3 now has a class-wide parameter where before it had a controlling parameter.  Internally R3 does a dispatching call, with a Tag_Check, but from the "outside" it is now a class-wide operation.

!ACATS test

The existing B950001 contains these cases (now commented out).

!appendix

From: Randy Brukardt

WG 9 Review issue #175 - May 22, 2021

Following is a distillation of another ACATS test that gets different results

in different Ada 2012 compilers:

procedure B950001 is

   package Nested is

      type Intf is synchronized interface;

      procedure PEN1 (Param : in out Intf) is abstract

         with Synchronization => By_Entry;

   end Nested;

   generic

      with procedure P;

   package Gen1 is

      Decl : Natural;

   end Gen1;

   procedure Test (Intf_In_Parm : in Nested.Intf'Class;

                   Intf_In_Out_Parm : in out Nested.Intf'Class) is

      package GPE139 is new Gen1 (Intf_In_Parm.PEN1); -- ERROR: Prefix is constant

      package GPE149 is new Gen1 (Intf_In_Out_Parm.PEN1); -- OK. Prefix is variable

begin

   null;

end Test;

...

The test is trying to check legality of prefixes of prefixed views.

However, one of the tested compilers rejected the "OK" instantiation because

the subprogram is abstract.

The actual here is a prefixed view of an abstract subprogram, but this view is

of a dispatching call that is legal for an actual call. But is it legal as the

actual of a generic subprogram? Should it be?

I can't find anything definitive. 3.9.3(7/5) suggests that the subprogram that

is called is abstract, but whether that is still the case for a generic actual

is unclear.

It seems weird at best that you can call the subprogram but that you couldn't

pass it as a generic actual. Could there be a reason for that??

I'd expect a similar issue with an instance without the synchronization, but I

have not tested (or analyzed) that case.

The test also renames these calls:

  procedure RPE139 renames Intf_In_Parm.PEN1;     -- ERROR: Prefix is constant

  procedure RPE149 renames Intf_In_Out_Parm.PEN1; -- OK. Prefix is variable

Would a call on RPE149 be legal? If the prefixed view is abstract, it would be

odd if a call on the renames is legal (the dispatching is bound in the prefix).

Tucker Taft suggested that a potential precedent might be the rules for what

happens when you instantiate a generic having a formal extension parameter

with a class-wide type. Effectively wrappers are created for each of the

primitives, which take 'Class parameters and do the dispatching-related checks

and the actual dispatching call internally. We should think of a prefixed-view

of a subprogram that has a class-wide prefix as effectively a non-abstract

wrapper, in analogy with the rules given in 12.5.1(23.1-23.3). Thus the lines

marked as "OK" are in fact "OK."

Note that the operation should still be abstract if the call isn't dispatching

(that is, the prefix is not the controlling parameter of the call).

This one needs quite a bit of wording and thought (it's not at all simple), so

it is best deferred.