!standard 7.3.1(6) 08-10-20 AI05-0125-1/01 !standard 8.3(23) !standard 8.3.1(5/2) !standard 8.3.1(6/2) !class Amendment 08-10-20 !status work item 08-10-20 !status received 08-10-03 !priority Low !difficulty Easy !subject Nonoverridable operations of an ancestor !summary (See proposal.) !problem Consider: package A1 is type Base is abstract tagged private; procedure P1 (X : Base); private type Base is tagged null record; procedure P2 (X : Base); end A1; package A1.A2 is type Child is new Base with private; procedure P1 (X : Child); private type Child is new Base with null record; procedure P2 (X : Child); end A1.A2; package A1.A3 is type Grandchild is new A1.A2.Child with private; procedure P1 (X : Grandchild); private type Grandchild is new A1.A2.Child with null record; procedure P2 (X : Grandchild); -- not overriding!! end A1.A3; The problem is that at the point where the full declaration of Grandchild occurs, the procedure P2 that operates on A1.A2.Child is not visible, and therefore the language does not treat the last declaration of P2 as overriding. Note that, unlike other visibility issues, there is no simple workaround. For accessing components or calling subprograms of the grandparent type, a type conversion can be used. But an inability to override a routine is fatal; the only solution is to move the type somewhere where the operations are inherited. For instance, to make a dispatching call to the original P2 with an object X of type Grandchild, a programmer could write: A1.P2 (Base'Class (X)); But there is no way to write a body of that dispatching operation specifically for type Grandchild. Since private dispatching operations are a convinient way to hide private information, this flaw forces O-O type trees to be placed in deep child package nesting. !proposal Add wording to ensure that an operation will override even if it is not declared. !wording Add after 8.3(23): A declaration that is a primitive operation of a type extension or private extension can also override an inherited subprogram that is not declared, if the inherited subprogram has the same defining name and has a type-conformant profile, the original corresponding operation of the inherited subprogram is an operation of a visible ancestor of the type extension or private extension declaration, and the original corresponding operation is visible at the point of the declaration. [Note: I have avoided using the term "homograph" since a homograph is a declaration and I don't want to change the definition to possibly refer to something that is not declared.] This depends on defining two terms, "original corresponding operation" and "visible ancestor". I would define them as follows: * * * * * * The "original corresponding operation" for a subprogram declaration S is defined as follows: If S is an inherited subprogram S2, or overrides an inherited subprogram S2, the original corresponding operation of S is the original corresponding operation of S2. Otherwise, the original corresponding operation of S is S itself. [The idea is that this is the declaration that caused the slot in the dispatch table to be allocated.] * * * * * * The "visible ancestors" of a type extension or private extension declaration D are: The parent and progenitor types of the declaration are visible ancestors of D; If T is a visible ancestor of a declaration, and T is a type extension, private extension, or interface type, then: -- if T has a partial view, and the full view of T is not visible at the point of D, then the parent and progenitor types of the private extension declaration of T are visible ancestors of D; -- otherwise, the parent and progenitor types of the type extension or interface type are visible ancestors of D. * * * * * * [These definitions may seem clumsy to some, because my personal preference is for mathematically precise definitions. I tried to define "original corresponding operation" in a way that would avoid the possible problem of a non-overriding homograph, such as P3 in the second example above.] Basically, what this all means is that a declaration can override an inherited operation P if the program can tell, looking at just the information visible to it at that point, that an operation P must exist, even if (due to an intermediate type declaration) the operation isn't actually declared. Other changes that would need to be made: In the last sentence of 7.3.1(6/1), the phrase "and cannot be overridden" would have to be deleted or changed. 8.3.1(5/2-6/2) would need to be changed: * if the overriding_indicator is OVERRIDING, then the operation shall override a homograph at the place of the declaration or body, or it shall override an undeclared inherited subprogram with the same defining name and type-conformant profile, * if the overriding_indicator is NOT OVERRIDING, then the operation shall not override any homograph or inherited subprogram (at any place). AARM 8.3(10.d) would need to be changed. !discussion [Editor's note: This problem had quite an effect on the design of Claw; we would have used a flatter package structure than we ultimately ended up with. Some of private routines in Claw define raw message handlers, which should not be exposed to the user of Claw and surely should not be overridden by them.] !example !ACATS test !appendix !topic Proposed fix to problem of nonoverridable operations !reference RM05 8.3, 8.3.1, 7.3.1(6) !from Adam Beneschan 08-10-03 !discussion This is in regard to an example recently posted on comp.lang.ada by Maxim Reznik. I tend to agree with Dmitry Kazakov that the language is broken in this regard, and I would like to discuss this and propose a possible language change. The example looked essentially like this: package A1 is type Base is abstract tagged private; procedure P1 (X : Base); private type Base is tagged null record; procedure P2 (X : Base); end A1; package A1.A2 is type Child is new Base with private; procedure P1 (X : Child); private type Child is new Base with null record; procedure P2 (X : Child); end A1.A2; package A1.A3 is type Grandchild is new A1.A2.Child with private; procedure P1 (X : Grandchild); private type Grandchild is new A1.A2.Child with null record; procedure P2 (X : Grandchild); -- not overriding!! end A1.A3; The problem is that at the point where the full declaration of Grandchild occurs, the procedure P2 that operates on A1.A2.Child is not visible, and therefore the language does not treat the last declaration of P2 as overriding. The Language Design Principle in 3.9(1.k/2) makes it clear that for a type extension, if there is a primitive subprogram of the parent type that is not visible anywhere where the type extension is declared, then any homograph is considered a separate, non-overriding subprogram that is given its own slot in the "type descriptor" (dispatch table, whatever). Thus: package B1 is type Root is tagged private; private type Root is tagged null record; procedure P3 (X : Root); end B1; package B2 is type Child is new B1.Root with ... ; procedure P3 (X : Child); end B2; At the point where Child is declared, B2 "doesn't know" that Child will have an inherited operation named P3 (although the operation does exist and can be called via dispatching), because that information is available only in the private part of B1, which B2 can't see. So it makes sense that P3 would be a separate non-overriding declaration here. But that doesn't apply in Maxim's example. At the point in A1.A3 where Grandchild is declared, A1.A3 "knows", from information that is visible to it, that Grandchild is descended from Child, that Child is descended from Base, and that a primitive subprogram P2 is declared for Base and will therefore exist for all types in Base'Class. But it cannot override it, because the declaration of the operation P2 on the *parent* is hidden from it. This is a flaw in the language. My proposed solution would be to add a paragraph to 8.3, probably between 8.3(23) and 8.3(23.1/2). I wouldn't add it to the list of bullet points starting with 8.3(9), because that discusses what happens when a declaration overrides a homograph, which is another declaration; and this paragraph would discuss when a declaration overrides an inherited subprogram that is not declared (see 7.3.1(6)), so it would be inappropriate to add it to that list. [Normally, it wouldn't seem to make sense to talk about a declaration overriding something that isn't declared, since part of the purpose of the overriding rules is to determine what is meant by an identifier when there are two competing declarations that it could mean. But the semantics of calls on dispatching operations depend on what inherited subprograms get overridden, whether those subprograms are declared or not. Furthermore, I would definitely want the "overriding" keyword to be legal on such overriding subprograms, and "not overriding" to be illegal.] Add after 8.3(23): A declaration that is a primitive operation of a type extension or private extension can also override an inherited subprogram that is not declared, if the inherited subprogram has the same defining name and has a type-conformant profile, the original corresponding operation of the inherited subprogram is an operation of a visible ancestor of the type extension or private extension declaration, and the original corresponding operation is visible at the point of the declaration. [Note: I have avoided using the term "homograph" since a homograph is a declaration and I don't want to change the definition to possibly refer to something that is not declared.] This depends on defining two terms, "original corresponding operation" and "visible ancestor". I would define them as follows: * * * * * * The "original corresponding operation" for a subprogram declaration S is defined as follows: If S is an inherited subprogram S2, or overrides an inherited subprogram S2, the original corresponding operation of S is the original corresponding operation of S2. Otherwise, the original corresponding operation of S is S itself. [The idea is that this is the declaration that caused the slot in the dispatch table to be allocated.] * * * * * * The "visible ancestors" of a type extension or private extension declaration D are: The parent and progenitor types of the declaration are visible ancestors of D; If T is a visible ancestor of a declaration, and T is a type extension, private extension, or interface type, then: -- if T has a partial view, and the full view of T is not visible at the point of D, then the parent and progenitor types of the private extension declaration of T are visible ancestors of D; -- otherwise, the parent and progenitor types of the type extension or interface type are visible ancestors of D. * * * * * * [These definitions may seem clumsy to some, because my personal preference is for mathematically precise definitions. I tried to define "original corresponding operation" in a way that would avoid the possible problem of a non-overriding homograph, such as P3 in the second example above.] Basically, what this all means is that a declaration can override an inherited operation P if the program can tell, looking at just the information visible to it at that point, that an operation P must exist, even if (due to an intermediate type declaration) the operation isn't actually declared. Other changes that would need to be made: In the last sentence of 7.3.1(6/1), the phrase "and cannot be overridden" would have to be deleted or changed. 8.3.1(5/2-6/2) would need to be changed: * if the overriding_indicator is OVERRIDING, then the operation shall override a homograph at the place of the declaration or body, or it shall override an undeclared inherited subprogram with the same defining name and type-conformant profile, * if the overriding_indicator is NOT OVERRIDING, then the operation shall not override any homograph or inherited subprogram (at any place). AARM 8.3(10.d) would need to be changed. There may be other necessary changes. I don't know. **************************************************************** From: Robert A. Duff Sent: Friday, October 3, 2008 3:06 PM I agree this is a flaw in the language. I am quite surprised that it works this way, but I checked the RM, and I think you're right. **************************************************************** From: Randy Brukardt Sent: Monday, October 13, 2008 10:51 PM I'm surprised that you feel that way. It was well-known (at least by us!), as we repeatedly ran into it during the Claw development. We eventually settled on the meta-rule to never derive from a sibling type, only from child types (that is, types declared in child packages of their parent type/package rather than in some other place). This wasn't necessary just for overriding but also for components of types. Note that in Claw, all of the types are declared in children of package Claw (which contains the root types), so this case comes up for *every* type. We placed various operations in the private part of Claw in order that only the implementation could use and extend them. However, I believe I'm against trying to "fix* this issue. The reason is that the inheritance/overriding rules are already impossible to understand. To make them subtly different would only increase the confusion. After all, you can't directly call one of these grandparent operations directly for the type, and you can't directly reference a grandparent component for the type (even though you can do that with an appropriate type conversion). To then say that overriding happens anyway seems dangerous, to say the least. Moreover, the "implemented by" rules would also need an analogous change (they surely ought to work similarly to overriding), which would increase the complexity several times. Possibly, we'd also need changes to the generic inheritance rules - the imfamous 12.5.1(21/2). (I also had worried about the abstract "require overriding" rules, but those aren't a problem here because private abstract operations [and constructor functions] are illegal.) Sounds like a mess to me. ****************************************************************