!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 !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; -- (1) end Pack1; package Pack2 is type T2 is tagged null record; private procedure Op1 (X : T2); -- (2) 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 (1)] --private -- procedure Op1 (X : T3); [inherited (2)] 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; -- (1) end Pack1; package Pack2 is type T2 is tagged null record; procedure Op1 (Y : Int1); -- (2) end Pack2; with Pack1, Pack2; package Pack1.Pack3 is type T3 is new Pack2.T2 and Pack1.Int1 with null record; -- procedure Op1 (Y : T3); [inherited (2)] -- (3) --private -- procedure Op1 (X : T3) is null; -- [inherited (1), overrides (3) by 8.3(12)] -- (4) end Pack1.Pack3; package body Pack1.Pack3 is Obj : T3; begin Op1 (X => Obj); -- (5) 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. ****************************************************************