• Assignment access type with discriminants

    From Dmitry A. Kazakov@21:1/5 to All on Wed Mar 22 10:19:28 2023
    I stumbled on a curious fact.

    The value of an object with a discriminant can be changed to a value
    with a different discriminant if the type's discriminants are defaulted.

    Right?

    Wrong! Not through an access type!

    procedure Test is
    type F is (F1, F2, F3);
    type Foo (K : F := F1) is record
    case K is
    when F1 =>
    X1 : Integer;
    when F2 =>
    X2 : Float;
    when F3 =>
    X3 : String (1..2);
    end case;
    end record;
    type Foo_Ptr is access all Foo;
    X : aliased Foo;
    P : Foo_Ptr := X'Access;
    begin
    X := (F2, 1.0); -- OK
    P.all := (F1, 3); -- Error!
    end Test;

    Is this a compiler bug or intentional language design? Any language lawyers?

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From =?UTF-8?Q?Bj=c3=b6rn_Lundin?=@21:1/5 to Dmitry A. Kazakov on Wed Mar 22 10:31:58 2023
    On 2023-03-22 10:19, Dmitry A. Kazakov wrote:
    I stumbled on a curious fact.
    ....
    Is this a compiler bug or intentional language design? Any language
    lawyers?


    I get
    Execution of ./test terminated by unhandled exception
    raised CONSTRAINT_ERROR : test.adb:18 discriminant check failed
    Call stack traceback locations:
    0x402c33 0x402b27 0x7f335b5cfd8e 0x7f335b5cfe3e 0x402b63 0xfffffffffffffffe

    bnl@hp-t510:/usr2$ gnatls -v
    GNATLS Pro 22.2 (20220605-103)

    Linux 64bit - ubuntu 22.04

    So it is (also) present on that platform at least


    --
    /Björn

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From G.B.@21:1/5 to Dmitry A. Kazakov on Wed Mar 22 15:10:44 2023
    On 22.03.23 10:19, Dmitry A. Kazakov wrote:
    I stumbled on a curious fact.

    The value of an object with a discriminant can be changed to a value with a different discriminant if the type's discriminants are defaulted.

    Right?

    Wrong! Not through an access type!

    procedure Test is
       type F is (F1, F2, F3);
       type Foo (K : F := F1) is record
          case K is
             when F1 =>
                X1 : Integer;
             when F2 =>
                X2 : Float;
             when F3 =>
                X3 : String (1..2);
          end case;
       end record;
       type Foo_Ptr is access all Foo;
       X : aliased Foo;
       P : Foo_Ptr := X'Access;
    begin
       X := (F2, 1.0);   -- OK
       P.all := (F1, 3); -- Error!
    end Test;

    Some experiments point at the general access type.

    type Foo_Ptr is access Foo; -- sans `all`
    X : Foo;
    P : Foo_Ptr := new Foo;
    type Foo1 is new Foo_Ptr (K => F1);
    begin
    X := (F2, 1.0); -- OK
    P.all := (F1, 3); -- _no_ Error!
    Foo1 (P).all := (F1, 3);
    end Test;

    (Doesn't rejection for general access types seem reasonable
    if assignment would otherwise require adjusting the storage
    layout of a variable, including all access paths to components?
    Just guessing.)

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to G.B. on Thu Mar 23 12:51:03 2023
    On 2023-03-22 15:10, G.B. wrote:

    Some experiments point at the general access type.

       type Foo_Ptr is access Foo; -- sans `all`
       X : Foo;
       P : Foo_Ptr := new Foo;
       type Foo1 is new Foo_Ptr (K => F1);
    begin
       X := (F2, 1.0);   -- OK
       P.all := (F1, 3); -- _no_ Error!
       Foo1 (P).all := (F1, 3);
    end Test;

    You get no error because you do not change the discriminant. Change your
    code to:

    P.all := (F2, 1.0); -- Error!

    (Doesn't rejection for general access types seem reasonable
    if assignment would otherwise require adjusting the storage
    layout of a variable, including all access paths to components?
    Just guessing.)

    I guess that an implementation must allocate memory for any value unless
    you constraint the discriminants in a subtype. But I am not a language
    lawyer to judge.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From AdaMagica@21:1/5 to All on Thu Mar 23 09:53:23 2023
    I do hope, this answers the question:

    3.10(14/3) … The first subtype of a type defined by … an access_to_object_definition is unconstrained if the designated subtype is an ... discriminated subtype; otherwise, it is constrained.
    4.8(6/3) If the designated type is composite, then … the created object is constrained by its initial value (even if the designated subtype is unconstrained with defaults).

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From J-P. Rosen@21:1/5 to All on Thu Mar 23 18:04:36 2023
    Le 22/03/2023 à 10:19, Dmitry A. Kazakov a écrit :
    I stumbled on a curious fact.

    The value of an object with a discriminant can be changed to a value
    with a different discriminant if the type's discriminants are defaulted.

    Right?

    Wrong! Not through an access type!

    (...)
    Is this a compiler bug or intentional language design? Any language
    lawyers?

    An access value is always constrained by its initial value; this is
    necessary because of constrained access subtypes. Here is a slightly
    modified version of your example:

    procedure Test is
    type F is (F1, F2, F3);

    type Foo (K : F := F1) is record
    case K is
    when F1 =>
    X1 : Integer;
    when F2 =>
    X2 : Float;
    when F3 =>
    X3 : String (1..2);
    end case;
    end record;
    type Foo_Ptr is access all Foo;
    type Foo_Ptr2 is access Foo;
    X : aliased Foo;
    P : Foo_Ptr := X'Access;
    PF2: Foo_PTR2 (F2);
    begin
    X := (F2, 1.0); -- OK
    PF2 := new Foo (F2);
    P := PF2.all'Access;
    P.all := (F1, 3); -- Error!
    end Test;

    Without this rule, PF2.all would now designate a value whose
    discriminant is F1!
    --
    J-P. Rosen
    Adalog
    2 rue du Docteur Lombard, 92441 Issy-les-Moulineaux CEDEX
    https://www.adalog.fr https://www.adacontrol.fr

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Niklas Holsti@21:1/5 to AdaMagica on Thu Mar 23 20:09:21 2023
    On 2023-03-23 18:53, AdaMagica wrote:
    I do hope, this answers the question:

    3.10(14/3) … The first subtype of a type defined by … an access_to_object_definition is unconstrained if the designated
    subtype is an ... discriminated subtype; otherwise, it is
    constrained.

    What do you infer from this, relating to Dmitry's original example code
    and the error? The "first subtype .. defined" here is the access
    subtype, and I don't see how that affects an assignment /via/ this
    access subtype to the accessed object.

    (It is not clear to me how an access subtype that is constrained differs
    from one that is unconstrained. Can someone clarify?)


    4.8(6/3) If the designated type is composite, then … the created
    object is constrained by its initial value (even if the designated
    subtype is unconstrained with defaults).


    That rule applies to objects created by allocators, but the original
    example code has no allocators (some later variants do). The object in
    question is created by a declaration (which includes the "aliased"
    keyword), not by an allocator.

    Also, AARM 3.10 contains the following notes on "Wording Changes from
    Ada 1995":

    26.d/2 {AI95-00363-01} Most unconstrained aliased objects with defaulted discriminants are no longer constrained by their initial values. [...]

    26.k/2 {AI95-00363-01} The rules about aliased objects being constrained
    by their initial values now apply only to allocated objects, and thus
    have been moved to 4.8, “Allocators”.

    This seems to mean that aliased objects created by declarations are
    /not/ constrained by the initial value, so it should be possible to
    change the discriminant. This seems to be a change from Ada 95 to Ada
    2005. I don't see why that change could not be done via an access to the object.

    I added some output to Dmitry's original code, with this result:

    X'Constrained = FALSE
    P'Constrained = TRUE
    P.all'Constrained = TRUE

    The first two values of 'Constrained (for X and P) are as expected by
    the RM rules, and the third value (for P.all) is consistent with the
    error, and seems valid for Ada 95, but the wording change quoted above
    suggests that it is wrong for Ada 2005 and later. This leads me to
    suspect that GNAT has not been fully updated for this RM change, so it
    would be a GNAT bug. Still, the addition of

    subtype Foo2_Ptr is Foo_Ptr (K => F2);

    to Dmitry's original example provokes this error message:

    fuf.adb:16:24: access subtype of general access type not allowed
    fuf.adb:16:24: discriminants have defaults

    which suggests that at least this part of AI95-00363 has been
    implemented, as noted in AARM 3.10:

    14.b/2 Reason: {AI95-00363-01} [...] Constraints are not allowed on
    general access-to-unconstrained discriminated types if the type has
    defaults for its discriminants [...]

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Niklas Holsti@21:1/5 to J-P. Rosen on Thu Mar 23 20:55:48 2023
    On 2023-03-23 19:04, J-P. Rosen wrote:
    Le 22/03/2023 à 10:19, Dmitry A. Kazakov a écrit :
    I stumbled on a curious fact.

    The value of an object with a discriminant can be changed to a value
    with a different discriminant if the type's discriminants are defaulted.

    Right?

    Wrong! Not through an access type!

    (...)
    Is this a compiler bug or intentional language design? Any language
    lawyers?

    An access value is always constrained by its initial value; this is
    necessary because of constrained access subtypes.


    But constrained access subtypes are not allowed for general access types
    like Foo_Ptr in the example.


    Here is a slightly
    modified version of your example:

    procedure Test is
       type F is (F1, F2, F3);

       type Foo (K : F := F1) is record
          case K is
             when F1 =>
                X1 : Integer;
             when F2 =>
                X2 : Float;
             when F3 =>
                X3 : String (1..2);
          end case;
       end record;
       type Foo_Ptr is access all Foo;
       type Foo_Ptr2 is access Foo;
       X : aliased Foo;
       P : Foo_Ptr := X'Access;
       PF2: Foo_PTR2 (F2);
    begin
       X := (F2, 1.0);   -- OK
       PF2 := new Foo (F2);
       P := PF2.all'Access;
       P.all := (F1, 3); -- Error!
    end Test;

    Without this rule, PF2.all would now designate a value whose
    discriminant is F1!


    This error is understandable and valid, because now P.all is PF2.all
    which is an allocated object and therefore constrained by its initial
    value with K = F2.

    But why should the same apply when P designates X, which is
    unconstrained? Is it just an optimization (in the RM) so that a general
    access value does not have to carry around a flag showing whether its designated object is constrained or unconstrained?

    Perhaps it would be better to make the assignment P := PF2.all'Access
    illegal, because it in effect converts a constrained access value (PF2)
    to an unconstrained access subtype (P), and so in some sense violates
    the prohibition of constrained subtypes of general access types.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Dmitry A. Kazakov@21:1/5 to Niklas Holsti on Thu Mar 23 20:53:02 2023
    On 2023-03-23 19:55, Niklas Holsti wrote:

    Perhaps it would be better to make the assignment P := PF2.all'Access illegal, because it in effect converts a constrained access value (PF2)
    to an unconstrained access subtype (P), and so in some sense violates
    the prohibition of constrained subtypes of general access types.

    Yes this is substitutability violation. Such cases never go without a punishment. In this case it is an implementation overhead.

    Consider:

    procedure Set (Destination : in out Foo; Source : Foo) is
    begin
    Destination := Source;
    end Set;

    The compiler cannot implement Set in a natural way, because Destination
    might be arbitrarily constrained by the caller. E.g. when the actual for Destination is P.all. So, the constraint must be passed together with
    the actual. A quite burden.

    --
    Regards,
    Dmitry A. Kazakov
    http://www.dmitry-kazakov.de

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From J-P. Rosen@21:1/5 to All on Fri Mar 24 10:41:20 2023
    Le 23/03/2023 à 19:55, Niklas Holsti a écrit :

    Here is a slightly modified version of your example:

    procedure Test is
    type F is (F1, F2, F3);

    type Foo (K : F := F1) is record
    case K is
    when F1 =>
    X1 : Integer;
    when F2 =>
    X2 : Float;
    when F3 =>
    X3 : String (1..2);
    end case;
    end record;
    type Foo_Ptr is access all Foo;
    type Foo_Ptr2 is access Foo;
    X : aliased Foo;
    P : Foo_Ptr := X'Access;
    PF2: Foo_PTR2 (F2);
    begin
    X := (F2, 1.0); -- OK
    PF2 := new Foo (F2);
    P := PF2.all'Access;
    P.all := (F1, 3); -- Error!
    end Test;

    Without this rule, PF2.all would now designate a value whose
    discriminant is F1!


    This error is understandable and valid, because now P.all is PF2.all
    which is an allocated object and therefore constrained by its initial
    value with K = F2.

    But why should the same apply when P designates X, which is
    unconstrained? Is it just an optimization (in the RM) so that a
    general access value does not have to carry around a flag showing
    whether its designated object is constrained or unconstrained?

    I didn't dig in the RM in all details, but I think this comes from the
    fact that being constrained (always) is a property of the pointer (more precisely, its subtype), not of the pointed-at object.

    --
    J-P. Rosen
    Adalog
    2 rue du Docteur Lombard, 92441 Issy-les-Moulineaux CEDEX
    https://www.adalog.fr https://www.adacontrol.fr

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Randy Brukardt@21:1/5 to J-P. Rosen on Sat Mar 25 03:51:07 2023
    The rule is question is 4.1(9/3):

    If the type of the name in a dereference is some access-to-object type T,
    then the dereference denotes a view of an object, the nominal subtype of the view being the designated subtype of T. If the designated subtype has unconstrained discriminants, the (actual) subtype of the view is constrained
    by the values of the discriminants of the designated object, except when
    there is a partial view of the type of the designated subtype that does not have discriminants, in which case the dereference is not constrained by its discriminant values.

    We have to do that so as otherwise the access value would have to carry a designation as to whether the object was allocated or not.

    -------

    This rule was inherited from Ada 83.

    IMHO, this rule is stupid. It's even more stupid with the hole for types
    that have partial views without discriminants. The *proper* solution is to
    get rid of the rarely used and mostly useless access constraints, and then
    have no extra restrictions on access values. But that's considered too incompatible.

    Randy.


    "J-P. Rosen" <rosen@adalog.fr> wrote in message news:tvjr7l$1js79$1@dont-email.me...
    Le 23/03/2023 19:55, Niklas Holsti a crit :

    Here is a slightly modified version of your example:

    procedure Test is
    type F is (F1, F2, F3);

    type Foo (K : F := F1) is record
    case K is
    when F1 =>
    X1 : Integer;
    when F2 =>
    X2 : Float;
    when F3 =>
    X3 : String (1..2);
    end case;
    end record;
    type Foo_Ptr is access all Foo;
    type Foo_Ptr2 is access Foo;
    X : aliased Foo;
    P : Foo_Ptr := X'Access;
    PF2: Foo_PTR2 (F2);
    begin
    X := (F2, 1.0); -- OK
    PF2 := new Foo (F2);
    P := PF2.all'Access;
    P.all := (F1, 3); -- Error!
    end Test;

    Without this rule, PF2.all would now designate a value whose
    discriminant is F1!


    This error is understandable and valid, because now P.all is PF2.all
    which is an allocated object and therefore constrained by its initial
    value with K = F2.

    But why should the same apply when P designates X, which is
    unconstrained? Is it just an optimization (in the RM) so that a
    general access value does not have to carry around a flag showing
    whether its designated object is constrained or unconstrained?

    I didn't dig in the RM in all details, but I think this comes from the
    fact that being constrained (always) is a property of the pointer (more precisely, its subtype), not of the pointed-at object.

    --
    J-P. Rosen
    Adalog
    2 rue du Docteur Lombard, 92441 Issy-les-Moulineaux CEDEX https://www.adalog.fr https://www.adacontrol.fr


    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)