AI22-0109-1

!standard 4.2(4/5)                                    25-03-24  AI22-0109-1/03

!class Binding Interpretation 24-06-27

!status Amendment 1-2022  24-07-18

!status WG9 Approved 25-07-18

!status WG9 Approved 24-10-10

!status ARG Approved  8-0-0  24-07-18

!status work item 24-06-27

!status received 24-06-27

!submitter Tucker Taft

!priority Medium

!difficulty Easy

!qualifier Omission

!subject Using class-wide types with user-defined literals

!summary

User-defined literals are allowed in a context where the expected type is a class-wide type.

!issue

Currently user-defined literals require a specific expected type with an appropriate *_Literal aspect defined. However, the *_Literal aspects are nonoverridable, implying they are inherited by descendants as needed by dispatching, so it clearly was intended to allow a class-wide type as the expected type rather than a specific type.

So for example:

     type Big_Int is tagged private
        with Integer_Literal => From_String;
     function "+" (Left, Right : Big_Int) return Big_Int;
     function From_String (Val : String) return Big_Int;
     procedure Print(V : Big_Int'Class);

     ...

      X : Big_Int'Class := ...
  begin
      X := 456;
      Print(X + 123);

 

Should this program be legal, where in the assignment statement 456 has an expected type of Big_Int'Class, and in the Print statement we essentially interpret 123 as a tag-indeterminate expression? (Yes.)

!recommendation

There is already an equivalence defined such that a literal in an appropriate context is treated like a call on the associated conversion function (From_String in this case). Hence, all we need to ensure is that the Name Resolution rules allow a user-defined literal in the same contexts where the equivalent function call would be allowed. This includes contexts where the expected type is class-wide, and in cases where a tag-indeterminate expression is permitted.

!wording

Modify 4.2(4/5):

The expected type for a primary that is a string_literal shall be a single string type or {a descendant of }a type with a specified String_Literal aspect (see 4.2.1). In either case, the string_literal is interpreted to be of its expected type. If the expected type of an integer literal is {a descendant of }a type with a specified Integer_Literal aspect (see 4.2.1), the literal is interpreted to be of its expected type; otherwise it is interpreted to be of type universal_integer. If the expected type of a real literal is {a descendant of }a type with a specified Real_Literal aspect (see 4.2.1), it is interpreted to be of its expected type; otherwise, it is interpreted to be of type universal_real.

!discussion

Because the *_Literal aspects are nonoverridable, they are well defined for all descendants, including class-wide types. The equivalence defined in 4.2.1 with a call on a specified conversion function ensures that the appropriate tag-indeterminate and dispatching rules apply, so the only required fix is to allow the expected type to be the class-wide type in addition to the specific type. We simply add the qualifier "a descendant of" which includes all interesting types, including class-wide types when the root type is tagged.

There is an additional use case involving class-wide types that we are not directly addressing here. That case is where the conversion function (such as From_String in the !issue) returns a class-wide type rather than a specific type. One could imagine an Integer/ Real/ String_Literal'Class aspect for such a case, where the associated conversion function has the same parameter(s), but returns the class-wide type instead of the specific type. This could be useful when attempting to construct values of a heterogeneous data structure, and the operations doing the constructing would take values of such a class-wide type.

For example:

    type Obj is tagged private
      with Integer_Literal'Class => From_String,
           Real_Literal'Class => From_String,
           String_Literal'Class => From_WW_String,
           Aggregate'Class => (Empty => Empty, Add_Named => Add_Named);
        -- These aspects are not legal Ada!

    function From_String(Str : String) return Obj'Class;
    function From_String(Num, Den : String) return Obj'Class;
    function From_WW_String(WWStr : Wide_Wide_String) return Obj'Class;

    function Empty return Obj'Class;
    procedure Add_Named
      (Agg : in out Obj'Class; Key : String; Elem : Obj'Class);

-- This would allow (heterogeneous) container aggregates such as:

   X : Obj'Class := ["Name" => "George", "Age" => 42, "Weight" => 165.4,
                      "Address" =>
                         ["Street" => "1 President Way",
                          "City" => "Mt. Vernon", Zip => 12304]];

 

Such a feature clearly was not anticipated in the original user-defined literal design, so we are not addressing this usage case here (for which we are just correcting the original rules to match the intent.

Moreover, all of the above can be more directly accomplished with no new Ada feature by defining a private type whose full type is a variant record, or an access-to-class-wide type, or an indefinite holder, or some combination thereof. Chances are that this heterogeneous structure would be represented using something like such a type, rather than directly using class-wide components, since Ada doesn't support structures with class-wide components. Hence, we conclude that there is already sufficient support for constructing such a heterogeneous structure using a combination of container aggregates and user-defined literals, given an appropriate private type whose full type handles the required heterogeneity.

!example

See the !issue for an example.

!corrigendum 4.2(4/5)

@drepl

The expected type for a @fa{primary} that is a @fa{string_literal} shall be a single string type or a type with a specified String_Literal aspect (see @ref{4.2.1}). In either case, the @fa{string_literal} is interpreted to be of its expected type. If the expected type of an integer literal is a type with a specified Integer_Literal aspect (see @ref{4.2.1}), the literal is interpreted to be of its expected type; otherwise it is interpreted to be of type @i{universal_integer}. If the expected type of a real literal is a type with a specified Real_Literal aspect (see @ref{4.2.1}), it is interpreted to be of its expected type; otherwise, it is interpreted to be of type @i{universal_real}.

@dby

The expected type for a @fa{primary} that is a @fa{string_literal} shall be a single string type or a descendant of a type with a specified String_Literal aspect (see @ref{4.2.1}). In either case, the @fa{string_literal} is interpreted to be of its expected type. If the expected type of an integer literal is a descendant of a type with a specified Integer_Literal aspect (see @ref{4.2.1}), the literal is interpreted to be of its expected type; otherwise it is interpreted to be of type @i{universal_integer}. If the expected type of a real literal is a descendant of a type with a specified Real_Literal aspect (see @ref{4.2.1}), it is interpreted to be of its expected type; otherwise, it is interpreted to be of type @i{universal_real}.

!ACATS test

An ACATS test incorporating a pattern such as that given in the !issue would be needed to test this new Name Resolution rule.

!appendix

This is related to ARG GitHub issue #5