Rationale for Ada 2012
4.3 Incomplete types
Incomplete types in
Ada 83 were very incomplete.
They were mostly used
for the traditional linked list such as
type Cell; -- incomplete
type Cell_Ptr is access Cell;
type Cell is -- the completion
The incomplete type could only be used in the declaration
of an access type. Moreover, the incomplete declaration and its completion
had to be in the same list of declarations. However, if the incomplete
declaration is in a private part then the completion can be deferred
to the body; this is the so-called Taft Amendment added to Ada 83 at
the last minute.
Ada 95 introduced tagged types and generalized access
types and so made the language much more flexible but made no changes
to incomplete types as such. However, it soon became clear that the restrictive
nature of incomplete types was a burden regarding mutually dependent
types and was a key issue in the requirements for Ada 2005.
The big step forward in Ada 2005 was the introduction
of the limited with clause. This enables a package to have an incomplete
view of a type in another package and solves many problems of mutually
However, the overall rule remained that an incomplete
type could only be completed by a full type declaration and, moreover,
a parameter could not (generally) be of an incomplete type. This latter
restriction encouraged the use of access parameters.
As mentioned in the
Introduction, the first rule prevented the following
type T2 (X: access T1) is private;
type T1 (X: access T2) is private; -- illegal in Ada 2005
since the completion of T1
could not be by a private type.
This is changed in Ada 2012 so that an incomplete
type can be completed by any type (other than another incomplete type).
Note especially that an incomplete type can be completed by a private
extension as well as by a private type.
The other major problem in Ada 2005 was that with
mutually dependent types in different packages we could not use incomplete
types as parameters because it was not known whether they were by-copy
or by-reference. Of course, if they were tagged then we did know they
were by reference but that was a severe restriction.
The need to know whether parameters are by reference
or by copy was really a red herring. The model used for parameter passing
in versions of Ada up to and including Ada 2005 was basically that at
the point of the declaration of a subprogram we need to have all the
information required to call the subprogram. Thus we needed to know how
to pass parameters and so whether they were by reference or by copy.
But this is quite unnecessary; we don't need to know
the mechanisms involved until a point where the subprogram is actually
called or the body itself is encountered since it is only at those points
that the parameter mechanism is really required. It is only at those
points that the compiler has to grind out the code for the call or for
So the rules in Ada 2012 are changed to use this
"when we need to know" model. This is discussed in AI-19
which is actually a binding interpretation and thus retrospectively applies
to Ada 2005 as well. This is formally expressed by the difference between
freezing a subprogram and freezing its profile. This was motivated by
a problem with tagged types whose details need not concern us.
As a highly benevolent consequence, we are allowed
to use incomplete types as both parameters and function results provided
that they are fully defined at the point of call and at the point where
the body is defined.
But another consequence of this approach is that
we cannot defer the completion of an incomplete type declared in a private
part to the corresponding body. In other words, parameters of an incomplete
type are allowed provided the Taft Amendment is not used for completing
The other exciting
change regarding incomplete types is that in Ada 2012 they are allowed
as generic parameters. In Ada 2005 the syntax is
type defining_identifier [discriminant_part] is formal_type_definition;
whereas in Ada 2012
type defining_identifier [discriminant_part] is formal_type_definition;
type defining_identifier [discriminant_part] [is tagged];
So the new kind of formal generic parameter has exactly
the same form as the declaration of an incomplete type. It can be simply
or can require that the actual
be tagged by writing type T is tagged;
— and in both cases a discriminant can be given.
A formal incomplete type can then be matched by any
appropriate incomplete type. If the formal specifies tagged, then
so must the actual. If the formal does not specify tagged then
the actual might or might not be tagged. Of course, a formal incomplete
type can also be matched by an appropriate complete type. And also, in
all cases, any discriminants must match as well.
An example of the use
of a formal incomplete type occurs in the package Ada.Iterator_Interfaces
whose generic formal part is
with function Has_Element(Position: Cursor) return Boolean;
package Ada.Iterator_Interfaces is ...
The formal type Cursor
is incomplete and can be matched by an actual incomplete type. The details
of this package will be described in Section 6.3
Another example is
provided by a signature package as mentioned in the Introduction. We
with function Empty return Set is <>;
with function Unit(E: Element) return Set is <>;
with function Union(S, T: Set) return Set is <>;
with function Intersection(S, T: Set) return Set is <>;
package Set_Signature is end;
Such a signature generic
can be instantiated with an actual set type and then the instance can
be passed into other generics that have a formal package such as
type VN is private;
type VN_Set is private;
with package Sets is
new Set_Signature(Element => VN, Set => VN_Set, others => <>);
package Analyse is
This allows the construction of a generic that needs
a Set abstraction such as a flow analysis
package. Remember that the purpose of a signature is to group several
entities together and to check that various relationships hold between
the entities. In this case the relationships are that the types Set
and Element do have the various operations
Empty, Unit and
The set generic could
be included in a set container package thus
type Element is private;
package My_Sets is
type Set is tagged private;
function Empty return Set;
function Unit(E: Element) return Set;
function Union(S, T: Set) return Set;
function Intersection(S, T: Set) return Set;
package My_Set is new Set_Signature(Element, Set);
The key point is that normally an instantiation freezes
a type passed as a generic parameter. But in the case of a formal incomplete
untagged type, this does not happen. Hence the actual in the instantiation
in the generic package My_Sets
can be a private type such as Set
This echoes back to the earlier discussion of changing
the freezing rules. We cannot call a subprogram with untagged incomplete
parameters (whether formal or not) because we do not know whether they
are to be passed by copy or by reference. But we can call a subprogram
with tagged incomplete parameters because we do know that they are passed
by reference (and this has to remain true for compatibility with Ada
2005). So just in case the actual subprogram in the tagged case is called
within the generic, the instantiation freezes the profile. But in the
untagged case, we know that the subprogram cannot be called and so there
is no need to freeze the profile.
This means that the type Set
should not be given as tagged incomplete in the package Set_Signature
since we could not then use the signature in the package My_Sets.
If a subprogram has both tagged and untagged formal
incomplete parameters then the untagged incomplete parameters win and
the subprogram cannot be called.
(If this is all too confusing, do not worry, the
compiler will moan at you if you make a mistake.)
Another rule regarding incomplete formal types is
that the controlling type of a formal abstract subprogram cannot be incomplete.
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by: