Version 1.6 of ai05s/ai05-0125-1.txt

Unformatted version of ai05s/ai05-0125-1.txt version 1.6
Other versions for file ai05s/ai05-0125-1.txt

!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;
with 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 convenient 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.

****************************************************************

From: Bob Duff
Sent: Friday, June 18, 2010  6:23 PM

It seems to me that AI05-0125-1 -- "Nonoverridable operations of an ancestor"
-- should be a binding interpretation if it is accepted at all.

To have a subtle difference like this between Ada 95/2005 and Ada 2012 is a
very bad idea.

If there's any support for this AI, we should probably first investigate whether
there are incompatibilities in practice.

****************************************************************

From: Jean-Pierre Rosen
Sent: Monday, August 23, 2010  6:52 AM

The specification of A1.A3 misses a "with A1.A2;"

****************************************************************

Questions? Ask the ACAA Technical Agent