!standard 8.3 (23) 02-09-24 AI95-00195/12 !standard 13.13.1 (09) !standard 13.13.1 (27) !standard 13.13.1 (35) !standard 13.13.1 (36) !standard 13.13.1 (36.1) !class binding interpretation 98-03-27 !status work item 02-05-31 !status ARG Approved 6-0-2 01-10-07 !status work item 98-04-04 !status received 98-03-27 !reference AI95-00108 !reference AI95-00145 !priority High !difficulty Hard !subject Streams !summary 1 - When the predefined Input attribute creates an object, this object undergoes default initialization and finalization. 2 - For the purposes of checking legality rules, it is necessary to determine whether a stream-oriented attribute has been specified for a limited type (13.13.2(9/1) and 13.13.2(36/1)). This is done by applying the normal visibility rules to the attribute_definition_clause. 3 - For a limited tagged type T, if Read is available then T'Input is available, even if it is not specified. Similarly, if Write is available then T'Output is available. T'Class'Read, T'Class'Write, T'Class'Input and T'Class'Output are available only if they are specified or the corresponding specific attribute is available somewhere within the same list of declarations as T. 4 - In the profiles of the stream-oriented attributes, the notation "italicized T" refers to the base subtype for a scalar type, and to the first subtype otherwise. 5 - In an attribute_definition_clause for a stream-oriented attribute, the name shall not denote an abstract subprogram. 6 - The predefined Read attribute for composite types with defaulted discriminants must ensure that, if exceptions are raised by the Read attribute for some discriminant, the discriminants of the actual object passed to Read are not modified. This may require the creation of an anonymous object, which undergoes initialization and finalization. 7 - The predefined Read attribute for composite types with defaulted discriminants must raise Constraint_Error if the discriminants found in the stream differ from those of the actual parameter to Read, and this parameter is constrained. 8 - If S is a subtype of an abstract type, an attribute_reference for S'Input is illegal unless this attribute has been specified by an attribute_definition_clause. 9 - The number of calls performed by the predefined implementation of the stream-oriented attributes on the Read and Write operations of the stream type is unspecified. An implementation may take advantage of this permission to perform internal buffering. However, all the calls on the Read and Write operations of the stream type needed to implement an explicit invocation of a stream-oriented attribute must take place before this invocation returns. !question 1 - 13.13.2(27) states that S'Input "creates an object (with the bounds or discriminants, if any, taken from the stream), initializes it with S'Read, and returns the value of the object." Does the verb "initialize" in this sentence refer to the entire initialization process mentioned in 3.3.1(18) and 7.6(10)? (Yes.) In particular, if S is a controlled subtype, or if it contains controlled components, is the Initialize subprogram called? (Yes.) Is the Finalize subprogram called when the intermediate object is finalized? (Yes.) For a record type whose components have initial values, are these values evaluated? (Yes.) 2 - 13.13.2(36) states that "an attribute_reference for one of these attributes is illegal if the type is limited, unless the attribute has been specified by an attribute definition clause." If some stream-oriented attribute has been specified in a private part, and we are at a point where declarations in that private part are not visible, is a reference to the attribute legal? (No.) 3 - Let T be a limited type with a visible attribute_definition_clause for attribute Read. Is a usage of T'Class'Read legal? (Yes.) 4 - The definition of the profiles of S'Read, S'Write, S'Input and S'Output given in 13.13.2 uses the notation "italicized T" for the type of the Item parameter. What is the meaning of "italicized T" in this context? (T'Base for scalars, first subtype otherwise.) 5 - In an attribute_definition_clause for a stream attribute, is it legal to give a name that denotes an abstract subprogram? (No.) 6 - 13.13.2(9) states that for a record type, the predefined S'Read reads the components in positional aggregate order. However, the language doesn't seem to specify what happens when exceptions are raised by the calls to the Read attribute for the components. Consider for example the following type declarations: type T1 is range ...; type T2 is range ...; type R (D1 : T1 := ...; D2 : T2 := ...) is record ... end record; Say that attribute_definition_clauses have been given for T1'Read and T2'Read, and consider a call to R'Read. Assume that, during this call, an exception is raised by T2'Read. Is the discriminant X.D1 modified? (No.) 7 - Consider a call to T'Read where T is a type with defaulted discriminants. If the discriminants found in the stream have values different from those of the discriminants of the object passed to T'Read for the Item parameter, and that object is constrained, is Constraint_Error raised? (Yes.) 8 - If T is an abstract type, is the function T'Input abstract? (No, but it cannot be called.) 9 - 13.13.1(1) states that "T'Read and T'Write make dispatching calls on the Read and Write procedures of the type Root_Stream_Type." Is the number of those calls specified? (No.) !recommendation (See Summary.) !wording (See Corrigendum.) !discussion 1 - It seems logical to make Input follow as closely as possible the semantics of code that could be written by a user, so the implementation of S'Input should obtain the discriminants or bounds from the stream, and then declare an object with appropriate discriminants or bounds, as in: declare Anon : S (); begin S'Read (..., Anon); end; Accordingly, the initialization described in 3.3.1(8-9) and 7.6(10) takes place when Anon is declared, and the finalization described in 7.6.1 takes place when Anon disappears (and before returning from the call to S'Input). Note that as part of initialization, compiler-specific fields are initialized as required by the implementation (and as permitted by AARM 3.3.1(14.a)). 2 - Consider for example: package P is type T is limited private; private type T is new Boolean; procedure Read (Stream : ...; Item : out T); for T'Read use Read; end P; with P; procedure Q is X : P.T; begin P.T'Read (..., X); -- Illegal? end Q; The call to P.T'Read is illegal, because the declarations and clauses (including the attribute_definition_clause for Read) in the private part of P are not visible in Q. On the other hand, at a place where the declarations and clauses in the private part of P are visible (and that comes after the attribute_definition_clause) a reference to T'Read is legal. This rule is necessary to preserve the privacy of private types. Note that it is the location of the attribute_definition_clause that counts, not that of the subprogram specified in that clause. Thus, if the procedure Read above were moved to the visible part of P, a reference to P.T'Read would still be illegal (but a reference to P.Read wouldn't). Because the stream attributes are operational attributes (13.13.2(1/1)), an attribute_definition_clause may be given before the type is fully defined. 3 - In order to simplify the discussion, we first define a new term: a stream- oriented attribute is "available" at some place in the program text if an attribute_reference for that attribute is (or would be) legal at that place. The rules given in 13.13.2(9/1) try to insure that if the Read attribute is available for a limited tagged ancestor type, then it is also available for all of the types derived from that ancestor. However, this is not true because this rule fails to take into account the visibility issues mentioned in item #2 above. Consider for example: package P is type T is tagged limited private; private type T is tagged record ...; for T'Read use ... end P; with P; package Q is protected type Prot is ... end Prot; type NT is new T with record C : Prot; end record; -- No need to define Read (13.13.2(9/1)) end Q; with Q; package body P is type NNT is new NT with null record; ... NNT'Read (...); -- Legal? end P; Because the attribute_definition_clause for P.T'Read is not visible at the place where NT is declared, 13.13.2(9/1) does not apply, and it is therefore not necessary to define the Read attribute for Q.NT. However, when we come to the declaration of NNT, we want to ensure that NNT'Read is not callable, because we have no mechanism for reading the component C. Instead of depending on whether the attribute has been specified for some ancestor type, 13.13.2(9/1 and 36/1) must depend on whether the attribute is available for the parent type (and for the types of the extension components). Moreover, dispatching to the Read attribute does not work in the above case: there is no guarantee that all the types derived from T will have an available Read attribute. So the attribute Read is not available for T'Class. The only case where NT'Read is guaranteed to be available for every NT descended from T is if T'Read is available in the visible part of P. In this case, T'Class'Read is available everywhere. The default implementation of T'Input is defined in terms of T'Read. If T'Read is available, then the implementation of T'Input is well-defined as well, and there is no reason to prohibit calls. A similar argument applies to T'Output when T'Write is available. But T'Class'Input and T'Class'Output are only available if T'Read and T'Write are available in the visible part of P. The above model is unfortunately not what is stated by the rules in 13.13.2(36/1). These rules seem to imply that a reference to Input (resp. Output) is legal for the type extension if Input (resp. Output) is available for the parent type. This is incorrect because the default implementation of Input and Output calls Read or Write for the same type, not Input or Output for the parent type. It should say that a reference to Input (resp. Output) is legal for the type extension if Read (resp. Write) is available for that type. 4 - AI95-00145 specifies the meaning of the notation "italicized T" for operators as follows: "The italicized T shown in the definitions of predefined operators means: - T'Base, for scalars - the first subtype, for tagged types - the type without any constraint, in other cases" In the case of stream-oriented attributes the notation "italicized T" must be consistent with the parameter subtype required for attribute_definition_clauses. If we chose the same rule as for operators, we would have a discrepancy in the case of constrained untagged types, and this would unnecessarily complicate the static and dynamic semantics. When one of the stream-oriented attributes is specified by an attribute definition clause, 13.13.2(36) states that "the subtype of the Item parameter shall be the base subtype if scalar, and the first subtype otherwise." Therefore, in the parameter profiles for the stream-oriented attributes in section 13.13, the notation "italicized T" means: - T'Base, for scalars - the first subtype, in other cases 5 - Obviously it should not be possible to perform a non-dispatching call to an abstract subprogram (stream-oriented attributes are always called in a non-dispatching manner). Therefore, we have two options: - Make the attribute_definition_clause illegal. - Make the calls (explicit or implicit) illegal. The second option is a significant implementation burden, and allowing the attribute_definition_clause only to reject all calls doesn't seem to do any good. That's why the first option was preferred. 6 - The problem mentioned in the question only exists for a type R that is passed by reference to R'Read: obviously if the type is passed by copy, an exception raised by R'Read cannot affect the original object. In the case of a type which is passed by reference, we must consider two cases: - If the exception in T2'Read is due to the failure of some language-defined check, 11.6(6) explains that the object or its parts may become abnormal, so we don't have to specify what happens to the discriminants. - If the exception in T2'Read is due to an explicit raise, the object obviously doesn't become abnormal, and therefore we must preserve the integrity of its discriminants. In other words, either all discriminants are updated (if the calls to the Read attributes for the discriminants were successful) or none (if any call to a Read attribute for a discriminant raised an exception). This model requires an implementation to read the discriminants, create an anonymous object using the given discriminants and assign that object to the formal parameter Item. The normal initialization and finalization take place for this anonymous object. Strictly speaking, the use of an anonymous object is only required for a type with defaulted discriminants which is passed by reference, if the actual parameter of Read is not constrained. However, an implementation is free to use anonymous objects in other cases. Use of an anonymous object is only required for discriminants. An implementation is free to read the other components directly in the Item parameter. For example, if we change the original example as follows: type R is record C1 : T1; C2 : T2; end record; then if T2'Read raises an exception when reading component C2, it is unspecified if C1 is modified or not. Similarly, 13.13.2(35) describes what checks must/may be performed on components of a scalar of access types, but doesn't specify at which point these checks take place. This could make an observable difference: for example, one implementation may check each discriminant as it reads it; another might read all the discriminants and then check them all before reading any other component; a third may attempt to read all the fields in the record (or at least all the fields that don't depend on a discriminant) before checking any of the discriminants. Clearly we want to leave such implementation details unspecified, as overconstraining implementations could have negative effects, notably in terms of performance. 7 - When the type has defaulted discriminants, the predefined Read attribute must read them from the stream. It can be the case that the actual object passed to Read is constrained. In this case, the discriminants found in the stream may or may not match those of the actual. If they don't match, Constraint_Error is raised, and this is a Discriminant_Check. It is unspecified whether this effect is achieved by assigning a temporary object, as explained in #6 above, or by other means. 8 - If T is an abstract type, calling the function T'Input would effectively create an object whose tag designates T, which is absurd. We could decide that T'Input is abstract, but it seems simpler to say that any attribute_reference for this attribute is illegal, by analogy with the rule stated in 13.13.2(36) for limited types. 9 - Surely the user could count the calls to the Read and Write operations for the stream type by writing a perverse implementation of these operations, but it seems that performance should have precedence: for the predefined implementation of String'Write, requiring a call to Ada.Streams.Write for each character would be a big performance hit, with no real benefits. Therefore, the number of calls to the Read and Write operations is unspecified, and implementations are free (and in fact advised) to do internal buffering. However, we don't want to allow an implementation to buffer all stream output and do only one call to Write when the program terminates: the user must be able to assume reasonable properties regarding the underlying buffering mechanism. That's why we require that all the calls to Read or Write take place before the top-level call to a stream-oriented attribute completes. In other words, an implementation may combine several consecutive calls to Write into a single one, provided these calls all pertain to a single top-level call to the attribute Write (or Output). !corrigendum 8.3(23) @dinsa @xbullet