======= LSN016.NewOOP ======= !topic Summary of revised OOP-related rules !title OOP-Summary;2.2 $Revision: 1.4 $ !reference MD-3.4.1;3.0 !reference MD-3.4.2;3.0 !reference MD-3.4.3;3.0 !reference MD-4.6;3.0 !reference MD-4.6.1;3.0 !reference OOP-Summary;1.0 91-08-24 !reference OOP-Summary;2.0 91-10-14 !from Tucker Taft $Date: 91/11/04 16:21:52 $ !discussion 0) A "class" is a set of types closed over derivation, with a unique "root type." 1) A "class-wide type" T'CLASS is defined for any "specific" type T. The set of values of T'CLASS is the union over the sets of values of T and its derivatives (see (4) below). 2) T and all derivs of T are "included" in the class-wide type T'CLASS. If T1 is included in a class-wide type, then T1'CLASS is also "included" in the class-wide type. Every type "includes" itself. MORE TERMINOLOGY: - For convenience of terminology, a value/object is considered "in a class-wide type" if its type is "included in" the class-wide type. - Two types "overlap" if one includes the other. - The "root" of a type is the type itself for a specific type, and is the root type of the class for a class-wide type. 3) Operations may be defined which have a formal parameter or result of a class-wide type. In addition, the operations of its root type are generally applicable to the class-wide type (see "matching" rules in (5) below). Two subprograms with the same designator and same number of parameters/results are considered "homographs" if for each parameter/result position, the types overlap. (Note that this definition for homograph prevents a class-wide op from hiding a specific-type op declared in the same scope.) 4) The value set for T'CLASS is: - if T is untagged, the (undiscriminated) union of the value sets of T and its derivatives; - if T is tagged, the discriminated union of the value sets of T and its derivatives, with the type tag as the (implicit) discriminant. 5) For an actual parameter to "match" a formal parameter in a subprogram call, the type of the actual parameter and the formal parameter must overlap. Furthermore, if the type is tagged, the type of the formal parameter must include the root of the type of the actual parameter (to avoid tag-check failure). Inside the subprogram, the actual parameter is "viewed" as though it were the type of the formal; for untagged types, this may require a representation change. 5.1) The membership operation "X in T" is legal if the type of X overlaps with the type T. To yield true, X must satisfy the constraints associated with T, and if X is tagged and class-wide, X'TAG must be for a specific type included in T. 5.2) An aggregate for a tagged type is legal only if the context determines a unique, specific type, rather than a class-wide one. 6) There is no implicit conversion. This concept has been replaced by the concept of matching between actuals and formals, and the ability to view a value of one type as though it were a value of some other overlapping type. [Note that the concept of "convertible" operands, etc., is also unnecessary, because the "primitive operations" usable on a class-wide type are simply those of its root type, whose result types are not class-wide.] 7) A tag is assigned when an object of a tagged type is created, and cannot be changed thereafter. The attribute TAG of any view of the object yields this "creation" tag. The tag is not affected by conversion to or from a class-wide type, nor to an "ancestor" specific type (a specific type from which its current specific type (view) is derived). 7.1) Explicit conversion is allowed only if there is a class which includes both the operand type and the target type. The "nearest common ancestor" is defined as the root of the smallest such class. For tagged types, there are additional rules: - If the types overlap, then the conversion is a "view" conversion. - If the target type is the nearest common ancestor, then the conversion is a "view" conversion. - Otherwise, the conversion is a "constructor" conversion. + A constructor conversion is disallowed if the target is a private extension of the nearest common ancestor. + A constructor conversion is disallowed if the type is limited. + All components of the new value must be determined by components of the nearest common ancestor "part" of the operand, or must be specified by explicit named associations in the conversion, or by explicit defaults in the record component declaration. + Any discriminant controlling a variant in an extension of the nearest common ancestor must be determinable statically given the above definition of the components of the new value. 8) The assignment operation for a specific tagged type changes the value, not the tag, of the view denoted by the left hand side. The asssignment operation applied to a tagged class-wide operand first checks that the tags of the left and right sides match, raising CONSTRAINT_ERROR if not. It then performs the assignment as above. [This is actually just a special case of the general rule on applying primitive ops to class-wide operands -- see (9.1) below.] [Note that assignment for access-to-class-wide will probably be much more common than assignment to class-wide objects themselves.] 9) A class-wide type has no "inherited" operations. [The primitive operations of the root of the class-wide type may be used, thanks to the matching rules -- see (5) above]. It is not permissible to derive from a class-wide type. [This last rule is not essential, but it simplifies things a bit.] 9.1) The primitive operations of a tagged type T are called "dispatching" operations, which operate as follows: - Each controlling operand determines a tag: + If the operand is class-wide, the TAG of the operand; + If the operand is a controlling result of a dispatching operation, the controlling tag of that operation; + Otherwise, the TAG of the specific type of the operand [which according to the matching rules must be T'TAG, i.e. this is a "static" binding case]. - If all controlling operands determine the same tag value, then this is the controlling tag value for the operation; otherwise, CONSTRAINT_ERROR is raised. - The controlling tag value determines which particular implementation of the operation is invoked. - If the controlling tag value is not determined by the operands, then it is determined by context (as defined in MD 3.0). If there are no class-wide controlling operands anywhere in the context which determine the tag, then the controlling tag value is by definition T'TAG [i.e., this is the normal "static" binding case]. NOTE: The above is a change. We are now saying that the fundamental semantics of the primitive ops of a tagged type are "dispatching." The "static" binding is just a special case, which happens when at least one of the operands is not a class-wide type, or when there are no "leaf" controlling operands at all (e.g. Print(Empty);). 9.2) When deriving from a tagged type, a primitive operation may not be overridden except by a subprogram which has conforming subtypes in all non-controlling parameter/result positions, and conforming modes in all positions. [This is essential because these subprograms are called through a level of indirection, seeing only the spec of the root type's operations.] Furthermore, the subtype in the controlling parameter/result positions of a primitive operation of a tagged type must be the first-named subtype of the tagged type. [This last rule is not essential, but it eliminates the need to perform extra constraint checks "inside" the implementation of a dispatching operation.] 10) Because there is no implicit conversion, there is less need for a "preference" rule. However, to allow "if 1 < 2 then ..." we still need a preference for the primitive operations of the root integer/real types (which are used for the class-wide types universal int and universal real). To avoid the Beaujolais effects identified by the LPT, and to simplify the rules, we suggest that this preference only be for the primitive ops of root int/real over the *corresponding* primitive ops of the other int/real types. Another sort of preference would be that the primitively visible ops of the *root* of a (class-wide) type would be considered if one interpretation of an operand, or the unique interpretation of the result, has the (class-wide) type. 11) The ACCESS attribute with a prefix of type T1, and an allocator with a type-mark T1, are overloaded on all access-to-T2 where T1 and T2 overlap. For tagged types, a run-time check on the tag of the designated object is made to ensure that it is for a type included in T2. If the check fails, CONSTRAINT_ERROR is raised. [Note that this run-time check eliminates the previous allowance for access-to-T to point at an object of any derivative of T. Now access-to-T only points at objects with tag = T'TAG.] 12) The predefined classes integer, real, and task each have names for their class-wide type declared in package SYSTEM: SYSTEM.INTEGER_CLASS -- equivalent to root_integer'CLASS; -- uses ops of longest integer type. SYSTEM.REAL_CLASS -- equivalent to root_real'CLASS; -- uses ops of float type with largest range. SYSTEM.TASK_CLASS -- equivalent to root_task'CLASS; -- supports abort and dynamic priority ops only. -- (Tasks are treated like implicitly tagged types, -- though there is no support for task type -- extension proposed, other than the -- extension from root_task implicit in a -- normal task type definition.) Numeric literals and named numbers are considered to be of class-wide type INTEGER_CLASS or REAL_CLASS (as appropriate), and therefore match an actual parameter of any type in the corresponding class. The primitive ops of root int/real are applicable to INTEGER/REAL_CLASS operands, but their results are not class-wide (as in Ada 83). 13) A derivative of a tagged type may not be declared at a scope level more deeply nested than the parent type. 14) A non-limited tagged type may not be extended with a limited extension. 15) If a tagged type has any abstract primitive operations, then its TAG attribute is undefined. The consequences of this are that the (specific) type may only be used as follows: - As the type of a formal parameter; - As the controlling result type of a dispatching function; - As the target type of a "view" conversion. In particular, such a type may not be used as the type in an object or component declaration. Note that there are no particular limitations on the use of the class-wide type rooted at such a type. 16) If a function has a controlling result, then it becomes abstract when inherited by a derivative that is limited or extended. [The part about "limited" is new, but necessary since we don't allow "constructor" conversions of limited types.]