Version 1.1 of ai05s/ai05-0197-1.txt
!standard 3.9.2(20.2/2) 10-02-08 AI05-0197-1/01
!class binding interpretation 10-02-08
!status work item 10-02-08
!status received 09-06-09
!subject Dispatching when there are multiple inherited operations
!summary
Dispatching calls the inherited concrete subprogram when multiple operations
are inherited.
!question
AI05-0126-1 changes the wording of 3.9.2(20.2/2) to talk about "the
corresponding operation of the parent type or progenitor type from which the
operation was inherited" handles the cases that motivated that question.
But what happens here?
package Pack1 is
type Int1 is interface;
procedure Op1 (X : Int1) is null; --
end Pack1;
package Pack2 is
type T2 is tagged null record;
private
procedure Op1 (X : T2); --
end Pack2;
with Pack1, Pack2;
package Pack2.Pack3 is
type T3 is new Pack2.T2 and Pack1.Int1 with null record;
--
--
--
end Pack2.Pack3;
with Pack2.Pack3;
procedure Proc4 is
X : Pack3.T3;
begin
Pack2.Pack3.Op1 (X);
end Proc4;
Now there are two implicitly declared operations that could be considered as
"the operation" in the new 3.9.2(20.2). At any point, only one or the other
will be visible, but which one depends on whether the private part of
Pack2.Pack3 is visible at that point. Applying 3.9.2(20.2) [as modified by
AI05-126] means we have to determine which one we're talking about, so that
we can determine "the parent type or progenitor type from which the operation
was inherited" -- we can't call both. If it's the first one, then the call to
Op1 is the same as a call on (1), which is null; if it's the second
one, then it's the same as a call on (2). Which is intended? (A call on (2).)
!recommendation
(See Summary.)
!wording
Modify 3.9.2(20.2/2):
* otherwise, the action is the same as the action for the corresponding operation
of the parent type or progenitor type from which the operation was inherited.
{If there is more than one such corresponding operation, the action is that
for the operation that is not a null procedure, if any; otherwise, the action
is that of an arbitrary one of the operations.}
Add to the AARM note 3.9.2(20.a):
For the last bullet, if there are multiple corresponding operations for the parent
and progenitors, all but one of them have to be a null procedure. (If the
progenitors declared abstract routines, there would have to be an explicit
overriding of the operation, and then the first bullet would apply.) We call the
non-null routine if one exists.
[Editor's note: An alternative formulation for this wording would be to talk about
the parent: "If there is more than one such corresponding operation, the action is that
for the operation of the parent type, if any; otherwise, the action is that of an
arbitrary one of the operations." This works because the parent operation is the
only one that can be concrete. Not sure if this is better; rewording of the
AARM Note would be required if this option is chosen.]
!discussion
Wording using "overriding" seems better than the proposed wording: "If there
is more than one such corresponding operation, the action is that for the
operation that overrides the others."
But this does not work properly, as "overriding" depends on the order of implicit
declarations. If the declarations don't occur at the same place, the later one
overrides the earlier one (by 8.3(12)), even though the later one might be null.
For example:
package Pack1 is
type Int1 is interface;
private
procedure Op1 (X : Int1) is null; --
end Pack1;
package Pack2 is
type T2 is tagged null record;
procedure Op1 (Y : Int1); --
end Pack2;
with Pack1, Pack2;
package Pack1.Pack3 is
type T3 is new Pack2.T2 and Pack1.Int1 with null record;
--
--
--
--
end Pack1.Pack3;
package body Pack1.Pack3 is
Obj : T3;
begin
Op1 (X => Obj); --
end Pack1.Pack3;
For the call (5), (4) is the declaration that is called. That can be verified
by using the formal parameter name X in named notation in the call; a call using
parameter name Y is illegal as declaration (3) is hidden from all visibility.
(4) is a null procedure, which we don't want to call in this case. Since (5)
is a dispatching call, we call the body determined by the definition of the
execution of a dispatching call. The wording using overriding would indeed
call (4), which is not what we want.
However, so long as we take care that the wording of the definition of the
execution of a dispatching call is correct, we are OK (even for statically
bound calls like this one). With proper wording, we still call the body of
the operation declared at (2). That makes it important that we get this wording
right.
[Editor's note: An alternative solution would be to ban hidden primitive
routines for interfaces. They're already illegal for abstract subprograms,
so only null subprograms would be affected, and this case seems to be messier
than it is worth. Then we could use the overriding wording.]
--!corrigendum 3.9.2(20.2/2)
!ACATS Test
It is thought that existing ACATS tests try examples like this.
!appendix
!topic AI05-126
!reference AI05-126, 3.9.2(20-20.2)
!from Adam Beneschan 09-06-09
!discussion
I have a feeling AI05-126 still isn't quite right.
I notice that the phrase in 3.9.2(20.2) was changed from
"otherwise, the action is the same as the action for the
corresponding operation of the parent type"
to
"otherwise, the action is the same as the action for the
corresponding operation of the parent type or progenitor type from
which the operation was inherited".
I think that that change was necessary, or else this simple case couldn't be
handled:
package Pack1 is
type Int1 is interface;
procedure Op1 (X : Int1) is null;
end Pack1;
package Pack2 is
type T2 is tagged null record;
end Pack2;
with Pack1, Pack2;
package Pack3 is
type T3 is new Pack2.T2 and Pack1.Int1 with null record;
end Pack3;
with Pack3;
procedure Proc4 is
X : Pack3.T3;
begin
Pack3.Op1 (X);
end Proc4;
Using the old wording, we'd run into problems when determining what happens
during the call on the dispatching operation on an object of type T3. 3.9.2(20)
wouldn't apply, since the Op1 operation is not "explicitly declared" for the
type T3; 3.9.2(20.1) doesn't apply since no tasks or protected types are
involved; so we have to apply 3.9.2(20.2), which referred to the "corresponding
operation of the parent type". But the parent type is T2 and there is no
corresponding operation.
The new wording, which talks about "the corresponding operation of the parent
type or progenitor type from which the operation was inherited" handles this
case. But what happens here?
package Pack1 is
type Int1 is interface;
procedure Op1 (X : Int1) is null;
end Pack1;
package Pack2 is
type T2 is tagged null record;
private
procedure Op1 (X : Int1);
end Pack2;
with Pack1, Pack2;
package Pack2.Pack3 is
type T3 is new Pack2.T2 and Pack1.Int1 with null record;
-- procedure Op1 (X : T3) is null; [inherited from Int1]
--private
-- procedure Op1 (X : T3); [inherited from T2]
end Pack2.Pack3;
with Pack2.Pack3;
procedure Proc4 is
X : Pack3.T3;
begin
Pack2.Pack3.Op1 (X);
end Proc4;
Now there are two implicitly declared operations that could be considered as
"the operation" in the new 3.9.2(20.2). At any point, only one or the other
will be visible, but which one depends on whether the private part of
Pack2.Pack3 is visible at that point. Applying 3.9.2(20.2) [as modified by
AI05-126] means we have to determine *which* *one* we're talking about, so that
we can determine "the parent type or progenitor type from which the operation
was inherited". If it's the first one, then the call to Op1 is the same as the
call to the corresponding operation on Int1, which is null; if it's the second
one, then it's the same as the call to the corresponding operation on T2, the
one defined in Pack2.
In this (statically tagged) case, it might make sense to use the one that's
visible at the point of the call. From Proc4, we can "see" the null Op1
inherited from Int1, and we can't see the Op1 inherited from T2, so it *might*
make sense that we can't directly call the latter one---although I thought that
in cases like this the intent was that the one with the body should be called.
(That's what would happen if an overriding Op1 were explicitly declared in the
private part of Pack2.Pack3.) So even in the statically tagged case, I don't
think the wording in AI05-126 leads to the desired result.
But the dynamically tagged case is more confusing:
with Pack1;
procedure Proc5 (X : Int1'Class) is
begin
Pack1.Op1 (X);
end Proc5;
Assume Proc5 is called on an object of type T3. 3.9.2(20-20.2) is still
supposed to apply, using the type identified by the controlling tag, and under
the new wording we need to determine what "the operation" in the phrase "parent
type or progenitor type from which the operation was inherited" means, and I'm
not at all clear on what "the operation" is. The whole section deals with "a
call on a dispatching operation", and I think "the dispatching operation" is the
one declared in Pack1. Clearly it would make no sense to say that this is "the
operation" for applying 3.9.2(20.2).
So what is "the operation"? If we mean the one that's implicitly declared for
the type identified by the controlling tag, i.e. T3---there are two of them,
which one? (And note that it may not work to decree that it's the one that
overrides the other, because I could mess it up with this:)
package Pack1 is
type Int1 is interface;
private
procedure Op1 (X : Int1) is null;
end Pack1;
package Pack2 is
type T2 is tagged null record;
procedure Op1 (X : Int1);
end Pack2;
with Pack1, Pack2;
package Pack1.Pack3 is
type T3 is new Pack2.T2 and Pack1.Int1 with null record;
-- procedure Op1 (X : T3); [inherited from T2]
--private
-- procedure Op1 (X : T3) is null;
-- [inherited from Int1, overrides the one inherited from T2
-- by 8.3(12)]
end Pack1.Pack3;
So I don't think the proposed wording in AI05-126 is adequate.
****************************************************************
Questions? Ask the ACAA Technical Agent