!topic LSN-1084 on Aggregates for Extensions of Abstract Types !key LSN-1084 on Aggregates for Extensions of Abstract Types !reference RM9X-4.3.2;4.0 !reference RM9X-7.6(4,11,12);4.0 !reference 93-3114.a Norman Cohen 93-10-06 !reference 93-3134.a Tucker Taft 93-10-11 !reference 93-3141.a Randy Brukardt 93-10-12 !reference 93-3149.a J-P Rosen 93-10-13 !reference 93-3171.a Randy Brukardt 93-10-19 !from Tucker Taft $Date: 94/02/16 17:32:51 $ $Revision: 1.2 $ !discussion In 93-3114.a Norm Cohen pointed out that the current definition of the type Finalization.Controlled is unnecessarily complicated, and suggested we define them as abstract null record types, rather than as abstract derivatives of some concrete type. The reason they were defined in RM9X;4.0 as abstract derivatives was to enable the use of extension aggregates for extensions of type Controlled. Norm's suggested alternative would also solve that problem, but it would not solve the general problem of aggregates for extensions of abstract types. This LSN examines this issue further, and proposes a general solution. Here is the general problem: type T is abstract tagged private; ... type NT is new T with private; Q : constant NT; -- deferred constant ... type NT is new T with record X : Integer; B : Boolean; end record; Q : constant NT := (??? with X => 3, B => True); One suggestion (in 93-3134.a) was to allow ordinary record aggregates to be used when a parent is a private type, with the components of the private type simply ignored in the aggregate. Hence, the aggregate in the above example would be simply: (X => 3, B => True) Jean-Pierre Rosen felt that this might be error prone, and suggested in 93-3149.a that an explicit syntax be used, perhaps: (private with X => 3, B => True) However, both of these proposals define a construct that works when outside the scope of the parent's full type definition, but not inside it, which can create unpleasant surprises and anomalies. Furthermore, suppose the parent type is an abstract private extension, rather than an abstract private type. Then it becomes unclear whether the "private" refers to all private parts, just the "root" one, just the immediate ancestor's one, etc. NEW PROPOSAL FOR EXTENSION AGGREGATES At the recent DR/XRG meeting, the MRT suggested the following approach -- rather than using the word "private," the parent (sub)type name could be used. Hence, the above aggregate would become: (T with X => 3, B => True) This would work whether or not T is a private type. It is somewhat unusual to have a context where either a subtype mark or an expression is permitted, but this is analogous to the syntax for allocators, where either a subtype_indication or a qualified_expression is permitted. Furthermore, the RM9X;4.0 overload resolution rules require that the "ancestor expression" be resolved using only the information that it must be nonlimited tagged. Hence, there shouldn't be any serious burden in allowing subtype marks as well in this context. The syntax for extension aggregates would be: extension_aggregate ::= (ancestor_part WITH record_component_association_list) ancestor_part ::= subtype_mark | expression The legality rules for an extension_aggregate when the ancestor_part is a subtype_mark would be: 1) The named subtype must be of a type that is the parent or more distant ancestor of the type of the aggregate. It *may* be of an abstract type. 2) The values of any components of the aggregate being defined that are not inherited from the ancestor type must be given after the "with." 3) In addition, if the denoted ancestor subtype is unconstrained, then the discriminants of the aggregate's type must be given explicitly after the "with," even if they are inherited from the ancestor type. Such discriminants come first in positional order, as usual. The dynamic semantics would be that the aggregate is equivalent to declaring a default initialized (temporary) object of the ancestor subtype, and then using it as the ancestor expression in a "regular" extension aggregate (but ignoring the normal rule that you aren't allowed to declare objects of an abstract type). As usual, the implementation would be permitted to eliminate logically redundant Split/Finalization pairs, in case any part of the ancestor type were controlled. MORE LIBERAL RULES? It is conceivable that more liberal rules could be adopted, namely that even (nondiscriminant) components that *are* inherited from the ancestor type could be specified after the "with," and it would be equivalent to the declaration of a variable initialized from an aggregate without these inherited components, followed by a sequence of assignment statements to these inherited components. For example, if the parent type T had a visible component A, then the following would be permitted with these more "liberal" rules: (T with A => 7, X => 3, B => True) However, this would make the rule for positional associations more complex (in particular, presumably inherited components like "A" would only be specifiable using named associations). It would also open up the question of whether a component like A should first be default initialized, and then overwritten, or simply have its default initialization skipped. One could even go further, where the named "ancestor" subtype would be allowed to be the type of the aggregate itself, meaning that *all* components were inherited. This would even make sense for nontagged record types, essentially giving us the ability to have "partial" aggregates. At the DR/XRG meeting, it was pointed out that allowing default initialization to apply to some but not all components of a type could be an implementation burden, given that many implementations use an out-of-line routine to perform default initialization. The basic MRT proposal does not incur this burden, because one can simply use the out-of-line default initialization routine of the specified ancestor type to perform the required default initializations. The more "liberal" proposal, depending on whether or not default initializations would be required/allowed for inherited components like "A", might incur the implementation overhead. Hence, to avoid these various additional issues, which could significantly complicate the semantics and the implementation, we recommend we go with the "basic" MRT proposal, as implied by the three legality rules given above. Although the more liberal rules are possible, we don't recommend them at this time. CONTROLLED TYPES AGAIN Given the above proposal, we can declare Finalization.Controlled as simply an abstract private type, and eliminate all mention of the package System.Finalization_Implementation and the types and constants therein. If one wishes to declare a constant of a type derived from Controlled, it is now straightforward: with Ada.Finalization; use Ada; package Dyn_Strings is type Dyn_String is private; Null_String : constant Dyn_String; ... private type String_Ptr is access String; type Dyn_String is new Finalization.Controlled with record Ptr : String_Ptr := null; end record; Null_String : constant Dyn_String := (Finalization.Controlled with Ptr => null); -- **** new syntax **** end Dyn_Strings; This seems much cleaner than the approach implied by RM9X-7.6;4.0, where the mysterious constant "Root_Part" of the mysterious type Finalization_Implementation.Root_Controlled would have to be used. Furthermore, this same approach is available for all types extended from abstract types, not just controlled types. -Tuck