Version 1.14 of ais/ai-00218.txt
!standard 8.3(26) 01-09-11 AI95-00218/06
!class amendment 99-03-23
!status work item 02-10-15
!status WG9 approved 01-10-05
!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); --
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 Derived_Type;
Data : in Boolean); --
procedure Finalise (Object : in out Derived_Type); --
--
end Leaf;
with Leaf;
procedure Sample is
Obj : Leaf.Derived_Type;
begin
Leaf.Do_Something (Obj, 10); --
--
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 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 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 to
which pragma Explicit_Overriding applies are new declarations and must not
override another routine.
!wording
Add to 8.3 after paragraph 26:
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)];
Pragma Explicit_Overriding is a configuration pragma.
Pragmas Overriding and Optional_Overriding shall immediately follow (except
for other pragmas) the explicit 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 nor
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; --
procedure Finalise (Object : in out Root_Type); pragma overriding; --
--
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 mean 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 processing
it. (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); --
package Nested is
type NNT is new T;
procedure Op (Y : NNT); --
--
--
end Nested;
private
--
end P.C;
package body P.C is
package body Nested is
--
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. That is, we do not want these pragmas
to 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);
pragma overriding (Op); --
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.
!corrigendum 8.3(26)
Insert after the paragraph:
A non-overridable declaration is illegal if there is a homograph occurring
immediately within the same declarative region that is visible at the place
of the declaration, and is not hidden from all visibility by the non-overridable
declaration. In addition, a type extension is illegal if somewhere within
its immediate scope it has two visible components with the same name. Similarly,
the context_clause for a subunit is illegal if it
mentions (in a with_clause) some library unit, and there is a homograph of
the library unit that is visible at the place of the corresponding stub, and
the homograph and the mentioned library unit are both declared immediately
within the same declarative region. These rules also apply to dispatching
operations declared in the visible part of an instance of a generic unit.
However, they do not apply to other overloadable declarations in an instance;
such declarations may have type conformant profiles in the instance, so long
as the corresponding declarations in the generic were not type conformant.
the new paragraphs:
Syntax
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)];
Pragma Explicit_Overriding is a configuration pragma.
Legality Rules
Pragmas Overriding and Optional_Overriding shall immediately follow (except
for other pragmas) the explicit 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 nor
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.
!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.
*************************************************************
!from Randy Brukardt 03-01-23
A problem was noted with this AI during the Bedford ARG meeting. If you have
a private type, you may not know whether an operation is overridding. Moreover,
this information would be a (minor) violation a privacy. Consider:
pragma Explicit_Overridding;
package P is
type T is private;
function "+" (Left, Right: T) return T; -- Not overriding here.
private
type T is range 0 .. 100; -- "+" overrides here.
end P;
You also can have a similar case for tagged types:
package P is
type T is new T1;
procedure Foo; -- Not overridding here.
private
type T is new T2;
procedure Foo; -- Does override here.
end P;
I've looked at three solutions for this:
The first solution is to add full profiles to the Overrides pragma, so we can
separate them from the declarations. Then, we also have to change the location
where the check occurs (probably to the freezing point of the type). In this
example, that would look like:
pragma Explicit_Overridding;
package P is
type T is private;
function "+" (Left, Right: T) return T;
private
type T is range 0 .. 100; -- "+" overrides
pragma Overrides ("+" (Left, Right: T) return T);
end P;
Note that this is completely new syntax for pragmas; existing compilers could
not handle it. And its ugly.
Another alternative is to allow redeclarations of operations simply to provide
a place to hang these pragmas:
pragma Explicit_Overridding;
package P is
type T is private;
function "+" (Left, Right: T) return T;
pragma Optional_Overriding("+"); -- So there is no check here.
private
type T is range 0 .. 100; -- "+" overrides
function "+" (Left, Right: T) return T; -- A redeclaration.
pragma Overrides ("+");
end P;
Having all of these "Optional_Overriding" pragmas is annoying. And, again,
redeclarations is a new feature that existing compilers could not swallow.
The third choice is to move the check to the freezing point and use
redeclarations. In this case, the original declaration's overriding state is
overridden later:
pragma Explicit_Overridding;
package P is
type T is private;
function "+" (Left, Right: T) return T;
private
type T is range 0 .. 100; -- "+" overrides
function "+" (Left, Right: T) return T; -- A redeclaration.
pragma Overrides ("+");
-- The check that "+" is overridding is made here.
end P;
Again, we have the new concept of redeclaration.
The main advantage of the pragmas only proposal was that no new syntax or
semantics (other than the check itself) is needed. That no longer appears to
be the case, so I have reluctantly abandoned this alternative of the AI.
*************************************************************
Questions? Ask the ACAA Technical Agent