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

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

!standard 7.3.1(6)          11-02-09 AI05-0125-1/03
!standard 8.3(12.3/2)
!standard 8.3.1(5/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.
This problem had quite an effect on the design of the graphic subsystem Claw; the designers would have used a flatter package structure than was ultimately used. Some of the 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.
!proposal
Add wording to ensure that an explicit declaration will override an inherited operation that is "known" to exist at the point of the explicit declaration. Require an overriding_indicator of OVERRIDING to ensure the overriding is intentional.
!wording
Modify 8.3(10.d):
As explained in 7.3.1, "Private Operations", some inherited primitive subprograms are never declared. Such subprograms cannot{, in general,} be overridden, although they can be reached by dispatching calls in the case of a tagged type.
Add after 8.3(12.3/2)
When a type extension inherits a subprogram that is never declared (see 7.3.1) and is not a homograph of an inherited subprogram that is declared, an explicit declaration of a dispatching operation with the same defining name and a type-conformant profile will nevertheless override the inherited subprogram (for the purposes of dispatching calls), so long as a corresponding dispatching operation of some ancestor type is visible at the point of the explicit declaration. Such an overriding requires an overriding_indicator, which shall be OVERRIDING (see 8.3.1).
Similarly, if a protected or task type inherits a subprogram that is never declared (and is not a homograph of an inherited subprogram that is declared), whose first parameter is of the synchronized type or is an access parameter designating the type, then if the type has a single entry or protected subprogram with the same defining name and a profile that is type-conformant with the prefixed view of the inherited subprogram, this entry or protected subprogram /implements/ the inherited subprogram (for the purposes of dispatching calls), so long as a corresponding dispatching operation of some progenitor is visible at the point of the entry or protected subprogram declaration. The declaration of such an entry or protected subprogram requires an overriding_indicator, which shall be OVERRIDING (see 8.3.1).
AARM NOTE: Such an inherited subprogram is necessarily a null procedure which was declared in the private part of the package where the associated interface was declared, since private dispatching operations may not be abstract, and null procedures are the only operations of an interface that are not abstract (other than the equality operators!).
Also note that a single overriding might override two null procedures that are private operations of two separate interfaces. This is fine, because as usual, if two interfaces have operations that are homographs, then it is presumed that these represent the "same" operation in some sense.
Modify 8.3.1(5/2) as follows:
* if the overriding_indicator is OVERRIDING, then the operation shall override a homograph {or an undeclared inherited subprogram} at the place of the declaration;
[Editor's note: 7.3.1(6/1) also requires changes, but AI05-0029-1 already made the needed changes.]
!discussion
In the case where a parent type of some derived type is "known" to have a dispatching operation through inheritance, but because of the visibility rules the operation is not declared for the derived type, we nevertheless want overriding to occur, for the purposes of dispatching. This only happens if the explicit declaration happens at a place where the corresponding operation of some ancestor type is visible. This is unlike the normal rule, where overriding happens even if the inherited operation is declared after the explicitly declared operation. To ensure that the overriding is intentional, we require that an overriding indicator of OVERRIDING be given in this special circumstance.
We considered allowing the user to specify NOT OVERRIDING at the point of the explicit declaration, but it was felt to be an abuse of the overriding indicator to have its presence or absence affect the semantics of the declaration. So instead we require a "confirming" OVERRIDING indication, and make it illegal to write NOT OVERRIDING in this case.
To minimize the incompatibility, if the explicit declaration happens at a place where the corresponding operation of the ancestor is not visible, no overriding occurs, and if an overriding indicator were given, it would have to indicate NOT OVERRIDING.
Note that if there is a visible inherited subprogram which is a homograph with the one that is never declared, then the visible inherited subprogram is the one being overridden, not the one that is never declared. This ensures compatibility with existing programs that override visible operations which happen to be homographs with private operations.
!ASIS
The overriding relationship is not currently represented in ASIS. If it were, it would have to properly reflect this new kind of overriding.
!example
!ACATS test
ACATS B and C tests are needed for this feature.
!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;"

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

From: Tucker Taft
Sent: Friday, October 29, 2010  11:40 PM

[Version /02 of the AI was attached - Editor.]

Here is an AI attempting to address another thorny "Adam" question.  Basically,
we say that an explicit declaration of a dispatching operation on a type
extension overrides an inherited subprogram that is never declared, if the
explicit declaration occurs at a point where a corresponding dispatching
operation of some ancestor is visible.

In other words, if you "know" that you inherited a dispatching operation, you
can override it for the purposes of dispatching calls, even if the inherited
subprogram isn't "officially" declared.

We require an overriding_indicator of OVERRIDING in this special case, so there
is no ambiguity, and to prevent a "silent" inconsistency with Ada 95/2005.

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

From: Tucker Taft
Sent: Wednesday, February  9, 2011  11:13 PM

[Version /03 of the AI was attached - Editor.]

Here is a modest update to AI-125.  It now worries about entries and protected
subprograms overriding inherited subprograms which are not declared (which are
necessarily null procedures).  It also worries about the case where there is an
inherited subprogram that *is* declared which is a homograph of the one that is
*not* declared. Finally it makes mention of the possibility that a single
overriding might override two inherited subprograms, neither of which are
declared.  We consider that as OK, since we already allow a single declaration
to override two inherited subprograms from different interfaces.

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

From: Randy Brukardt
Sent: Wednesday, February  9, 2011  11:38 PM

> Here is a modest update to AI-125.

I must be dreaming. I actually thought I received some homework from Tucker. :-)

> It now worries about
> entries and protected subprograms overriding inherited subprograms
> which are not declared (which are necessarily null procedures).  It
> also worries about the case where there is an inherited subprogram
> that *is* declared which is a homograph of the one that is *not*
> declared.
> Finally it makes mention of the possibility that a single overriding
> might override two inherited subprograms, neither of which are
> declared.  We consider that as OK, since we already allow a single
> declaration to override two inherited subprograms from different
> interfaces.

I found the new AARM note midly confusing, since it seems to apply to both of
the new paragraphs, but it is really only talking about the second one. I was
trying to figure how it worked in the "normal" case and it didn't make much
sense.

Perhaps that won't be a problem in the actual AARM.

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

Questions? Ask the ACAA Technical Agent