Version 1.5 of ai05s/ai05-0039-1.txt

Unformatted version of ai05s/ai05-0039-1.txt version 1.5
Other versions for file ai05s/ai05-0039-1.txt

!standard 13.3(4)          08-05-15 AI05-0039-1/04
!standard 13.3(6)
!standard 13.13.2(38/2)
!class binding interpretation 07-02-02
!status ARG Approved 8-0-1 06-11-10
!status work item 07-02-02
!status received 07-01-31
!priority Low
!difficulty Easy
!qualifier Omission
!subject User-defined stream attributes cannot be dynamic
!summary
The subprogram specified in an attribute_definition_clause for a stream attribute cannot be a dynamic expression.
!question
The only compile-time rules restricting the specification of a stream attribute are 13.3(4), 13.3(6), and 13.13.2(38/2). Indeed, AARM 13.3(4.c-d) states that dynamic names like Subprogram_Ptr.all denoting a subprogram can be used to specify a stream attribute.
However, these attributes require the subprogram to have a parameter of a type, and must be specified before the type is frozen. That greatly restricts what expressions can be legally written when specifying a stream attribute. Indeed, the elaboration and freezing rules combine to make anything but pathological expressions illegal.
AI05-0019 relaxed the freezing rules for subprograms, which would make more of these expressions legal. But even with those relaxations, only pathological cases (those that raise exceptions or that access subprograms already available directly) would be legal.
This can be implemented with wrappers. But wrappers cannot be generated until the parameter types are frozen, requiring a delay to later in the compilation unit. That's a lot of complication for something useless. Moreover, at least some existing compilers (IBM) do not support dynamic names here and have not had any complaints on the topic.
Should this be changed to require statically denoted subprograms? (Yes.)
!recommendation
(See Summary.)
!wording
Change the second sentence of 13.13.2(38/2) to:
The subprogram name given in such a clause shall {statically denote a subprogram that is not} [not denote] an abstract subprogram.
!discussion
The original freezing rules make most remotely interesting dynamic expressions illegal.
type My_Big_Int is range 0 .. 10000;
type Write_Ptr is access procedure (Stream : not null access Ada.Streams.Root_Stream_Type'Class; A : in My_Big_Int'Base);
procedure Good_Write (Stream : not null access Ada.Streams.Root_Stream_Type'Class; A : in My_Big_Int'Base);
WPtr : Write_Ptr := Good_Write'access; -- (1)
for My_Big_Int'Write use WPtr.all; -- (2).
The current rules freeze Good_Write, and the parameter types of Good_Write, at (1). That obviously includes My_Big_Int, and that makes (2) illegal.
If Good_Write'Access is replaced by null, (2) is legal, but of course it isn't useful as it must raise Constraint_Error.
The approved rules for AI05-0019 relax the freezing rules such that freezing a subprogram would not necessarily freeze the types of its parameters and result. But a subprogram call would freeze those types. That would allow the example above.
But it still could do nothing interesting. Even without any freezing on a call, initializing Write_Ptr with a function call would necessarily fail elaboration checks and thus would raise Program_Error. That's because Write_Ptr necessarily includes a parameter of the type, and thus any function returning it has to have its body later. The body could not occur before the freezing point of My_Big_Int, because a body freezes all types. Thus either the type would be frozen too soon, or Program_Error would be raised.
We could try to stream in the access value; but nothing in the language requires that to work, so depending on it is dubious.
So using a subprogram access type to specify a stream attribute is just a convoluted way to write something that could easily be written directly.
The only other such expression that has been proposed (remember that a parameter of the type is required, so that any subprograms that will be usable have to be declared locally or in a nested package) is another stream attribute of the same type:
for My_Big_Int'Write use My_Big_Int'Output;
But this isn't useful either. If 'Output is directly specified, then that same routine can easily be used for 'Write. The default semantics of 'Write are either the same as the default semantics for 'Output (which means this does nothing at all), or they are dubious for the operation (writing too much or too little). This doesn't seem useful, either.
Thus we conclude that this feature is useless and most examples are pathologies (raising predefined exceptions). When a feature is certain to raise an exception, we prefer a compile-time rule to accelerate the detection of the error. In this case, it is be possible to write cases that would be legal, but this clearly will take a non-zero implementation effort. We don't want implementers spending their time implementing pathologies, so we make it illegal.
This new rule would only be incompatible in the case of pathologies. And it freed AI05-0019 from having to consider the impact on stream attributes.
!corrigendum 13.13.2(38/2)
Replace the paragraph:
The stream-oriented attributes may be specified for any type via an attribute_definition_clause. The subprogram name given in such a clause shall not denote an abstract subprogram. Furthermore, if a stream-oriented attribute is specified for an interface type by an attribute_definition_clause, the subprogram name given in the clause shall statically denote a null procedure.
by:
The stream-oriented attributes may be specified for any type via an attribute_definition_clause. The subprogram name given in such a clause shall statically denote a subprogram that is not an abstract subprogram. Furthermore, if a stream-oriented attribute is specified for an interface type by an attribute_definition_clause, the subprogram name given in the clause shall statically denote a null procedure.
!ACATS test
There are no current ACATS tests for legality rules on user-defined stream attributes, so a B-Test covering all of these rules is needed. (Randy Brukardt has written one for RR Software; he should be convinced to submit his TestW96 to the ACATS.)
!appendix

From: Randy Brukardt
Date: Wednesday, January 31, 2007  8:19 PM

As I mentioned earlier, I'm still concerned about the interaction of AI05-0019, dynamically
specified stream attributes, and freezing rules. Since the rules of AI05-0019 aren't
determined yet, this is somewhat guesswork.

Consider the following example was:

    type My_Big_Int is range 0 .. 10000;

    type Write_Ptr is access procedure 
        (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
         A : in My_Big_Int'Base);

    procedure Good_Write
        (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
         A : in My_Big_Int'Base);

    WPtr : Write_Ptr := Good_Write'Access;

    for My_Big_Int'Write use WPtr.all; -- OK (I).

The attribute definition clause is (almost) legal, because there is no restriction
against subprograms that are not statically denoted. Indeed, AARM 13.3(4.c-d)
says specifically that this is intentional.

Tucker pointed out, however, that Good_Write'Access frezzes Good_Write, and that
freezes it's parameters, which freeze My_Big_Int. Thus the attribute definition clause
for My_Big_Int is illegal; it it working on a frozen type. He went on to claim
that this means that no dynamic subprograms can be given, and thus an
implementation does not have to implement it.

This brings up two issues. First of all, the claim that it doesn't have to be
implemented is not quite true; there are a couple of pathologies that could in
theory occur. One was included in the test I gave (because for some reason Isaac
had a large chunk of code to support it in Janus/Ada):

    type My_Big_Int is range 0 .. 10000;

    procedure Good_Write
        (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
         A : in My_Big_Int'Base);

    for My_Big_Int'Write use Good_Write; -- OK.
    for My_Big_Int'Output use My_Big_Int'Write; -- OK (I).

Surely, in this case, you could just give Good_Write instead. I can't think of
anything useful that could be done with this (the only interesting thing would be
to use the default implementation of Write - but that would usually have either the
wrong semantics or exactly the same semantics).

More annoying is the following:

    type My_Big_Int is range 0 .. 10000;

    type Write_Ptr is access procedure 
        (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
         A : in My_Big_Int'Base);

    WPtr : Write_Ptr := null;

    for My_Big_Int'Write use WPtr.all; -- OK (I).

The attribute_definition_clause must necessarily raise Constraint_Error, but it is
legal by all of the current Ada rules.

Of course, such a construct would violate Bob's good taste in freezing rule: if the
compiler has to deduce some fact (that such a construct does not really need to be
supported because it will always raise Constraint_Error), then the construct should
be statically illegal. Thus, one could make a case that both of these cases should
be statically illegal. (As both appear to be pathologies, I don't think either will
ever appear in the ACATS, so it doesn't really matter.)

However, the changes to AI05-0019 will change this dynamic. The first example relies
on the freezing of Good_Write freezing its parameters. But this is precisely what
AI05-0019 is intending to change (somehow). One possibility is that only calls
would freeze profiles; it's not clear whether Good_Write'Access would freeze the
profile or not. (Not doing so would bring up some interesting implementation issues
in the case where a compile needs to generate a thunk to implement 'Access -- for
instance, to correct the profile to eliminate effects of shared generics. But
there doesn't appear to be any real need to freeze the profile at this point; the
thunks could be deferred to the end of the unit.)

In that case, the original example would be legal. AI05-0019 needs to take this
into account (somehow); in particular, it needs to decide if it intends to allow
this case.

The question still remains as to whether this construct could ever be useful.
I claim, because of freezing rules other than those considered in AI05-0019, that
it could never be useful.

The reason is that any routines used in stream attribute redefinition clauses have a
parameter of the type, and the type must not be frozen at the point of the clause.
Thus, the routines must necessarily be declared between the type declaration and the
attribute_definition_clause; about the only "interesting" thing you could do with
the declarations is put them in a nested package (and possibly the private part
of such a package). You can't use a function to change the value of an access
variable which will be used in such a stream attribute clause (such a function
would necessarily have to be in the body of the package where the type is declared
in order to get access to the procedures; and thus that function would not yet be
elaborated and thus would always raise Program_Error). Streaming in of access types
is potentially erroneous; surely we don't require that to work across multiple
runs of a program. So I don't think there is any way to get a value into an
access-to-subprogram variable that couldn't be directly denoted.

(Note that we can't use formal subprograms in this case, because the
attribute_definition_clause would be in the wrong scope. And of course the original
routine would be directly available anyway, why use the formal?)

Thus I conclude this capability has a value roughly equivelent to nothing. I think
we should require stream attributes to be statically denoted, and then we don't
need to constrain AI05-0019 in order to keep this problem solved. (It would
hardly be incompatible, as Tucker has proved that it is currently illegal in all
but pathological cases.)

Perhaps we need a separate AI on this topic? (Although there is little need to fix
this pathology unless the changes to the freezing rules make it more possible.)

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

From: Tucker Taft
Date: Wednesday, January 31, 2007  10:05 PM

> ...
> 
>     type My_Big_Int is range 0 .. 10000;
> 
>     procedure Good_Write
>         (Stream : not null access Ada.Streams.Root_Stream_Type'Class;
>          A : in My_Big_Int'Base);
> 
>     for My_Big_Int'Write use Good_Write; -- OK.
>     for My_Big_Int'Output use My_Big_Int'Write; -- OK (I).

Doesn't the attribute reference to My_Big_Int'Write
cause freezing of My_Big_Int as well?

...
> However, the changes to AI05-0019 will change this dynamic. The first
> example relies on the freezing of Good_Write freezing its parameters. But
> this is precisely what AI05-0019 is intending to change (somehow
> Perhaps we need a separate AI on this topic? (Although there is little need
> to fix this pathology unless the changes to the freezing rules make it more
> possible.)
 > ...

I guess I don't understand your concern in the first place.
You implied that it is hard to support the "for X'Write use WPtr.all;"
but I don't understand why this is hard.  And suppose you wrote:

     procedure Write_Rename(...);
     for X'Write use Write_Rename;
     ...
     procedure Write_Rename(...) renames WPtr.all;

Shouldn't this be allowed?  What is the big deal with supporting
WPtr.all directly?  Can't you always just create a wrapper if
you really need to?

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

From: Randy Brukardt
Date: Wednesday, January 31, 2007  10:30 PM

> Doesn't the attribute reference to My_Big_Int'Write
> cause freezing of My_Big_Int as well?

I don't see why. *Object* names cause freezing, but this is the name
of a subprogram. If this occurrence of a subprogram name freezes the
subprogram (which it would need do to freeze the prefix), then that
would freeze the type of the parameters, which would mean you could
never specify a stream attribute. That's silly.

...
> I guess I don't understand your concern in the first place.
> You implied that it is hard to support the "for X'Write use WPtr.all;"
> but I don't understand why this is hard.  And suppose you wrote:
> 
>      procedure Write_Rename(...);
>      for X'Write use Write_Rename;
>      ...
>      procedure Write_Rename(...) renames WPtr.all;
> 
> Shouldn't this be allowed?

Yes, I guess. It would create a wrapper.

> What is the big deal with supporting
> WPtr.all directly?  Can't you always just create a wrapper if
> you really need to?

You can only (correctly) create a wrapper if the profile is frozen. Which
of course cannot be the case in the attribute definition clause. I suppose
you could delay the wrappers to the end of the unit in order to ensure the
types are frozen, but that is messy because you'd have to evaluate the value
somewhere first. Plus its messy just because of the effort to delay things.

And why do all of that if the feature in question can't be used to do anything
useful??

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

From: Pascal Leroy
Date: Thursday, February  1, 2007  3:51 AM

> Thus I conclude this capability has a value roughly 
> equivelent to nothing. I think we should require stream 
> attributes to be statically denoted, and then we don't need 
> to constrain AI05-0019 in order to keep this problem solved. 

I agree, this seems like the best way to cut the Gordian Knot.

> (It would hardly be incompatible, as Tucker has proved that 
> it is currently illegal in all but pathological cases.)

Incidentally my favorite compiler has implemented exactly this (static
denotation) for 12 years and nobody complained.

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

From: Pascal Leroy
Date: Thursday, February 1, 2007  3:54 AM

> You can only (correctly) create a wrapper if the profile is 
> frozen. Which of course cannot be the case in the attribute 
> definition clause. I suppose you could delay the wrappers to 
> the end of the unit in order to ensure the types are frozen, 
> but that is messy because you'd have to evaluate the value 
> somewhere first. Plus its messy just because of the effort to 
> delay things.

You cannot delay until the end of the unit in general because the
attribute could be called in the middle of the declarative part (think of
Input).  You have to delay until the freezing point.  But that requires a
semi-decent implementation of the freezing rules.

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

From: Randy Brukardt
Date: Thursday, February  1, 2007  2:54 PM

Well, you can't always generate the wrapper at the point of freezing (you
might be in the middle of something). We tried that for a while and it
was a disaster.

In any case, there's no problem with forward calls to wrappers generated
at the end of the scope, any more than there is with calls to the body
of a subprogram. And surely you're not making elaboration checks on the
wrapper itself (as opposed to the stuff inside). There is nothing that could
elaborate after the call anyway (else there would be freezing violations),
so the actual location of the code can't matter. (Especially as such a call
must necessarily raise Program_Error.)

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

From: Randy Brukardt
Date: Thursday, February  1, 2007  2:55 PM

>Incidentally my favorite compiler has implemented exactly this (static
>denotation) for 12 years and nobody complained.

Doesn't surprise me; it makes a good data point that the change is not harmful.

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


Questions? Ask the ACAA Technical Agent