Version 1.4 of ais/ai-00218.txt

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

!standard 8.3(9-13)          99-11-24 AI95-00218/03
!standard 6.1(1)
!class amendment 99-03-23
!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); 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: British 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 in 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.
In all of these cases, the Ada compiler is being actively harmful to the programmer, violating the Ada design principles of least surprise.
!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, Overrides, and Might_Override. Explicit_Overriding is a configuration pragma which requires explicit marking of overriding subprograms. Pragma Overrides is used to mark subprograms that are overriding. Pragma Might_Override 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 Overrides is as follows:
pragma Overrides [(designator)];
The form of a pragma Might_Override is as follows:
pragma Might_Override [(designator)];
Add to 8.3 after paragraph 26:
Pragma Explicit_Overriding is a configuration pragma.
Pragmas Overrides and Might_Override shall immediately follow (except for other pragmas) the declaration for a primitive operation. The optional designator of a pragma Overrides or Might_Override shall be the same as the designator of the operation which it follows. Only one of pragmas Overrides and Might_Override shall be given for a single primitive operation.
A primitive operation to which pragma Overrides applies shall override another operation. This rule is not enforced at the compile time of a generic_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.
For a primitive subprogram inherited in a unit to which a pragma Explicit_Overriding applies, an overriding explicit declaration shall be a subprogram to which pragma Overrides or Might_Override applies. This rule is not enforced at the compile time of a generic_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: 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 Overrides; -- Error: procedure Finalise (Object : in out Root_Type); pragma Overrides; -- Error: -- Note: British 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 (Might_Override) 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 Might_Override as a "don't care" marker, so that projects can still use Explicit_Overriding even if such generics are included.
Requiring the explicit declaration of overriding is used in some other programming languages, most notably Eiffel. They also have noticed this problem.
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 Overrides. Syntax was determined to be too heavyweight a solution for an amendment. 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 Overrides and Might_Override 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 operation 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.
This 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:
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 Overrides (Op); -- OK. package Nested is type NNT is new T; procedure Op (Y : NNT); -- Illegal (Overrides or -- Might_Override 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 Might_Override exists in this example; it can be used here is avoid breaking the abstraction (privateness) of the original type P.
The pragmas Overrides and Might_Override are not checked in a generic_declaration. We do this because the an operation may not override in the generic_definition, but be intended to override in an instance. We do not want to force the use of Might_Override in all generics; the checking can be useful.
For instance:
generic type GT is tagged private; package Gen is type DGT is new GT with private; procedure Op (Z : DGT); pragma Overrides (Op); -- OK. private -- ... end Gen;
If we enforced these pragmas in generic_declarations, we could not give Overrides in this generic, as Op does not override anything in the generic_declaration or formal part. However, this pragma can be useful, as it forces all actual types for GT to have an operation Op. For example, using the types from the preceding example:
package Inst1 is new Gen (P.C.NT); -- OK, P.C.Op is overridden by Gen.Op. package Inst2 is new Gen (P.T); -- Illegal, P.Op is not visible in Inst2, -- and does not override.
!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;

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

Questions? Ask the ACAA Technical Agent