!standard 4.5.2(13)                                    23-01-12  AI22-0053-1/02

!standard 4.5.2(15/5)

!class binding interpretation 22-10-27

!status work item 22-10-27

!status received 22-07-01

!priority Low

!difficulty Easy

!qualifier Omission

!subject An unintended consequence of AI12-0101-1


If an untagged record type overrides "=" in a private part, that overriding equality is

used for all calls to "=".


Suppose you have an untagged record type declared (completely; no partial view) in the visible part of a package and an overriding equality operator for it declared in the private part. At one point, that was illegal. AI12-0101-1 made it legal, but failed to adjust the dynamic semantics, illustrated by the example below.

procedure Test53 is
  pragma Assertion_Policy (Check);

  package Pkg is
         type Untagged_Rec is record
            X, Y : Integer;
         end record;

         type Tagged_Rec is tagged record
            X, Y : Integer;
         end record;
         function "=" (L, R : Untagged_Rec) return Boolean is (L.X = R.X);
         function "=" (L, R : Tagged_Rec) return Boolean is (L.X = R.X);
  end Pkg;
  use Pkg;
  U1 : constant Untagged_Rec := (0, 111);
  U2 : constant Untagged_Rec := (0, 222);
  T1 : constant Tagged_Rec := (0, 111);
  T2 : constant Tagged_Rec := (0, 222);

  U_Eq : constant Boolean := U1 = U2;
  T_Eq : constant Boolean := T1 = T2;

  type U_Wrapper is record F : Untagged_Rec; end record;
  type T_Wrapper is record F : Tagged_Rec;   end record;

  Uw_Eq : constant Boolean := U_Wrapper'(F => U1) = (F => U2);
  Tw_Eq : constant Boolean := T_Wrapper'(F => T1) = (F => T2);
  -- 3 out of 4 dentists recommend user-defined equality
  pragma Assert (not U_Eq);
  pragma Assert (T_Eq);
  pragma Assert (Uw_Eq);
  pragma Assert (Tw_Eq);
end Test53;


The !discussion section for AI05-0123-1 very explicitly addresses this point:

It would be pretty weird if the directly called equality didn't agree with the composed equality. For example, a call to the "=" operator for an untagged record type R ought to yield the same answer as a corresponding call to the predefined "=" operator for a one-field record type whose one component is of type R.

An argument was made in AI12-0101-1 that no changes in dynamic semantics were needed, but that argument ignored the case where the view declared in the visible part is not a partial view. The following is from the !discussion section of AI12-0101-1:

If we simply delete the second sentence of 4.5.2(9.8/3) (as proposed here), then 4.5.2(15/3) comes into effect and any overriding of "=" [before freezing] is used as the definition of "=" everywhere (even where the overriding "=" is not visible).

Note that there are no partial views of anything in the preceding example, so 4.5.2(15/5) does not come into play at all.


(See Summary.)


Add after 4.5.2(13):

For a record type, after the first freezing point of the type, predefined equality is defined in terms of the primitive equals operator of the type.

[Author's Note: Type extensions already have a separate rule in 4.5.2(14/3), and we have the private type rule in 4.5.2(15/3). It seems to make the most sense to have this new rule be separate and precede the existing rules.]

AARM Reason: Hidden overriding of "=" is used for all predefined equality operators  of the type, so that composition works sensibly. We say "after the first freezing point of the type" so that a squirreling rename can be used to make the actual predefined equality visible if that is needed.


There are two ways to fix this problem.

We could ban such overrides, as they are unusual and likely a mistake. That was the solution given in AI05-0123-1. But the solution proved to be too incompatible, and thus it was repealed by AI12-0101-1. Putting back part of it would seem to simply be repeating the previous mistake. Thus we rejected that alternative.

The other option is to add a rule like 4.5.2(15/5) that applies in this case. This is OK as we do not allow overriding (of anything) after the type is frozen. This fixes the problem without creating a new (compile-time) incompatibility, but it does create a new inconsistency (a runtime incompatibility). In particular, existing client code would now get the (hidden) overriding "=" rather than the predefined "=". AI12-0101-1 argues fairly persuasively that this inconsistency is most likely fixing a bug.

The proposed rule only hides predefined equality after the freezing point of a type. Any attempt to call equals will freeze the associated type, so this does not cause any anomalies. But it does allow using a squirreling rename to name the actual predefined equality for an untagged record type. This provides a work-around in the unusual case that one needs both the actual predefined equality and the overriding equality.

Note that the proposed rule only changes the effect of "=" for an untagged type declared in the visible part of a package specification, and which has an overriding "=" declared in the private part of that specification. Tagged types already call the overriding body regardless of where it appears, and if "=" is visibly overridden, the overriding body is always used for the call of "=" as well.

!ACATS test

An ACATS C-Test should check that an example like that in the !Issue gives the

expected results.


This issue was originally raised by Steve Baird privately.