!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 Amendment 201Z 08-11-26 !status WG9 Approved 08-06-20 !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) @drepl The stream-oriented attributes may be specified for any type via an @fa. 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 @fa, the subprogram name given in the clause shall statically denote a null procedure. @dby The stream-oriented attributes may be specified for any type via an @fa. 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 @fa, 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 freezes 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. ****************************************************************