!standard 12.5.1(20/3) 12-06-28 AI12-0030-1/02 !class binding interpretation 12-06-06 !status work item 12-06-06 !status received 12-05-11 !priority Low !difficulty Medium !subject Formal derived types and stream attribute availability !summary Stream attributes are inherited like primitive operations of an untagged formal derived type. !question Consider this example: procedure Foo (S : not null access Ada.Streams.Root_Stream_Type'Class) is type Lim is limited null record; type Dlim is new Lim; -- does not inherit Lim'Read procedure Read_Lim (Stream : not null access Ada.Streams.Root_Stream_Type'Class; Item : out Lim) is null; for Lim'Read use Read_Lim; Lim_Var : Lim; Dlim_Var : Dlim; generic type Flim is new Lim; procedure G; procedure G is Flim_Var : Flim; begin Flim'Read (S, Flim_Var); -- Legal? (Yes.) end G; procedure I is new G (Dlim); begin Lim'Read (S, Lim_Var); Dlim'Read (S, Dlim_Var); -- Illegal. I; end; Is the use of Flim'Read legal? Should it be? If it is legal, are the dynamic semantics well defined? Assuming that stream attribute availability is a "characteristic", it seems that 12.5.1(20/3) applies and the use within the generic body is legal. This means we need to define the meaning of the corresponding construct in the instance. The "even if it is never declared" wording in 12.5.1(21/3) handles a similar case, the case where a generic formal type promises a primitive operation that the corresponding actual type lacks. Do we need to add some analogous wording to handle this case? (No.). !reponse Stream attributes act like predefined primitive operations in most contexts, so they should act that way in this example as well. !wording Add after AARM 12.5.1(21.a/3): AARM To Be Honest: The availability of stream attributes is not formally a characteristic of a type; but it is still determined by the ancestor type for a formal derived type in the same way as the the characteristics are. Similarly, while stream attributes are not formally primitive operations of a type, the implementation of a stream attribute that is executed for a call of an attribute of a formal derived type is determined in the same way as the subprogram body that is executed for a primitive subprogram of a formal derived type. !discussion The answer to the legality question is clear: characteristics of all sorts come from the ancestor type, not the actual type, so of course the attribute is legal and it will call the attribute of the ancestor type. However, the wording of the Standard is definitely not clear on this point. "Availability of stream attributes" is not a characteristic of a type (at least it is not listed in 3.4, which is the definition of a "characteristic". So the existing rules don't cover this case. We could try to define "availability" as a "characteristic" (by adding a new bullet at 3.4(15), but that most likely would lead to conflicts as 7.3.1 defines how characteristics are inherited for private types, while 13.13.2 does so for "availability". These are similar, but probably not precisely the same, and we'd have to reword 13.13.2 in order to avoid confusion. Alternatively, we could just throw some text and add a new rule after 12.5.1(20/3) specifically to say that "availability of stream attributes" is the same as that of the ancestor type, and the stream attribute called is that of the ancestor type. But that just adds a lot of text to say something obvious. It's rare to see a derived untagged formal type, as such types can only match derived untagged types -- which is very limiting, especially given that untagged derivations themselves aren't that common (it's usually better to declare a new type). As such, it's not critical to have the Standard wording perfect in this area. So we simply add a To Be Honest note so there is no doubt of the intent. !ACATS test An ACATS C-test could be written that is similar to the example in the question. !appendix From: Steve Baird Sent: Friday, May 11, 2012 5:27 PM Consider this example: procedure Foo (S : not null access Ada.Streams.Root_Stream_Type'Class) is type Lim is limited null record; type Dlim is new Lim; -- does not inherit Lim'Read procedure Read_Lim (Stream : not null access Ada.Streams.Root_Stream_Type'Class; Item : out Lim) is null; for Lim'Read use Read_Lim; Lim_Var : Lim; Dlim_Var : Dlim; generic type Flim is new Lim; procedure G; procedure G is Flim_Var : Flim; begin Flim'Read (S, Flim_Var); -- legal? end G; procedure I is new G (Dlim); begin Lim'Read (S, Lim_Var); Dlim'Read (S, Dlim_Var); -- illegal I; end; Is the use of Flim'Read legal? Should it be? If it is legal, are the dynamic semantics well defined? Assuming that attribute availability is a "characteristic", it seems that 12.5.1(20/3) applies and the use within the generic body is legal. This means we need to define the meaning of the corresponding construct in the instance. The "even if it is never declared" wording in 12.5.1(21/3) handles a similar case, the case where a generic formal type promises a primitive operation that the corresponding actual type lacks. We may need to add some analogous wording to handle this case (or at least a TBH note if this seems like too much of a corner case). **************************************************************** From: Tucker Taft Sent: Sunday, May 13, 2012 10:08 PM > generic > type Flim is new Lim; > procedure G; > procedure G is > Flim_Var : Flim; > begin > Flim'Read (S, Flim_Var); -- legal? Yes. > end G; > procedure I is new G (Dlim); > begin > Lim'Read (S, Lim_Var); > Dlim'Read (S, Dlim_Var); -- illegal True. > I; > end; > > Is the use of Flim'Read legal? Should it be? Yes, just like all operations for non-tagged types. > If it is legal, are the dynamic semantics well defined? Yes, since the semantics are always to use the operation of the specified ancestor type if non-tagged. We have now made "=" a special case, but all other operations come from the ancestor type. > Assuming that attribute availability is a "characteristic", it seems > that 12.5.1(20/3) applies and the use within the generic body is > legal. This means we need to define the meaning of the corresponding > construct in the instance. > > The "even if it is never declared" wording in 12.5.1(21/3) handles a > similar case, the case where a generic formal type promises a > primitive operation that the corresponding actual type lacks. We may > need to add some analogous wording to handle this case (or at least a > TBH note if this seems like too much of a corner case). I don't really see this as a very special case. For non-tagged types, unless you explicitly import the operation (or it is equality), you always use the operations of the specified ancestor, even if they are overridden in the actual type. **************************************************************** From: Steve Baird Sent: Monday, May 14, 2012 12:31 PM > I don't really see this as a very special case. > For non-tagged types, unless you explicitly import the operation (or > it is equality), you always use the operations of the specified > ancestor, even if they are overridden in the actual type. If I understand you correctly, you are talking about the following 12.5.1(21/3) rule: In an instance, the copy of such an implicit declaration of a primitive subprogram of a formal derived type declares a view of the corresponding primitive subprogram of the ancestor or progenitor of the formal derived type, even if this primitive has been overridden for the actual type and even if it is never declared for the actual type. and (in passing) the 3.4(17/2) rule that begins For each user-defined primitive subprogram (other than a user-defined equality operator - see below) of the parent type ... Note that these rules talk about primitive subprograms, not primitive operations. I think that means that they don't apply to the question I raised. Note that it wouldn't change the example in any essential way if we introduced a nested package so that the procedure Read_Lim is not a primitive operation of type Lim, as in type Lim is limited null record; type Dlim is new Lim; -- does not inherit Lim'Read package Prevent_Primitiveness is procedure Read_Lim (Stream : not null access Ada.Streams.Root_Stream_Type'Class; Item : out Lim) is null; end Prevent_Primitiveness; for Lim'Read use Prevent_Primitiveness.Read_Lim; This is not a question about primitive subprograms. **************************************************************** From: Tucker Taft Sent: Monday, May 14, 2012 12:56 PM > ... > Note that these rules talk about primitive subprograms, not primitive > operations. I think that means that they don't apply to the question I > raised. You are right in the letter of the law, but I think the intent was to include all overrideable operations. Note that the streaming operations of tagged types are considered dispatching operations, so that would mean that we are already treating them pretty similarly to primitives. In general, we have not presumed any significant "descriptor" needed to accompany formal derived non-tagged types (presuming you have some amount of sharing), since essentially all of the properties inside the generic are determined by the specified ancestor type. I agree we should clarify this, but I have no doubt which way it should be clarified. Your example is a good indication of why for non-tagged types we should carry this same principle through to the streaming operations, and use the operations of the specified ancestor type inside the generic. > ... This is not a question about primitive subprograms. Agreed, but it is a question about something which is the "moral equivalent" of a primitive, and I believe we should clarify the RM that they should follow the same rules. **************************************************************** From: Steve Baird Sent: Monday, May 14, 2012 1:24 PM Sounds good to me. ****************************************************************