Version 1.3 of ai05s/ai05-0130-1.txt

Unformatted version of ai05s/ai05-0130-1.txt version 1.3
Other versions for file ai05s/ai05-0130-1.txt

!standard 7.6(12)          09-01-16 AI05-0130-1/02
!standard 7.6.1(9/2)
!class ramification 09-01-16
!status work item 08-12-04
!status received 08-10-17
!priority Low
!difficulty Medium
!qualifier Clarification
!subject Order of initialization/finalization of record extension components
!summary
The rules about order of initialization of record components are correct. This can cause some extension components to be initialized before some ancestor components.
!question
When a record contains two fields, one with an access discriminant and one without, the field with the access discriminant is initialized last and finalized first.
Does this also apply for record extensions? It would seem that the wording in 7.6(12) means that some of the components of a parent type could be required to be initialized after the extension components. In the worst case, it could make composition of initialization/finalization operations impossible (the operations would have to be different for every extension in order to get the ordering right).
Is this the intent? (Yes.)
!response
The question is asking about an example like:
with Ada.Finalization; package Pak1 is type Rec0;
type Rec1 (D : access Rec0) is new Ada.Finalization.Limited_Controlled with null record;
type Rec0 is tagged limited record Comp1 : Rec1 (Rec0'access); -- has access discriminant end record;
type Rec2 is new Ada.Finalization.Limited_Controlled with null record;
procedure Initialize (X : in out Rec1); procedure Finalize (X : in out Rec1);
procedure Initialize (X : in out Rec2); procedure Finalize (X : in out Rec2);
type Rec4 is new Rec0 with record Comp2 : Rec2; -- no access discriminant end record;
end Pak1;
with Ada.Text_IO; package body Pak1 is
procedure Initialize (X : in out Rec1) is begin Ada.Text_IO.Put_Line ("Initialize Rec1"); end Initialize;
procedure Finalize (X : in out Rec1) is begin Ada.Text_IO.Put_Line ("Finalize Rec1"); end Finalize;
procedure Initialize (X : in out Rec2) is begin Ada.Text_IO.Put_Line ("Initialize Rec2"); end Initialize;
procedure Finalize (X : in out Rec2) is begin Ada.Text_IO.Put_Line ("Finalize Rec2"); end Finalize;
end Pak1;
with Pak1; procedure Test1 is X : Pak1.Rec4; begin null; end Test1;
The language rules as currently constituted require that this program print:
Initialize Rec2 Initialize Rec1 Finalize Rec1 Finalize Rec2
The reason for this requirement is in order that the Initialize operation for a component with a self-referential access discriminant can assume that other components of the enclosing object have already been properly initialized (AARM 7.6(12.b)). While that can't happen in this case, it is possible to construct cases where an ancestor component has an Initialize operation that does manage to access extension components. For instance, change Rec1.D to a classwide type, and dispatch on it to an operation that uses the extension components.
This requirement is counter-intuitive, as it means that an extension component cannot assume that all of the ancestor components are initialized (if there are any components constrained by access discriminants). The conceptual model of extension components being added to the existing components without any semantic changes is false in this case. That is a possible source of bugs.
But, clearly, the current rule also prevents bugs (just different bugs). In the absence of any reason to choice one set of bugs over another, it makes the most sense for the language to be consistent and use the same rules for all types. Thus, we do not recommend a change to this rule.
Note that the rules as written potentially have an extra implementation cost over the "composing" order. If the implementation uses a subprogram to initialize components of a tagged type T, it would make sense to use that subprogram to initialize the parent components of an extension of T (and just initialize the extension components directly in T). But that would be complicated by this requirement, and could not be done at all in unusual cases.
[Data point: at least some existing implementations get this wrong. There is clearly an implementation burden (and possible introduction of bugs) in redoing the compilers to get this right. Generally, we'd want an ACATS test to check these examples, which would encourage implementers to change. Do we want that??]
!ACATS Test
An ACATS C-test should be constructed to test a case like the example given here, in order to verify that the rule is implemented properly.
!appendix

!topic  order of initialization/finalization of record components
!reference Ada 2005 RM 7.6(12), RM 7.6.1(9), AARM 7.6(12.b), AARM 7.6(9.a)
!from Dan Eilers 2005-10-17
!keywords initialize finalize access discriminant
!discussion

When a record contains two fields, one with an access discriminant and one
without, the field with the access discriminant is initialized last and
finalized first. The AARM explains:

  7.6(12.b):

   The fact that Initialize is done for components with access
   discriminants after other components allows the Initialize operation
   for a component with a self-referential access discriminant to assume
   that other components of the enclosing object have already been
   properly initialized.

  7.6.1(9.a):

   Reason: This allows the finalization of a component with an access
   discriminant to refer to other components of the enclosing object
   prior to their being finalized.

Is this rule supposed to apply even when the record is a type extension, and
the field with the access discriminant is in the ancestor part of the record?
The fields in the ancestor part should probably be initialized first and
finalized last, right?

    with Ada.Finalization;
    package Pak1 is
        type Rec0;

        type Rec1 (D : access Rec0) is new
            Ada.Finalization.Limited_Controlled with null record;

        type Rec0 is tagged limited record
            Comp1 : Rec1 (Rec0'access);     -- has access discriminant
        end record;

        type Rec2 is new Ada.Finalization.Limited_Controlled with null record;

        procedure Initialize (X : in out Rec1);
        procedure Finalize (X : in out Rec1);

        procedure Initialize (X : in out Rec2);
        procedure Finalize (X : in out Rec2);

        type Rec4 is new Rec0 with record
           Comp2 : Rec2;   -- no access discriminant
        end record;

    end Pak1;


    with Pak1;
    procedure Test1 is     -- related to c760012.a
        X : Pak1.Rec4;
    begin
        null;
    end Test1;


    with Ada.Text_IO;
    package body Pak1 is

        procedure Initialize (X : in out Rec1) is
        begin
            Ada.Text_IO.Put_Line ("Initialize Rec1");
        end Initialize;

        procedure Finalize (X : in out Rec1) is
        begin
            Ada.Text_IO.Put_Line ("Finalize Rec1");
        end Finalize;


        procedure Initialize (X : in out Rec2) is
        begin
            Ada.Text_IO.Put_Line ("Initialize Rec2");
        end Initialize;

        procedure Finalize (X : in out Rec2) is
        begin
            Ada.Text_IO.Put_Line ("Finalize Rec2");
        end Finalize;

    end Pak1;

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

From: Adam Beneschan
Sent: Thursday, December 18, 2008  7:52 PM

I wanted to point out that an important statement in the discussion of
AI05-0130-1 is, I believe, not quite correct.

The example looks essentially like:

    type Rec0;

    type Rec1 (D : access Rec0) is new
       Ada.Finalization.Limited_Controlled with null record;

    type Rec0 is tagged limited record
       Comp1 : Rec1 (Rec0'access);     -- has access discriminant
    end record;

    procedure Initialize (X : in out Rec1);
    procedure Finalize (X : in out Rec1);

    type Rec4 is new Rec0 with record
       Comp2 : Rec2;   -- a controlled type, no access discriminant
    end record;

The language rules currently require that, for an object X of type Rec4, that
X.Comp2 be initialized before X.Comp1 and finalized after.

The discussion section of the AI contains this:

"But the reason for this requirement is in order that the Initialize operation for
a component with a self-referential access discriminant can assume that other
components of the enclosing object have already been properly initialized (AARM 7.6(12.b)).
Such a component cannot access (or even know about) components of any extensions, so
there does not appear to be any value to having this requirement apply to all of
the components of a type extension (rather than just to the extension components alone)."
     
The last sentence is correct in the above example.  But change it to: 

    type Rec0 is tagged;

    type Rec1 (D : access Rec0'Class) is new  -- CHANGED!!!!!
       Ada.Finalization.Limited_Controlled with null record;

    type Rec0 is tagged limited record
       Comp1 : Rec1 (Rec0'access);     -- has access discriminant
    end record;

    procedure Initialize (X : in out Rec1);
    procedure Finalize (X : in out Rec1);

    type Rec4 is new Rec0 with record
       Comp2 : Rec2;   -- a controlled type, no access discriminant
    end record;

and it's no longer correct, because now when Comp1 is initialized or finalized, the
user-defined Initialize or Finalize routine *could* access Comp2, via a dispatching call
on D.all (or by other means).  So it would be a problem if the rules were changed to
allow Comp2 to be initialized after Comp1, or finalized before.  E.g.:

    type Rec0 is tagged;

    type Rec1 (D : access Rec0'Class) is new
       Ada.Finalization.Limited_Controlled with null record;

    type Rec0 is tagged limited record
       Comp1 : Rec1 (Rec0'access);     -- has access discriminant
    end record;
    function Image_Of (X : in Rec0) return String;

    procedure Initialize (X : in out Rec1);
    procedure Finalize (X : in out Rec1);

    type Rec4 is new Rec0 with record
       Comp2 : Rec2;   -- a controlled type, no access discriminant
    end record;
    overriding function Image_Of (X : in Rec4) return String;


    procedure Finalize (X : in out Rec1) is
    begin
       Write_To_Log ("Finalizing Rec1 that refers to: " & Image_Of(X.D.all));
       ...
    end Finalize;

Assuming that Image_Of (X : in Rec4) contains some information about X.Comp2, the above
Finalize routine would be calling Image_Of at a point after Comp2 were finalized, if the
language rules were changed. 
   
****************************************************************

From: Randy Brukardt
Sent: Thursday, December 18, 2008  8:07 PM

Thanks, Adam, for this example, I obviously didn't think of it. Did you tell Dan?? :-)
[This was originally his question.]

Specifically, with this additional example, I no longer understand precisely what change
he was recommending. Moreover, I can't think of a good reason for changing the rules for
*some* extension types, but not *all* extension types. If the compiler can't use composition
in some cases, there's little reason to go out of our way allow it in other cases -- all of
the support code needed to handle non-composing initialization must necessarily already exist
in the compiler. Once you've gone to all of that work, there doesn't seem to be any reason
not to use it always. (And, of course, rules that change because of the contents of some
construct are a maintenance hazard.)

As such, I will probably rewrite it as a confirmation (the existing rules are correct),
unless someone explains better than I can why that would be a bad idea.

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

From: Dan Eilers
Sent: Monday, January 5, 2009  8:22 PM

My main concern is that the existing rules are somewhat counter-intuitive, in that they imply
that an ancestor may not be fully initialized before its descendants.  This could conceivably
cause problems if the initializations have side-effects, and the order of the side-effects
matters.  Adam has a good point, that changing this can also cause problems.  So I don't feel
strongly about how this is decided.

In any case, there should be an ACATS test, especially considering that implementations currently
differ.  There should also probably be an RM note.

p.s.
  I think access discriminants are often used as workarounds for missing language features, and
their problems could be minimized by providing direct language support for controlled types as
interfaces, and by-reference IN parameters with modifiable state, etc.

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

Questions? Ask the ACAA Technical Agent