Version 1.2 of ai12s/ai12-0435-1.txt

Unformatted version of ai12s/ai12-0435-1.txt version 1.2
Other versions for file ai12s/ai12-0435-1.txt

!standard 4.10(6/5)          21-05-29 AI12-0435-1/01
!standard 4.10(15/5)
!standard 4.10(26/5)
!standard 13.13.2(9.1/5)
!class Amendment 21-05-29
!status work item 21-05-29
!status received 21-05-29
!priority Low
!difficulty Easy
!subject Fixups from WG 9 Issue #143 - Image and streams
!summary
(1) Exclude null extensions from the Implementation Advice about type extensions.
(2) Define the subtype of the parameters to the default Put_Image implementations similarly to that for the stream-oriented attributes.
(3) The subprogram specified for Put_Image cannot be abstract.
(4) Stream-oriented attributes are implicitly composed.
!problem
(1) It seems desirable that if we have a tagged specific noninterface type T1 and a specific type T2 which is a proper descendant of T1 (i.e., a descendant of T1 other than T1 itself), and if T2 is "the same" as T1 ("the same" meaning that no new components are defined nor Put_Image redefined for T2), then for any value X of T2 it should be the case that T2'Image (X) = T1'Image (T1 (X)).
This is not true when T2 is a null extension of T1. If T1'Image (X) = "(Some Image Text)", then (according to the implementation advice) T2'Image (T2 (X)) should equal something like "((Some Image Text) with NULL)".
(2) Are rules for determining the parameter subtypes for T'Put_Image procedures sufficiently well-defined?
For example, is the "others" aggregate in the following code legal?
type T is array (1 .. 10) of Integer; -- first subtype is constrained
procedure Foo (Buff : in out Ada.Strings.Text_Buffers.Root_Buffer_Type'Class) is begin
T'Put_Image (Buff, (others => 123)); -- legal?
end Foo;
(3) For streams, we have a rule The subprogram name given in such an attribute_definition_clause or aspect_specification shall statically denote a subprogram that is not an abstract subprogram.
We don't have an similar rule for Put_Image. Since we want the image of any type (including abstract types) to be well-defined, this seems to be an oversight.
(4) we have rules in 13.1.1 and 12.5.1 that apply to aspects that are "implicitly composed", but we have no explicit statements anywhere in the manual that any particular aspects are "implicitly composed". This should be stated explicitly for Put_Image and for the aspects corresponding to the stream-oriented attributes.
!proposal
(See Summary.)
!wording
(1) Modify 4.10(6/5):
For an untagged derived type{, or a null extension}, the default implementation of T'Put_Image invokes the Put_Image for its parent type on a conversion of the parameter of type T to the parent type.
Modify 4.10(15/5):
For a {nonnull} type extension, the default implementation of T'Put_Image depends on whether there exists a noninterface ancestor of T (other than T itself) for which the Put_Image aspect has been directly specified. If so, then T'Put_Image will generate an image based on extension aggregate syntax where the ancestor type of the extension aggregate is the nearest ancestor type whose Put_Image aspect has been specified. If no such ancestor exists, then the default implementation of T'Put_Image is the same as described below for a nonderived record type.
(2) and (3) Add after 4.10(26/5):
In the parameter_and_result_profile for the default implementation of Put_Image, the subtype of the Arg parameter is the base subtype of T if T is a scalar type, and the first subtype otherwise. For an aspect_specification or attribute_definition_clause specifying Put_Image, the subprogram name shall denote a nonabstract procedure whose second parameter is either of the first subtype of T, or as an option when T is scalar, the base subtype of T.
(4) Modify 13.13.2(9.1/5):
{For derived types, the default implementations of the Write and Read attributes are not inherited, but are rather implicitly composed.} For type extensions, the Write or Read attribute for the parent type is called, followed by the Write or Read attribute of each component of the extension part, in canonical order. For a limited type extension, if the attribute of the parent type or any progenitor type of T is available anywhere within the immediate scope of T, and the attribute of the parent type or the type of any of the extension components is not available at the freezing point of T, then the attribute of T shall be directly specified. For untagged derived types, the Write (resp. Read) attribute invokes the corresponding attribute of the parent type, if the attribute is available for the parent type.
[Editor's complaint: This only covers Read and Write. What about Input and Output, and the class-wide versions of all of these?? I would have expected a general statement at the top of 13.13.2 instead of this.]
!discussion
(1) Its unclear to the Editor whether this is really a principle that we want to support. OTOH, he has consistently objected to treating null extensions specially and has lost that argument consistently.
(2) We copy the definition of stream-oriented attributes for Put_Image.
(3) We never want Put_Image to be uncallable, so abstract routines should be banned. There's no problem with null procedures (even though they don't seem very useful in this context), so we allow them.
(4) 4.10(4/5) states that Put_Image is implicitly composed. We don't have such wording for stream-oriented attributes, though.
!ASIS
No ASIS effect.
!ACATS test
ACATS B-Tests should be constructed to test the subtypes of the parameters of Put_Image and to check that abstract subprograms cannot be specified.
ACATS C-Tests should be constructed to check that null extensions do not affect the image.
!appendix

From WG 9 review issue #143 - Steve Baird

It seems desirable that if we have a tagged specific noninterface type
T1 and a specific type T2 which is a proper descendant of T1 (i.e., a
descendant of T1 other than T1 itself), and if T2 is "the same" as T1
("the same" in a sense I'll describe in a moment), then for any value
X of T2 it should be the case that T2'Image (X) = T1'Image (T1 (X)).

When I say that T1 and T2 are "the same", I mean that neither T2 nor any
other proper descendant of T1 that is also an ancestor of T2 either

    defines any new components (including discriminants); or
    has an explicitly-specified Put_Image aspect.

This seems to generally be true, but (unfortunately) not always.
Consider the case where a type T1 has a user-defined Put_Image aspect
and a type T2 is a null extension of T1, declared as
type T2 is new T1 with null record;

The implementation advice about how to handle this case does not have
a special case for null extension (or a null extension of a null extension,
etc.). So if T1'Image (X) = "(Some Image Text)", then (according to that
advice) T2'Image (T2 (X)) should equal something like
"((Some Image Text) with NULL)".
That's not what we want.

===

Are rules for determining the parameter subtypes for T'Put_Image
procedures sufficiently well-defined? [Tuck raised this question.]

For example, is the "others" aggregate in the following code legal?

type T is array (1 .. 10) of Integer; -- first subtype is constrained

procedure Foo
(Buff : in out Ada.Strings.Text_Buffers.Root_Buffer_Type'Class) is
begin
T'Put_Image (Buff, (others => 123)); -- legal?
end Foo;

===

For streams, we have a rule
The subprogram name given in such an attribute_definition_clause or
aspect_specification shall statically denote a subprogram that is not
an abstract subprogram.

The word "abstract" does not appear anywhere in 4.10; that seems like a
problem. We want to prohibit something like

type T is tagged record ... end record with Put_Image => Foo;
procedure Foo () is abstract;

Assuming such a rule, would a (redundant) note be useful pointing out
that Some_Abstract_Type'Put_Image is never an abstract procedure?

===

On a related note, we have rules in 8.6 that apply to aspects that are
"implicitly composed", but we have no explicit statements anywhere in
the manual that any particular aspects are "implicitly composed".
This should be stated explicitly for Put_Image and for the
aspects corresponding to the stream-oriented attributes.

[Tucker Taft replied:]

As far as null extensions, I would agree with Steve we should suppress them.
So I would suggest:

Modify 4.10(6/5):

   For an untagged derived type{, or a null extension}, the default 
   implementation of T'Put_Image invokes the Put_Image for its parent type
   on a conversion of the parameter of type T to the parent type.

Modify 4.10(15/5):

   For a {nonnull} type extension, the default implementation of T'Put_Image 
   depends on whether there exists a noninterface ancestor of T (other than T
   itself) for which the Put_Image aspect has been directly specified. If so,
   then T'Put_Image will generate an image based on extension aggregate syntax
   where the ancestor type of the extension aggregate is the nearest ancestor
   type whose Put_Image aspect has been specified. If no such ancestor exists,
   then the default implementation of T'Put_Image is the same as described
   below for a nonderived record type.

---

As far as the subtype of the parameters, for stream attributes we have:
13.13.2(50/3-51/3):

   In the parameter_and_result_profiles for the default implementations of the 
   stream-oriented attributes, the subtype of the Item parameter is the base 
   subtype of T if T is a scalar type, and the first subtype otherwise. The
   same rule applies to the result of the Input attribute.

   For an attribute_definition_clause specifying one of these attributes, the 
   subtype of the Item parameter shall be the first subtype or the base
   subtype if scalar, and the first subtype if not scalar. The same rule
   applies to the result of the Input function.

We presumably need something analogous for Put_Image. As far as the use of 
"abstract" procedures for Put_Image, I agree we want to disallow them. All
types have Put_Image, including all abstract types, and we want to be able to
presume it is a non-abstract procedure in generics, etc. And the implicitly
composed Put_Image for a type extension of an abstract type will invoke the
parent's operation, so again, we want to be sure it is nonabstract. Hence,
I recommend we add a paragraph to specify both the parameter subtype, and the
requirement to be nonabstract:

Add after 4.10(26/5):

  In the parameter_and_result_profile for the default implementation of 
  Put_Image, the subtype of the Arg parameter is the base subtype of T if T
  is a scalar type, and the first subtype otherwise. For an 
  aspect_specification or attribute_definition_clause specifying Put_Image,
  the subprogram name shall denote a nonabstract procedure whose second
  parameter is either of the first subtype of T, or as an option when T
  is scalar, the base subtype of T.

---

As far as "implicitly composed" we do say something in 4.10(4/5) about 
Put_Image, but there is nothing about how the stream attributes are
implicitly composed. So I would suggest:

Modify 13.13.2(9.1/5):

  {For derived types, the default implementations of the Write and Read 
  attributes are not inherited, but are rather implicitly composed.} For
  type extensions, the Write or Read attribute for the parent type is
  called, followed by the Write or Read attribute of each component of the
  extension part, in canonical order. For a limited type extension, if the
  attribute of the parent type or any progenitor type of T is available
  anywhere within the immediate scope of T, and the attribute of the parent
  type or the type of any of the extension components is not available at
  the freezing point of T, then the attribute of T shall be directly
  specified. For untagged derived types, the Write (resp. Read) attribute
  invokes the corresponding attribute of the parent type, if the attribute
  is available for the parent type.

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

From: Tucker Taft
Sent: Sunday, May 30, 2021  9:00 AM

Randy recently posted AIs for our upcoming review of WG9 comments.  He asked
me to comment on the Put_Image/Stream attribute fixups.  Here are two of the
four issues for which I had a comment:

>(1) Modify 4.10(6/5):
>   For an untagged derived type{, or a null extension}, the default
>   implementation of T'Put_Image invokes the Put_Image for its parent type
>   on a conversion of the parameter of type T to the parent type.
>Modify 4.10(15/5):
>   For a {nonnull} type extension, the default implementation of T'Put_Image
>   depends on whether there exists a noninterface ancestor of T (other than T
>   itself) for which the Put_Image aspect has been directly specified. If so,
>   then T'Put_Image will generate an image based on extension aggregate syntax
>   where the ancestor type of the extension aggregate is the nearest ancestor
>   type whose Put_Image aspect has been specified. If no such ancestor exists,
>   then the default implementation of T'Put_Image is the same as described
>   below for a nonderived record type.
>...
>(4) Modify 13.13.2(9.1/5):
>  {For derived types, the default implementations of the Write and Read
>  attributes are not inherited, but are rather implicitly composed.} For
>  type extensions, the Write or Read attribute for the parent type is
>  called, followed by the Write or Read attribute of each component of the
>  extension part, in canonical order. For a limited type extension, if the
>  attribute of the parent type or any progenitor type of T is available
>  anywhere within the immediate scope of T, and the attribute of the parent
>  type or the type of any of the extension components is not available at
>  the freezing point of T, then the attribute of T shall be directly
>  specified. For untagged derived types, the Write (resp. Read) attribute
>  invokes the corresponding attribute of the parent type, if the attribute
>  is available for the parent type.
>[Editor's complaint: This only covers Read and Write. What about Input
>and Output, and the class-wide versions of all of these?? I would have expected
>a general statement at the top of 13.13.2 instead of this.]

I think we could instead put a general statement at the end of paragraph 
13.13.2(38/4):

Modify 13.13.2(38/4):
The stream-oriented attributes may be specified for any type via an 
attribute_definition_clause. [Alternatively, each of the specific 
stream-oriented attributes may be specified using an aspect_specification
on any type_declaration, with the aspect name being the corresponding
attribute name.] Each of the class-wide stream-oriented attributes may be
specified using an aspect_specification for a tagged type T using the name
of the stream-oriented attribute followed by 'Class; such class-wide aspects
do not apply to other descendants of T. {If not directly specified, a default
implementation of a stream-oriented attribute is implicitly composed for a
nonlimited type, and for certain limited types, as defined above.}
 
>!discussion
>(1) It's unclear to the Editor whether this is really a principle that we
>want to support. OTOH, he has consistently objected to treating null
>extensions specially and has lost that argument consistently.

As far as the proposal about null extensions identified as issue (1) above,
here is the logic:  If you have a tagged type T1 and its null extension T2,
the default implementation of the Put_Image attributes for T1 and T2 will be
identical, since they will each be displayed as a record aggregate, and they
have the same number of components.  Similarly, if the parent of T1 (or some
more remote ancestor) has a Put_Image attribute specified (let's call it T0),
then the implicitly composed Put_Image attributes for T1 and T2 will again be
identical, since they have the same set of additional components relative to
T0.  So it makes sense that in general the implicitly composed Put_Image
attribute for T2 should be the same as whatever T1'Put_Image produces. Another
reason for null extensions to directly reuse the Put_Image of their parent is
that a standard idiom for making a tagged type exported by a generic
instantiation directly visible, is by using a null extension, so it would be
annoying to instantiate a generic which provides a Put_Image, and then
suddenly find a "(... with Null)" wrapper appearing around all of the 'Image
results.  Finally, we have gone out of our way to make (essentially) all
primitive operations of a type be inherited "as is" by a null extension,
including functions with controlling results, which would normally require
overriding.  So "inheriting" Put_Image (via implicit composition) as is,
makes sense as well.
...

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

From: Randy Brukardt
Sent: Wednesday, June 2, 2021  9:43 PM

>>	!discussion
>>	(1) It's unclear to the Editor whether this is really a principle that we
>>	want to support. OTOH, he has consistently objected to treating null
>>	extensions specially and has lost that argument consistently.

>	As far as the proposal about null extensions identified as issue (1) above,
> here is the logic:  If you have a tagged type T1 and its null extension T2, the
> default implementation of the Put_Image attributes for T1 and T2 will be
> identical, since they will each be displayed as a record aggregate, and they
> have the same number of components.  Similarly, if the parent of T1 (or some
> more remote ancestor) has a Put_Image attribute specified (let's call it T0), 
> then the implicitly composed Put_Image attributes for T1 and T2 will again be
> identical, since they have the same set of additional components relative to
> T0.  So it makes sense that in general the implicitly composed Put_Image 
> attribute for T2 should be the same as whatever T1'Put_Image produces.

This seems like justifying a glitch by doubling down on the glitch: I see no
reason that T1 and T2 necessarily have the same Image, by default or any other
reason. Perhaps the advice makes that the case, but that would seem to be
accidental rather than any sort of intentional result.

> Another reason for null extensions to directly reuse the Put_Image of their 
> parent is that a standard idiom for making a tagged type exported by a 
> generic instantiation directly visible, is by using a null extension, so
> it would be annoying to instantiate a generic which provides a Put_Image,
> and then suddenly find a "(... with Null)" wrapper appearing around all of
> the 'Image results.

This "idiom" is evil, and needs to be replaced by something (anything!) else.
One *never* wants to introduce a type unless one really needs it to be
different in an apples-and-oranges sense. If the Image was to increase
encouragement to avoid this idiom (and perhaps provide a replacement, as in
deferred AI12-0229-1), that would seem to be a good thing!

> Finally, we have gone out of our way to make (essentially) all primitive 
> operations of a type be inherited "as is" by a null extension, including
> functions with controlling results, which would normally require
> overriding. So "inheriting" Put_Image (via implicit composition) as is, 
> makes sense as well.

This is also evil, at least for the likely uses that I have for null
extensions. Most likely, a null extension is just a placeholder for a type
that eventually will gain extension components (it's unlikely that one can
usefully replace operations of a type without some additional/different data).
In this case, getting a clean compiler for a null extension tells one nothing
about the requirements on the type in the actual program; one has to declare
dummy components in order to do that. Which brings up all of the problems of
dummy components. I would not object to extending this capability to other
extensions (those with extension components with appropriate defaults), as
that would mitigate the problem -- but of course no extension to Image would
make sense in that case. (AI12-0083-1, also deferred, proposed something on
this line to make classic mix-in generics more general.)

Anyway, as I stated at the outset, I've consistently lost this argument, so
I'll go crawl back under my rock. :-)

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

Questions? Ask the ACAA Technical Agent