CVS difference for ais/ai-00195.txt

Differences between 1.1 and version 1.2
Log of other versions for file ais/ai-00195.txt

--- ais/ai-00195.txt	1998/09/30 00:17:35	1.1
+++ ais/ai-00195.txt	1998/10/01 00:24:30	1.2
@@ -1,24 +1,411 @@
-!standard 13.13.1 (00)                                98-03-27  AI95-00195/01
+!standard 13.13.1 (00)                                98-04-04  AI95-00195/02
 !class binding interpretation 98-03-27
 !status received 98-03-27
+!reference AI95-00108
+!reference AI95-00145
 !priority High
 !difficulty Hard
 !subject Streams
 
-!summary 98-03-27
+!summary 98-04-04
 
+When the predefined Input attribute creates an object, this object undergoes
+initialization and finalization.
 
-!question 98-03-27
+In determining whether a stream-oriented attribute has been specified for a
+type, the normal visibility rules are applied to the
+attribute_definition_clause.
 
+For a derived type which is limited, the attributes Read and Write are
+inherited, and the attributes Input and Output revert to their predefined
+definition (i.e. they cannot be called).  (This amends AI95-00108.)
 
-!recommendation 98-03-27
+In the profiles of the stream-oriented attributes, the notation "italicized T"
+refers to the base type for a scalar type, and to the first subtype otherwise.
 
+For an untagged derived type with new discriminants that have defaults, the
+predefined stream-oriented attributes read or write the new discriminants, not
+the old ones.  (This amends AI95-00108.)
 
-!wording 98-03-27
+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.
 
+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.
 
-!discussion 98-03-27
+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.
 
+The number of calls performed by the predefined implementation of the stream-
+oriented attributes to the Read and Write operations of the stream type is
+unspecified, but there must be at least one such call for each top-level call
+to a stream-oriented attribute.
+
+The predefined stream-oriented attributes for a scalar type shall only read or
+write the minimum number of stream elements required by the first subtype of
+the type.  Constraint_Error is raised if such an attribute is passed (or would
+return) a value outside the range of the first subtype.
+
+In an attribute_definition_clause for a stream-oriented attribute, the name
+shall not denote an abstract subprogram.
+
+!question 98-04-04
+
+1 - RM95 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 RM95 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 - RM95 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 that doesn't have visibility over that private part, is a
+reference to the attribute legal? (no)
+
+3 - Let T be a limited type with an attribute_definition_clause for attribute
+Read, and D a type derived from T, and assume that there is no
+attribute_definition_clause for D'Read.  Is a usage of D'Read legal? (yes)
+
+4 - The definition of the profiles of S'Read, S'Write, S'Input
+and S'Output given in RM95 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 - AI95-00108 states (in the !discussion section) that "for untagged derived
+types, there is no problem for the derived type inheriting the stream
+attributes."
+
+This doesn't seem clear if the derived type includes a known discriminant
+part.  Consider:
+
+        type Parent (D1, D2 : Integer := 1) is ...;
+        type Child (D : Integer := 2) is new Parent (D1 => D, D2 => D);
+
+Clearly Parent'Write writes two discriminant values.  How many discriminants
+does Child'Write write? (one)
+
+6 - RM95 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 attributes_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 - RM95 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)
+
+10 - For a scalar type T, the second parameter of the predefined stream
+attributes is of type T'Base.  Given the same type declaration for T,
+different compilers may choose different base ranges, and therefore write
+different numbers of storage units to the stream.  This compromises
+portability, and makes it difficult to use streams to match a file format
+defined externally to Ada.
+
+11 - In an attribute_definition_clause for a stream attribute, is it legal to
+give a name that denotes an abstract subprogram? (no)
+
+!recommendation 98-04-04
+
+See summary.
+
+!wording 98-04-04
+
+See summary.
+
+!discussion 98-04-04
+
+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 (<discriminants or bounds taken from the stream>);
+        begin
+           S'Read (..., Anon);
+        end;
+
+Accordingly, the initialization described in RM95 3.3.1(8-9) and RM95 7.6(10)
+takes place when Anon is declared, and the finalization described in RM95
+7.6.1 takes 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
+           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 Q doesn't have visibility over the
+private part of P, which contains the attribute_definition_clause for
+attribute Read.  On the other hand, at a place that has visibility over the
+private part of P (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).
+
+3 - AI95-00108 states that "for a type extension, the predefined Read
+attribute is defined to call the Read of the parent type, followed by
+the Read of the non-inherited components, if any, in canonical order."
+
+This rule doesn't work for limited (tagged) types, because the non-
+inherited components might include protected objects or tasks for which
+the predefined Read and Write attributes cannot be called.
+
+For limited derived types (tagged or not), the only sensible rule is
+that Read and Write are inherited "as is".  This is consistent with what
+happens with the operator "=".  On the other hand the attributes Input
+and Output cannot be inherited, as explained in the discussion of AI95-
+00108.  Therefore, these attributes must revert to their predefined
+definition, which means that they cannot be called, as stated in RM95
+13.13.2(36).
+
+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, RM95 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 - The inheritance rule given in AI95-00108 should only apply to those
+attributes that have been specified for the parent type.
+
+If this rule was applied to the predefined stream-oriented attributes, it
+would require, in the example given, to read or write two discriminants,
+because the predefined Read and Write attributes of type Parent do read or
+write two discriminants.  But that would be inconsistent with the rule given
+in RM95 13.13.2(9): "the Read or Write attribute for each component is called
+in canonical order," since D1 and D2 are not components of type Child.
+
+Furthermore, definiteness can be changed by type derivation, and the dynamic
+semantics of Read and Write depend on definiteness.  Consider the following
+modification of the original example:
+
+        type Parent (D1, D2 : Integer) is ...;
+        type Child (D : Integer := 2) is new Parent (D1 => D, D2 => D);
+
+In this case the predefined stream-oriented attributes for type Parent do not
+read or write the discriminants, so applying the inheritance rule of AI95-
+00108 would cause the stream-oriented attributes for Child to not read or
+write any discriminant, which doesn't make sense.
+
+Therefore, RM95 13.13.2(9) must have precedence, and the predefined stream-
+oriented attributes for Child only read or write exactly one discriminant, D.
+
+The underlying model is that the predefined stream-oriented attributes are
+created anew for each type declaration, based on the structure of the type,
+much like predefined operators.
+
+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, RM95 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.
+
+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 RM95 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 pervert 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 at least one call to Read or Write
+for each top-level call to a stream-oriented attribute.  In other words, an
+implementation should not perform buffering across top-level calls.
+
+10 - Consider the declaration:
+
+	type T is range 1 .. 10;
+
+This declaration "defines an integer type whose base range includes at least
+the values [1 and 10] and is symmetric around zero, excepting possibly an
+extra negative value," as explained in RM95 3.5.4(9).
+
+Based on this rule (and assuming typical hardware), an implementation might
+choose an 8-, 16-, 32- or 64-bit base type for T.
+
+RM95 13.13.2(17) advise the implementation to "use the smaller number of
+stream elements needed to represent all values in the base range of the scalar
+type" when reading or writing values of type T.
+
+Clearly this is a portability issue: if two implementation use (as is typical)
+8-bit stream elements, but have different rules for selecting base types, the
+number of elements read to or written from a stream will differ.  This makes
+it very hard to write stream operations that comply with an externally defined
+format.
+
+In the above case, it would seem reasonable to read or write only the minimum
+number of stream elements necessary to represent the range 1 .. 10.  This
+would remove the dependency on the base type selection, and make it easier to
+write portable stream operations.  (There is still the possibility that
+different implementations would choose different sizes for stream elements,
+but that doesn't seem to happen in practice on typical hardware.)
+
+The only issue with that approach is that the stream-oriented attributes for
+scalar types have a second parameter of type T'Base, e.g.:
+
+	procedure S'Write (Stream : access Ada.Streams.Root_Stream_Type'Class;
+                         Item : in T'Base);
+
+So one might call T'Write with the value 1000 for the Item parameter, and this
+might exceed the range representable in the stream.  However, this usage is
+non-portable in the first place (because it depends on the choice of base
+range), so it doesn't seem important to preserve it.  In fact any attempt at
+reading or writing a value outside the range of the first subtype is highly
+suspicious.
+
+Based on this reasoning, the following rules are added.  Note that these rules
+are Dynamic Semantics rules, not Implementation Advices:
+
+   - The predefined stream-oriented attributes for a scalar type T shall
+     only read or write the minimum number of stream elements necessary to
+     represent the first subtype.  If S is the first subtype, the number
+     of stream elements read to or written from the stream is exactly:
+
+        (S'Size + Stream_Element'Size - 1) / Stream_Element'Size
+
+   - If Write or Output is called with a value of the Item parameter outside
+     the range of the first subtype, Constraint_Error is raised.  This check
+     is a Range_Check.
+
+   - If the value extracted from the stream by Read or Input is outside the
+     range of the first subtype, Constraint_Error is raised.  This check is a
+     Range_Check.
+
+11 - 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.
 
 !appendix 98-03-27
 

Questions? Ask the ACAA Technical Agent