Ada-Rapporteur-Group / User-Community-Input

Ada User Community Input Working Group - Github Mirror Prototype
27 stars 1 forks source link

Squirreling away inherited/predefined operations of a private extension #99

Open sttaft opened 5 months ago

sttaft commented 5 months ago

In the following program, it is not completely clear what the renamings of the inherited/predefined operations of GrandChild are supposed to do. In particular, Eq is defined as a rename of "=", and Save_Label is defined as a rename of "Label", at a point where we don't know what type is the immediate parent of GrandChild, since GrandChild is a private extension of Root, and only in the private part we learn it is an immediate descendant of Child.

In general, renaming of dispatching operations has some special rules, given in RM 8.5.4(8/3):

For a call on a renaming of a dispatching subprogram that is overridden, if the overriding occurred before the renaming, then the body executed is that of the overriding declaration, even if the overriding declaration is not visible at the place of the renaming; otherwise, the inherited or predefined subprogram is called. A corresponding rule applies to a call on a renaming of a predefined equality operator for an untagged record type.

We are in the "otherwise" case of the above rule, but what is meant by "the inherited or predefined subprogram" is a bit ambiguous, since we haven't seen the full definition of GrandChild yet, so we don't know what are the properties of this inherited or predefined subprogram. Ultimately, GrandChild is defined as an immediate descendant of Child, and so presumably it is the version of Label inherited from Child, and the predefined "=" operator composed from the primitive "=" of Child and the "=" operators of any new components of GrandChild (there are none).

In this case, the current version of GNAT uses the "Root" version of "Label" and the GrandChild version of "=", so it seems to be wrong in both cases. On the other hand, it is a bit weird for a renaming that is supposed to be "squirreling away" something, to do it before that something even exists...

So the question is do we want to confirm that a renaming of an operation of a private extension before any overriding does in fact map at runtime to the operations inherited and/or constructed from those of its eventual immediate ancestor? Or do we want to make such a renaming illegal? Or do we want it to squirrel away operations of the ancestor specified in the private extension ("Root" in this example), or do we want it to simply map to the "final" operations of the full definition of the private extension ("GrandChild's operations)? Should the answer depend on whether or not the overridings happen in the visible part?

At the moment GNAT is all over the map on answering this question. It might be interesting to test other Ada 95+ compilers, since this feature has been present since Ada 95.

with Ada.Text_IO; use Ada.Text_IO;
procedure Bar is
   package A is
      type Root is tagged null record;
      function Label (R : Root) return Integer is (0);
      type GrandChild is new Root with private;
      function Eq (L : GrandChild; R : GrandChild) return Boolean renames "=";
      function Save_Label (R : GrandChild) return Integer renames Label;
      overriding function Label (R : GrandChild) return Integer is (2);
      function Make return GrandChild;
      overriding function "=" (L : GrandChild; R : GrandChild) return Boolean;
   private
      type Child is new Root with null record;
      overriding function Label (R : Child) return Integer is (1);
      overriding function "=" (L : Child; R : Child) return Boolean is (False);
      type GrandChild is new Child with null record;
      function Make return GrandChild is (null record);
      function "=" (L : GrandChild; R : GrandChild) return Boolean is
         (raise Program_Error);
   end A;
   X : A.GrandChild := A.Make;
begin
   Put_Line (X.Save_Label'Image); --  GNAT: 0
   if X.Eq (X) then -- GNAT: Program_Error
      Put_Line ("True");
   else
      Put_Line ("False");
   end if;
end Bar;
yannickmoy commented 5 months ago

Thanks Tuck for the clear explanation of the issue. For consistency with the renaming of a non-dispatching primitive which "squirrels away" its designated operation, I think it makes more sense to squirrel away operations of the ancestor specified in the private extension.

ARG-Editor commented 5 months ago

Yannick Moy wrote:

Thanks Tuck for the clear explanation of the issue. For consistency with the renaming of a non-dispatching primitive which "squirrels away" its designated operation, I think it makes more sense to squirrel away operations of the ancestor specified in the private extension.

I was going to write something like this, but Yannick beat me to it. Suffice it to say that I agree with him. I don't see any compelling reason to break privacy here, and the programmer can squirrel the operation of the record extension by putting it into the private part after the record extension. Seems more flexible (if marginally useful either way).

I would have expected typical squirreling to occur in the private part anyway; there's no reason to make it available to clients of the package (at least for the usual use of being able to call it in the overridden operation). So in some sense this is a very unlikely case.

            Randy.
sttaft commented 5 months ago

I am glad to see some agreement on how to handle this. What you propose makes some sense, but it also means that we are holding onto operations that are pretty strange.

The "predefined" equality in particular seems weird, and I am not sure exactly what it would do. If each of the hidden ancestors of GrandChild adds some components, do these components participate in the equality test, even if the corresponding ancestor has overridden its own "=" operator?

I think in cases where it is unclear what are the semantics, my own instinct would be to make it illegal. It would be interesting to see how many uses of "early renaming" of private extension operations exist in the "wild", and what exactly the users of the feature expect to happen. If we base it on what GNAT does, it is a bit hard to know!

yannickmoy commented 5 months ago

Yeah, making it illegal looks good too. With a nice explanation message in GNAT about the odd case at hand!