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
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.
Following is a distillation of another ACATS test that gets different results
in different Ada 2012 compilers:
procedure B950001 is |
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 |
Would a call on RPE149 be legal? (Yes)
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.
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.]}
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 -- Obj.F3(Other_Obj) is equivalent to F3(Obj, Other_Obj). function R1b (B : in Natural) return Boolean -- Illegal -- (1b) renames P1.Intf(Obj).F1; -- because non-disp call on
abstract subp renames Obj.F2; -- because Obj.F2(B) is call on
abstract subp renames Obj.F3; |
|
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.
The existing B950001 contains these cases (now commented out).
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.