Rationale for Ada 2012

John Barnes
Contents   Index   References   Search   Previous   Next 

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
      Next: Cell_Ptr;
      Element: Pointer;
   end record;
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 recursive types.
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 T1;
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 the body.
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 type.
The other exciting change regarding incomplete types is that in Ada 2012 they are allowed as generic parameters. In Ada 2005 the syntax is
formal_type_declaration ::=
   type defining_identifier [discriminant_part] is formal_type_definition;
whereas in Ada 2012 we have
formal_type_declaration ::=
 | formal_incomplete_type_declaration
formal_complete_type_declaration ::=
   type defining_identifier [discriminant_part] is formal_type_definition;
formal_incomplete_type_declaration ::=
   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 type T; or can require that the actual be tagged by writing type T is tagged; — and in both cases a discriminant can be given.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
   type Cursor;
   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 on iteration.
Another example is provided by a signature package as mentioned in the Introduction. We can write
   type Element;
   type Set;
   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 so on.
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);
end My_Sets;
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 of Set_Signature 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.

Contents   Index   References   Search   Previous   Next 
© 2011, 2012, 2013 John Barnes Informatics.
Sponsored in part by:
The Ada Resource Association:


and   Ada-Europe: