Version 1.8 of ais/ai-00218.txt

Unformatted version of ais/ai-00218.txt version 1.8
Other versions for file ais/ai-00218.txt

!standard 8.3(9-13)          01-09-07 AI95-00218/06
!standard 6.1(1)
!class amendment 99-03-23
!status ARG approved 7-0-1 01-05-18
!status work item 99-03-23
!status received 99-03-23
!priority Medium
!difficulty Medium
!subject Accidental overloading when overriding
!summary
In order to reduce problems with overriding of primitive operations, three pragmas are introduced.
!problem
Subtle bugs result from mistakes in the profile of an overriding subprogram. For example:
with Ada.Finalization; package Root is type Root_Type is new Ada.Finalization.Controlled with null record; procedure Do_Something (Object : in out Root_Type; Data : in Natural); -- (1) procedure Finalize (Object : in out Root_Type);-- (2) end Root;
with Root; package Leaf is type Derived_Type is new Root.Root_Type with null record; procedure Do_Something (Object : in out Root_Type; Data : in Boolean); -- (3) procedure Finalise (Object : in out Root_Type); -- (4) -- Note: Alternative spelling of "Finalize". end Leaf;
with Leaf; procedure Sample is Obj : Leaf.Derived_Type; begin Leaf.Do_Something (Obj, 10); -- Calls (1). -- Finalization of Obj will call (2). end Sample;
Assume the programmer intended (3) and (4) to override (1) and (2), but made subtle declaration errors. Because (1) and (3) are not homographs, the declaration of (3) is legal. However, (3) does not override (1) as intended. Therefore, the call in Sample calls the wrong routine. Similarly, the incorrect declaration of "Finalise" (4) does not override (2). Thus, the finalization of Sample.Obj will call the wrong routine.
The resulting bugs are very difficult to find, especially because the programmer will probably look at this call several times before even thinking that the wrong routine might be called. The bug is even harder to find when the call is dispatching or implicit (as it is for Finalize), because the reason that the wrong routine is being called is not obvious. In the Finalize case, the programmer might even think that the compiler is failing to call Finalize at all. These problems have generated many "bug" reports to compiler writers, including ones from their own development teams.
A more serious problem is that it makes maintenance of object-oriented Ada 95 code much more difficult. Consider a maintenance change to the root class which adds a parameter to Do_Something. All extensions of Root_Type that override Do_Something will now silently overload it instead. The change probably will break all existing code, yet provide no warnings of the problem.
A rarer, but similar problem can occur when a routine accidentally overrides another. In this case, an inappropriate routine may be called via dispatching, and may cause the failure of an abstraction.
All of these cases violate the Ada design principle of least surprise, as described in paragraph 8 of the Introduction to the standard.
!proposal
The problem of accidental overloading rather than overriding can be eliminated if the Ada compiler knows when the programmer wishes to override a routine. Similarly, accidental overriding can be minimized if the Ada compiler knows that the programmer has declared all intentional overriding.
Unfortunately, a complete solution to this problem will be incompatible with existing Ada 95 code. Therefore, we have to introduce an optional solution which applies only when the programmer asks for it.
Thus, we introduce three new pragmas, Explicit_Overriding, Overriding, and Optional_Overriding. Explicit_Overriding is a configuration pragma which requires explicit marking of overriding subprograms. Pragma Overriding is used to mark subprograms that are overriding. Pragma Optional_Overriding marks subprograms which may or may not be overriding. Unmarked subprograms are new declarations, and may not override another routine.
!wording
Add to 8.3 after paragraph 1:
The form of a pragma Explicit_Overriding is as follows:
pragma Explicit_Overriding;
The form of a pragma Overriding is as follows:
pragma Overriding [(designator)];
The form of a pragma Optional_Overriding is as follows:
pragma Optional_Overriding [(designator)];
Add to 8.3 after paragraph 26:
Pragma Explicit_Overriding is a configuration pragma.
Pragmas Overriding and Optional_Overriding shall immediately follow (except for other pragmas) the declaration of a primitive operation. The optional designator of a pragma Overriding or Optional_Overriding shall be the same as the designator of the operation which it follows. Only one of the pragmas Overriding and Optional_Overriding shall be given for a single primitive operation.
A primitive operation to which pragma Overriding applies shall override another operation. In addition to the places where Legality Rules normally apply, this rule also applies in the private part of an instance of a generic unit.
The configuration pragma Explicit_Overriding applies to all declarations within compilation units to which it applies, except that in an instance of a generic unit, Explicit_Overriding applies if and only if it applies to the generic unit. At a place where a pragma Explicit_Overriding applies, an explicit subprogram_declaration to which neither pragma Overriding or Optional_Overriding applies shall not be an overriding declaration. In addition to the places where Legality Rules normally apply, this rule also applies in the private part of an instance of a generic unit.
AARM Note: The overriding required by these rules does not necessarily need to happen immediately at the declaration of a primitive operation. It could occur later because of a later implicit declaration in the declarative region (see 7.3.1).
!example
Here is our original example using the new pragmas.
pragma Explicit_Overriding;
with Root; package Leaf is type Derived_Type is new Root.Root_Type with null record; procedure Do_Something (Object : in out Root_Type; Data : in Boolean); pragma overriding; -- Error: procedure Finalise (Object : in out Root_Type); pragma overriding; -- Error: -- Note: Alternative spelling of "Finalize". end Leaf;
The declarations of Do_Something and Finalise will now cause errors. The problem will immediately be pointed out to the programmer.
!discussion
We need to add two pragmas in order that we can have override checking even when no overriding subprograms are declared. This checking will prevent accidental overriding.
We add the third pragma (Optional_Overriding) so that we can handle generic units where we cannot tell for sure whether overriding occurs. For instance, a mix-in generic does not care whether its operations override existing ones (the user of the generic may care, but that cannot be specified as part of the generic). Therefore, we provide Optional_Overriding as a "don't care" marker, so that projects can still use Explicit_Overriding even if such generics are included. The effect of pragma Optional_Overriding can be thought of as restoring the Ada 95 rules for overriding for a particular declaration.
Requiring the explicit declaration of overriding is used in some other programming languages, most notably Eiffel. Other commonly used languages (including Java and C++) also have the same problem, but do not have the tradition of safety that Ada has.
This additional checking still could allow an error to occur. The problem could still occur if there existed two or more overloaded inherited routines, and the user overrode the wrong one. However, this would be extremely rare, as it is normally the case that all of the inherited routines with a single name are overridden as a group.
We considered adding syntax rather than defining pragma Overriding. Syntax was determined to be too heavyweight a solution for this problem. One idea considered using existing keywords was: subprogram_specification is not new; These ideas were not considered satisfactory; it was felt that proper syntax would need a new reserved word "overriding" or "redefined". That is much too heavy to introduce at this time. A pragma solution also has the advantage that compilers can implement it, and users can use it, even before most compilers support it. If a compiler has not yet implemented the pragmas, it will ignore them, and will accept any legal program (along with some that would be illegal if the pragmas were accepted).
The pragmas Overriding and Optional_Overriding were defined so that they apply only to the immediately preceding subprogram. This is even true if a designator is given, which is different than most other Ada pragmas. This behavior is required for these pragmas, as it is very important that they do not automatically apply to all members of a set of overloaded operations with a single designator. For instance, consider a tagged type with a constructor routine called "Create". An extension of the type might define a new constructor (also named "Create") with additional parameters, as well as overriding the existing constructor (giving appropriate defaults for the new parameters). It is critical that these pragmas handle this case. The solution adopted is that the pragmas only apply to the operations which immediately precede them. This makes it important that these pragmas are not accidentally separated from the subprogram declaration. The optional designator in the pragma provides a means to ensure that this does not happen.
The chosen semantics means that the pragmas can be treated almost as a part of the subprogram declaration. This eases the implementation, as a compiler can determine what kind of declaration it is processing before it starts doing so. (Without this rule, an implementation must postpone error messages until it reaches the next declaration. Since the next declaration could be almost anything, this would provide a distributed overhead.)
Implementing the checking of these pragmas takes some care, because an operation may become overriding at a later point (because of the rules of 7.3.1(6)). A compiler always knows that such overriding will happen later, so this should not provide an implementation burden.
An example of how this could happen:
pragma Explicit_Overriding;
package P is type T is tagged private; private procedure Op (Y : T); end P;
package P.C is type NT is new T; procedure Op (Y : NT); pragma overriding (Op); -- OK. package Nested is type NNT is new T; procedure Op (Y : NNT); -- Illegal because the body of Nested has -- visibility on primitive 'Op' (Overriding or -- Optional_Overriding needs to be given) end Nested; private -- P.C.Op overrides P.Op here (P.Op is inherited here). end P.C;
package body P.C is package body Nested is -- P.C.Nested.Op overrides P.Op here (P.Op is inherited here). end Nested; end P.C;
Another use for Optional_Overriding exists in this example; it can be used here to avoid breaking the abstraction (privateness) of the original type P.T.
The pragmas Overriding and Optional_Overriding are checked in a generic_declaration. We do this because we do not want these pragmas to be part of the "contract" of a generic. To see how this could happen, consider the following example:
generic type GT is tagged private; package Gen is type DGT is new GT with private; procedure Op (Z : DGT); pragma overriding (Op); -- Error: Doesn't override anything. private -- ... end Gen;
If we didn't enforce these pragmas in generic_declarations, we could give Overriding in this generic. The effect would be to force all actual types for GT to have an operation Op. This would effectively modify the contract of the generic to include that operation.
An effect of enforcing the pragmas in a generic_declaration is that pragma Overriding can never be used on operations of a derivation of a generic formal private type (as in the example), as no primitive operations are inherited from the formal type. This is not too serious, as pragma Optional_Overriding can always be used to allow overriding.
Note that the only check required at instantiation is to recheck operations which have neither pragma Overriding nor pragma Optional_Overriding to insure that they do not override some inherited operation. The check in the generic_declaration is sufficient for pragma Overriding, while pragma Optional_Overriding needs no check at all.
The configuration pragma Explicit_Overriding does not necessarily apply to the entire partition. It can be given in front of individual units in order to apply only to those units. This allows subsystems which take advantage of the pragmas to be mixed with those that do not.
In a mixed environment, it is important that units be checked based on the environment for which they were designed. Thus, generic instantiations are checked based on whether pragma Explicit_Overriding applies to the generic unit, and not whether it applies to the instantiation.
!appendix

!from Randy Brukardt 11-24-99

Following are my notes on this AI from the Marlboro ARG meeting (Sept. 1999).
All of these changes have been made to the AI.

----

Should Explicit_Overriding be a Restriction or Feature? (No, after much
discussion)

Why no syntax?  Syntax was ugly. New keyword is bad. New reserved word is bad.
Existing keywords are ugly.

The wording was revised to use "designator" and "primitive operation".

A discussion about whether the optional designator should be included ended
inconclusively.

Add wording so rule is enforced only upon instantiation, and add
"Might_Override" pragma allowing unknown overriding. (Generic mix-ins may or
may not override, depending on the actual).

Make sure the wording covers the following:

package P is
    type T is tagged private;
private
    procedure P (Y : T);
end P;

package P.C is
    type NT is new T;
    procedure P (Y : NT);
    pragma Overrides (P);   -- Could use Might_Overrides here,
                            -- so privateness is not broken.
private
    -- Overrides here P.
end P.C;

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

!from Randy Brukardt 12-07-00

Following are my notes on this AI from the Columbia ARG meeting (Nov. 2000).
All of these changes have been made to the AI.

----

Eiffel: Uses redefines; Java & C++ have the problem.

Change name Overrides to Overriding; Change name Might_Overrides to
Optional_Overriding.

Delete: "This rule is not enforced at the compile time of a
generic_declaration." 6-1-3. Two occurrences.

The intent is that the rules are checked both in the template and in the
instance. (The instance is only necessary for subprograms that do not have an
pragma Overriding or Optional_Overriding.)

Tuck's implementation model: Each subprogram has a flag (Over, Might, Not),
which defaults to Might unless the configuration is set (then it defaults to
Not). Instantiation rechecks. (But it does not recheck whether the configuration
pragma is in use).

Change wording:

The configuration pragma Explicit_Overriding applies to all declarations within
compilation units to which it applies, except that in an instance of a generic
unit, Explicit_Overriding applies if and only if it applies to generic unit. At
a declaration where a pragma Explicit_Overriding applies, an explicit
subprogram_declaration to which neither pragma Overriding or Optional_Overriding
applies shall not be an overridding declaration. In addition to the places where
Legality Rules normally apply, this rule also applies in the private part of an
instance of a generic unit.

Note probably ought to be AARM only.

Change "British spelling" to "alternative spelling". (Two places)

Proposal:

In all of these cases, [the Ada compiler is being actively harmful to the
programmer, ]violat{ed}[ing] the Ada design principle[s] of least surprise. (Introduction, paragraph 8).

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

!from Randy Brukardt 05-25-01

Following are my notes on this AI from the Leuven ARG meeting (May 2001).
All of these changes have been made to the AI.

Only minor editorial changes were needed. The last paragraph of the wording
must be corrected as follows:

Explicit_Overriding applies if and only if it applies to {the} generic unit.
At a [declaration]{place} where a ...

and the following typo must be fixed:

... an overrid[d]ing declaration.

Steve M. is concerned that configuration pragmas are partition-wide. Erhard
replies that they are not defined that way in the RM. Moreover, it is very
useful semantics to localize the effects of the pragma, as library code might
be part of the partition, but not adhere to the use of the Overrides pragma.

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


Questions? Ask the ACAA Technical Agent