Version 1.1 of ais/ai-10218.txt

Unformatted version of ais/ai-10218.txt version 1.1
Other versions for file ais/ai-10218.txt

!standard 8.3(26)          02-09-30 AI95-00218-02/01
!class amendment 00-09-30
!status work item 02-09-30
!status received 02-09-30
!priority Medium
!difficulty Medium
!subject Accidental overloading when overriding
In order to reduce problems with overriding of primitive operations, additional syntax for subprogram declarations is introduced. A pragma controls the use of this syntax.
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 Derived_Type; Data : in Boolean); -- (3) procedure Finalise (Object : in out Derived_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.
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 would 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 a new pragma, Explicit_Overriding, and additional syntax with new nonreserved keywords which indicate that overiding will or might occur. Explicit_Overriding is a configuration pragma which requires use of the alternative syntax.
Declarations that are overriding are concluded with "overrides;" whereas those that might or might not be overriding (this situation can occur with generics) are concluded with "maybe overrides;" Subprogram declarations without this new syntax and to which pragma Explicit_Overriding applies are new declarations and must not override another routine.
In 2.9(3) as modified by AI 284 add the following to the list of nonreserved keywords
maybe overrides
Change the syntax in 6.1 to read
overriding_indicator ::= [maybe] overrides subprogram_declaration ::= subprogram specification [overriding_indicator]; abstract_subprogram_declaration ::= subprogram specification [overriding_indicator] is abstract;
Add to 8.3 after paragraph 26:
The form of a pragma Explicit_Overriding is as follows:
pragma Explicit_Overriding;
Pragma Explicit_Overriding is a configuration pragma.
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, abstract_subprogram_declaration or generic_instantiation of a subprogram without an overriding_indicator shall not be an overriding declaration whereas such a subprogram declaration with the overriding_indicator "overrides" shall 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.
Change the syntax in 12.3 to read
generic_instantiation ::= package defining_program_unit_name is new generic_package_name [generic_actual_part]; | procedure defining_program_unit_name is new generic_procedure_name [generic_actual_part] [overriding_indicator]; | function defining_designator is new generic_function_name [generic_actual_part] [overriding_indicator];
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).
Here is our original example using the new pragma and syntax.
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) overrides; -- Error: procedure Finalise (Object : in out Root_Type) overrides; -- 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.
The configuration pragma Explicit_Overriding is necessary for backward compatibility. If the pragma is applied then the form "overrides" on subprogram declarations provides override checking even when no overriding subprograms are declared. This checking prevents accidental overriding.
The form "maybe overrides" is required 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 "maybe overrides" as a "don't care" marker, so that projects can still use the pragma Explicit_Overriding even if such generics are included. The effect of "maybe overrides" can be thought of as restoring the Ada 95 rules for overriding for a particular declaration.
Note that the forms "overrides" and "maybe overrides" are part of a subprogram declaration and not the subprogram specification. This is because we do not want them to be permitted in all syntactic situations which use subprogram specification such as renaming, generic subprogram declarations, formal subprogram parameters and subprogram body stubs. However, we do want them to apply to abstract subprograms and to generic instantiations. For example
procedure Op(X: T) overrides is abstract;
One consequence is that we cannot use these forms with a subprogram body given alone but must supply a distinct subprogram declaration if we need to use them.
Requiring the explicit declaration of overriding is used in some other programming languages, most notably Eiffel (which uses the keyword redefine). 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.
Implementing the checking of this feature 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) overrides; -- OK. package Nested is type NNT is new T; procedure Op (Y : NNT); -- Illegal because the body of Nested has -- visibility on primitive 'Op' -- We must add "overrides" -- or "maybe overrides". 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;
Note the possibility of using "maybe overrides" in this example as indicated in the comment; it can be used here to avoid breaking the abstraction (privateness) of the original type P.T.
The pragma Explicit_Overriding and the "overrides" syntax are checked in a generic_declaration (as well as in instantiations). We do this because we do not want the feature to be part of the "contract" of a generic and thereby add additional restrictions on the actual parameters of the 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) overrides; -- Error: Doesn't override anything. private -- ... end Gen;
If we didn't enforce the rule in generic_declarations (but only in instantiations), we could give "overrides" in this generic as shown. 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.
As a consequence "overrides" 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 "maybe overrides" can always be used to allow overriding.
Note that the only check required at instantiation is to recheck operations which have neither "overrides" nor "maybe overrides" to ensure that they do not override some inherited operation. The check in the generic_declaration is sufficient for "overrides", while "maybe overrides" 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 new syntax 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.
We considered using further pragmas rather than syntax since syntax was originally considered to be too heavyweight a solution for this problem. However, the introduction of unreserved keywords makes the use of syntax much more attractive than if the words had to be reserved.
An advantage over pragmas is that the syntax is clearly part of the declaration whereas pragmas are not. In the alternative approach the pragmas had to immediately follow the declaration (with optional inclusion of the subprogram identifier) and required special rules for the pragmas to ensure that they only applied to the immediately preceding declaration. The normal rules would have been that the pragma applied to all overloaded declarations with the same identifier which would clearly be inappropriate. There are also dangers if the pragmas get separated from the declaration during program amendment if the identifier is not included and including it seems a burden.
We also considered "is overriding" rather than "overrides" but this led to the awkward
procedure Op(X: T) is overriding is abstract;
The double use of is was considered ugly.
The keyword "overrides" seems natural but "maybe overrides" is perhaps unusual and introduces the keyword "maybe". Other possibilities are "perhaps overrides", or even "perchance overrides".
!corrigendum 8.3(26)
To be done.

At the Cupertino and Vienna meeting, John Barnes indicated that he would
prefer this AI to use keywords. It was suggested that he prepare an alternative
AI using keywords.


Questions? Ask the ACAA Technical Agent