Version 1.2 of ais/ai-20359.txt

Unformatted version of ais/ai-20359.txt version 1.2
Other versions for file ais/ai-20359.txt

!standard 12.03 (12)          04-07-29 AI95-00359-03/01
!standard 12.03 (20)
!standard 13.14 (05)
!class amendment 04-07-29
!status No Action (9-0-0) 04-09-19
!status work item 04-07-29
!status received 04-07-29
!priority Medium
!difficulty Hard
!subject Delayed completion of a generic instantation
!summary
To better support generic signature packages, and mutually dependent types that involve generic instantiations, syntax is defined to allow a generic instantiation can be split into two parts.
For such an instantiation, no freezing or elaboration occurs at the point of the instantiation, rather it is deferred until a completion point. Between the instantiation and the completion, only a limited view of the instance is available.
!problem
Ada 95 provides formal package parameters. One way of using these parameters is to define a "signature" for a class of abstractions, such as all set abstractions, or all physical unit abstractions, and then build a new generic abstraction on top of the original class of abstractions using this signature as the template for a formal package parameter.
Unfortunately, it is difficult to use signatures because of the fact that an instantiation freezes all of its actual parameters.
For example:
Given the signature for a set abstraction:
generic type Element is private; type Set is private; with function Size(Of_Set : Set) return Natural is <>; with function Union(Left, Right : Set) return Set is <>; with function Intersection(Left, Right : Set) return Set is <>; with function Empty return Set is <>; with function Unit_Set(With_Item : Element) return Set is <>; with function Nth_Element(Of_Set : Set) return Element is <>; package Set_Signature is end;
... we could define a generic that required some set abstraction, but it didn't care which one so long as it implemented the above signature:
generic with package Base_Set is new Set_Signature(<>); package Layered_Abstraction is type Cool_Type(Set : access Base_Set.Set) is limited_private; procedure Massage(CT : in out Cool_Type; New_Set : Base_Set.Set); ...
end Layered_Abstraction;
Now if we want to define a set type and provide the pre-instantiated signature for it, we run into trouble:
generic type Elem is private; with function Hash(El : Elem) return Integer; package Hashed_Sets is type Set is private; function Union(Left, Right : Set) return Set;
...
package Signature is new Set_Signature(Elem, Set); private type Set is record
... end record; end Hashed_Sets;
The problem is that we can't do the instantiation of Set_Signature where we would want to do so, because the instantiation freezes the type "Set" prematurely.
A similar problem occurs when a type wants to include a pointer to a container based on the type being defined. For example:
package Expressions is type Expr_Ref is private;
package Expr_Sequences is new Sequences(Expr_Ref); -- Freezing trouble here! type Seq_Of_Expr is access Expr_Sequences.Sequence;
function Operands(Expr : Expr_Ref) return Seq_Of_Expr;
...
private type Expression; -- completion deferred to body type Expr_Ref is access Expression; end Expressions;
Here we have a case where we want to instantiate a generic using a private type, and use the results of the instantiation as the designated type of an access type, which is in turn used as a parameter or result type of an operation on the private type.
Unfortunately, we can't instantiate the "Sequences" generic with Expr_Ref, since it is private.
!proposal
(See summary.)
!wording
Add "deferred" to the list of keywords in 2.9.
Change 12.3(2):
generic_instantiation ::= package defining_program_unit_name is [deferred] new generic_package_name [generic_actual_part]; ....
Add after 12.3(7):
A generic_instantiation which contains the reserved word deferred is called a deferred instantiation.
The form of a pragma Complete is as follows:
pragma Complete(generic_instantiation_local_name);
Add after 12.3(10):
A deferred instantiation shall not be a library_unit_declaration.
AARM Note: This is not strictly necessary, but the completion of such a unit would have to implicitly follow it, so it would be identical to not using the deferred. That just makes the use confusing.
The generic_instantiation_local_name of a pragma Complete shall be the name of a deferred instantiation, and shall not occur in any other pragma Complete immediately in the same declarative region.
AARM Note: "Local_Name" takes care of the check that the instantiation is in the same declarative region, and insures that the instantiation comes first. See 13.1(5/1).
A deferred instantiation is said to be completed after the occurrence of a pragma Complete referencing the name of the instance; if no pragma exists, the deferred instantiation is completed at the end of the nearest enclosing declarative_part or package_specification. If multiple deferred instantations are completed at the end of a declarative_part or package_specification (because pragma Complete was not specified for them), they are completed in order of their declaration.
A name other than a package name or subtype name declared by a deferred instantiation shall be referenced only if the deferred instantiation is completed. A package name declared by a deferred instantiation shall occur in a use_package_clause only if the deferred instantiation is completed.
AARM Notes: These rules (plus the static semantic rules below) are intended to prevent any access that would require freezing or elaboration of the generic instance. Thus they can be thought of as being compile-time access-before-elaboration checks.
Since we know that the elaboration of the instance body has completed before we finish elaborating the immediate declarative region, references to the instance from outside of that region do not need these rules. That's different than the usual case in Ada; usually there is increasing capability for local scopes. Here we have restrictions that apply only locally, but not globally.
These rules are intentionally similar to the limited view rules. End AARM Notes.
Add after paragraph 12.3(19):
If a deferred instantiation is not completed, each type_declaration in the visible part denotes an incomplete view of the type. If the type_declaration is tagged, then the view is a tagged incomplete view.
Add before paragraph 12.3(20):
A non-deferred instantiation is elaborated at the place of the generic_instantiation. A deferred instantiation is elaborated at at the place where it is completed.
[Editor's note: Do we need to change the wording of 12.3(20)? I think we don't because the semantics of elaboration are the same, only the location is different. But the use of the syntactic term might be confusing. A way around would be to use the long form "generic_instantation including the reserved word deferred" rather than the shorthand as above.]
Replace paragraph 13.14(5) by:
o The occurrence of a pragma Complete causes freezing of the deferred
instantiation that it references.
AARM Note: We don't need to specifically mention default completions, because the end of the declarative_part or package_specification will cause freezing, as noted in 13.14(3/1). Implementers can imagine that the freezing occurs in the same order as elaboration of the instances (that is, the order of declaration).
o The occurrence of a generic_instantiation that does not contain the
reserved word deferred causes freezing.
o If an instantiation causes freezing, if a parameter of that instantiation
is defaulted, the default_expression or default_name for that parameter causes freezing.
Add after paragraph 13.14(19/1):
A deferred instantiation shall be completed before it is frozen (see 12.3).
AARM Note: This rule is needed in case a non-instance body occurs before the implicit or explicit completion. Other references that might freeze are already illegal by the rules given uses of deferred instantiations that are not completed.
!example
(See discussion.)
!discussion
Signature packages can be a very useful feature, but if they can't be instantiated in the generic that defines the abstraction, they become of much less use.
Generics in general can be very useful, but the freezing rules mean that they are not as powerful as cut-and-paste, since they can't be applied to private types before the private type is completely defined.
This proposal makes it possible for a generic instantiation to be used in more places than before, since it can be used with a private type before the type is completely defined. As things stand now, it is often necessary to completely restructure things to be able to use a generic container as part of defining a private type.
This proposal allows spliting an instantiation into two parts, delaying the freezing and all of the run-time effects (and thus full definition requirements) to the second part. Note that the actuals are not evaluated until the completion is elaborated. That's necessary because evaluation of the actuals would necessarily require them to be frozen. But freezing of actual types, subprograms, and expressions would simply prevent the use of partial views -- which of course is what we're trying to support. (We could revise the freezing rules to work around this by saying that actual types and subprograms, but that would still prevent the use of deferred constants as actual parameters, which would be a significant restriction.)
The restrictions on the use of the generic unit before the completion are similar to those of a limited view. We didn't use limited views directly, because the restrictions only apply between the deferred instance declaration and the completion, and thus are best described as legality and static semantics rules.
These restrictions can be thought of as static access-before-elaboration checks. This has the nice property of maintaining the invariant that there is never any runtime Elaboration_Check for entities exported by an instance.
Of course, it is possible for the instance body to be (implicitly) located at a place where some of the actual types are not completely defined, and that's problematic. We solve this by changing the freezing rules so that the instance body freezes the actual parameters. This may lead to compile-time errors with rather mysterious error messages, but hopefully these errors will be infrequent.
We only support deferred package instantiations, as subprograms do not export anything interesting other than the subprogram itself. If it is desired to use such a subprogram instantiation with partial views, that can easily be accomplished with a renames-as-body. So we don't need a special construct for that. (Supporting it would complicate some of the wording, but otherwise is not a problem.)
With this proposal, the following example extracted from the email discussion works as expected:
generic type Elem is private; with function Code (I : Elem) return Natural; type Elem_Array is array (Positive range <>) of Elem; package Array_Hashing is function Hash (Arr : Elem_Array) return Natural; end Array_Hashing;
package body Array_Hashing is type Nat_Mod is mod Natural'Last; function Hash (Arr : Elem_Array) return Natural is Res : Nat_Mod := 1; begin for I in Arr'range loop Res := Res*3 + Nat_Mod(Code(Arr(I))); end loop; return Natural(Res); end Hash; end Array_Hashing;
package User is type Something is private; function Count (A : Something) return Natural; type Array_of_Something is array (Positive range <>) of Something; package My_Hash is deferred new Array_Hashing (Something, Count, Array_of_Something); NULL_HASH : constant Natural; private type Something is range 0 .. 200; pragma Complete (My_Hash); NULL_HASH : constant Natural := My_Hash.Hash ((1..0 => 0)); end User;
The instance body for My_Hash is located immediately before the call to My_Hash.Hash, so this call is legal. The instance body freezes Something, but this type is already completely defined so there is no illegality.
NOTES ON THE DESIGN
The use of a pragma for completion is somewhat uncomfortable. This pragma changes the visibility of items, which generally not allowed for pragmas. A pragma was used because it didn't require the introduction of yet another keyword or odd conformance rules. It also seems more in keeping with something optional; syntax would seem more appropriate if the completion was required to be given.
A syntax alternative would be:
package deferred_generic_instantiation_local_name is complete;
with the same semantics as the pragma. If syntax is used, it probably ought to be required, so that there is no implicit completion. That would simplify some of the rules.
Other syntax alternative considered were:
package deferred_generic_instantiation_local_name is elaborated;
which also requires a second keyword; and
package deferred_generic_instantiation_local_name is new generic_name (<>);
which requires a conformance rule for the generic_name.
Another alternative would be to repeat the entire instantiation at the point of the completion. But that would look like a full instantiation, which is confusing both readers and the compiler. It also would require conformance rules for the formal parameters, which just isn't worth the effort.
A number of alternative syntaxes were considered for the instantiation.
We could use limited instead of deferred to avoid using a new keyword:
package My_Hash is limited new Array_Hashing (Something); pragma Complete (My_Hash);
but this seems confusing, since the view is limited only within the package.
Another option avoiding a new keyword is:
delay package My_Hash is new Array_Hashing (Something); pragma Complete (My_Hash);
but this is pretty weird.
--!corrigendum 13.14(05)
!ACATS test
An ACATS test should be created for this feature.
!summary
!appendix

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


Questions? Ask the ACAA Technical Agent