Version 1.6 of ai05s/ai05-0197-1.txt
!standard 3.9.2(20.1/2) 11-03-17 AI05-0197-1/02
!standard 3.9.2(20.2/2)
!class binding interpretation 10-02-08
!status ARG Approved 8-0-0 11-03-17
!status work item 10-02-08
!status received 09-06-09
!priority Low
!difficulty Medium
!qualifier Error
!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
Insert a new bulleted item after 3.9.2(20.1/3):
* If the corresponding operation is a predefined operator
then the action comprises an invocation of that operator;
[Editor's note: This handles predefined "=", which is not inherited but
rather constructed anew for each time.]
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
An ACATS C-Test similar to the example in the question should be constructed.
!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.
****************************************************************
From: Steve Baird
Sent: Thursday, March 3, 2011 3:28 PM
> AI05-0197-1: Improve this wording (with help from Tucker). [Sorry, I
> don't have any more detail in my notes than that; they say "Tucker
> does not like this wording which" and little else.]
Neither Tuck nor I could reconstruct the problem that we thought we had identified
at the Tampa meeting, but I did find a couple of new issues while trying.
Again, thanks to Randy and Tuck for their review and suggestions.
Problem #1:
Applying 3.9.2(20-20.3) to a dispatching call to the predefined "=" operator of
a tagged type, it says
Is it explicitly declared? No. Move to next bullet.
Is it implemented via an entry/protected subp? No. Move to next
bullet.
This one begins with "otherwise", so it must be right!
We use the parent's equality op and extension components
do not participate in the result. Not good.
Suggested fix:
!wording (in addition to the existing wording for this AI)
Insert a new bulleted item after 3.9.2(20.1/3):
* If the corresponding operation is a predefined operator
then the action comprises an invocation of that operator;
This should not require any change to any compiler; this is just changing the
wording to reflect what was obviously intended to begin with.
Problem #2:
Making an arbitrary choice among null procedures assumes that they are
interchangeable, and leads to problems if they are not.
Consider the following example:
declare
package Pkg1 is
type Ifc is interface;
procedure Op (X : in out Ifc) is null;
end Pkg1;
package Pkg2 is
type T is tagged null record
with Type_Invariant => Is_Valid (T);
procedure Op (X : in out T) is null;
function Is_Valid (X : T) return Boolean;
end Pkg2;
package body Pkg2 is ... end Pkg2;
package Pkg3 is
type D is new Pkg1.T and Pkg2.Ifc with null record;
end Pkg3;
begin ...; end;
Does a dispatching call to Pkg3.Op where the tag of the controlling operand is
Pkg3.D'Tag result in a call to Is_Valid?
It seems like it depends on the "arbitrary" choice mentioned in this AI's new
wording for 3.9.2(20.2/2), which defines the dynamic semantics of such a
dispatching call:
* 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.}
If we flip a coin to decide which from among two candidates is the
"corresponding operation ... from which the operation was inherited", and if
exactly one of these two candidates includes a call to Is_Valid in its dynamic
semantics, then it seems like we have a problem.
Both here and when 8.3(12.3/2) says "one is chosen arbitrarily", we are relying
on the premise that syntactically null procedures with appropriately conformant
profiles are interchangeable with respect to dynamic semantics.
One approach to this problem (which Randy can explain his views on in a separate
message) would involve two steps:
1) In these two arbitrary-choice situations (3.9.2 and 8.3),
we add a preference rule preferring operations inherited
from a non-interface type over operations inherited from
an interface type.
2) We take whatever steps are needed (possibly none?)
in order to ensure that null procedures which are primitive ops
of interface types really are interchangeable (e.g., we
already disallow pre and post conditions for null procedures).
This issue needs more work.
[Editor's note: Discussion of Problem #2 continues in AI05-0247-1.]
****************************************************************
Questions? Ask the ACAA Technical Agent