Version 1.1 of acs/ac-00281.txt

Unformatted version of acs/ac-00281.txt version 1.1
Other versions for file acs/ac-00281.txt

!standard 4.6(50)          16-08-18 AC95-00281/00
!class confirmation 16-08-18
!status received no action 16-08-18
!status received 16-06-28
!subject Downward conversions
!summary
!appendix

From: Jeff Cousins
Sent: Tuesday, June 28, 2016  11:27 AM

Using CodePeer has (indirectly) revealed that a few of our programmers seem to
have circumvented the rule against converting tagged objects away from the root
by converting pointers instead.  GNAT doesn't give any warning.  Is this legal?
Shouldn't it at least require an Unchecked_Conversion of some sort?

package AFU is
   type Root_Facade_Type is tagged limited record
      Error_Code : Integer;
   end record;
end AFU;

with AFU;

package MCI is
   type Facade_Handle_Type is limited private;

   procedure Define_Construct
    (Fhandle : in out Facade_Handle_Type); private
   type Facade_Handle_Type is access all AFU.Root_Facade_Type'Class; end MCI;

package body MCI is
   type Construct_Message_Data_T is
   record
      Valid_Operation : Boolean;
   end record;

   type Constructs_IF_Attr_Type is new AFU.Root_Facade_Type with
   record
      Message_In_Progress : Construct_Message_Data_T;
   end record;

   type Constructs_IF_Attr_Ptr_Type is access all Constructs_IF_Attr_Type;

   procedure Define_Construct
    (Fhandle : in out Facade_Handle_Type) is

      -- Downward conversion of tagged object not allowed
      Root_Facade : AFU.Root_Facade_Type := (Error_Code => 99);
      Constructs_IF_Attr : constant Constructs_IF_Attr_Type :=
       Constructs_IF_Attr_Type (Root_Facade);

      -- But downward conversion of pointer to tagged object is allowed
      Constructs_IF_Attr_Ptr : constant Constructs_IF_Attr_Ptr_Type :=
       Constructs_IF_Attr_Ptr_Type (FHandle);
      Message_Data : Construct_Message_Data_T
       renames Constructs_IF_Attr_Ptr.Message_In_Progress;
   begin
      if Message_Data.Valid_Operation then
         null;
      end if;
   end Define_Construct;
end MCI;

with MCI;

procedure Downward_Conversion_Test is

   MEZ_Construct_IF_Handle : MCI.Facade_Handle_Type;

begin

   MCI.Define_Construct (Fhandle => MEZ_Construct_IF_Handle);

end Downward_Conversion_Test;

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

From: Tucker Taft
Sent: Tuesday, June 28, 2016  12:01 PM

So long as the access type is access-to-classwide (which is the case for
Facade_Handle_Type), conversion toward the leaves is permitted.  See good old RM
4.6, paragraph 24.13 and then 23/2.  Conversion toward the leaves is permitted
on tagged class-wide types directly as well.

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

From: Steve Baird
Sent: Tuesday, June 28, 2016   1:07 PM

Tuck pointed out that we don't allow a downward tagged-to-tagged conversion
unless the operand's type is class-wide, and that an analogous rule applies to
conversion to a general access-to-tagged type.

I'll add that the consistency in the treatment of the two forms of conversion
extends to the associated runtime checks.

4.6(50) says:
    "any checks associated with evaluating a conversion to the target
     designated subtype are performed" .

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

From: Randy Brukardt
Sent: Tuesday, June 28, 2016  2:18 PM

Right. The case in Jeff's example will always raise Constraint_Error. That is,
it's not unsafe, it's just not going to work as expected.

This sort of class-wide (root) to specific (leaf) conversion is commonly used; I
had to use many of them in the Claw Builder. It's the only way to get at
specific operations of the object if dispatching isn't appropriate. But it's
important to verify that the object is in fact of the right type before doing
such a conversion -- I suspect that is the real problem that Codepeer is
diagnosing.

I.e.
    type Any_Window_Access is access all Root_Window_Type'Class;

    type Checkbox is new Root_Window_Type with private;
    function Is_Checked (C : in Checkbox) return Boolean;

    Foo : Any_Window_Access := ...;


In order to find out if Foo is checked, one ought to use code something
like:

    if Foo.all in Checkbox then
        if Is_Checked (Checkbox (Foo.all)) then
            ...
    -- else not a checkbox
    end if;

And for an access-to-checkbox type:

    type Checkbox_Access is access all Checkbox;

    if Foo.all in Checkbox then
        if Is_Checked (Checkbox_Access (Foo).all) then
            ...
    -- else not a checkbox
    end if;

(I personally don't see any reason to use access-to-specific in OOP code - I
think it should be discouraged - but of course YMMV.)

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

From: Jeff Cousins
Sent: Tuesday, June 28, 2016  4:54 PM

Many thanks for all your replies, I thought some of you clever people would have
answers!

I'm surprised that our code isn't failing due to a Tag_Check - presumably the
Constraint_Error that Randy mentions - as the callers of Define_Construct can't
have any knowledge of the derived type declared within the package body.

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


Questions? Ask the ACAA Technical Agent