!standard 03.09.03 (04) 04-12-03 AI95-00391/02 !standard 03.04 (27) !standard 03.09.01 (04) !standard 03.09.03 (06) !class amendment 04-11-13 !status Amendment 200Y 04-12-03 !status ARG Approved 5-0-5 04-11-20 !status work item 04-11-13 !status received 04-11-13 !priority Medium !difficulty Medium !subject Functions with controlling results in null extensions !summary If a type extension is a record extension with a null extension part, and no new discriminant part, then primitive functions with controlling results do *not* need to be overridden. The result of calling an implicitly declared inherited function with a controlling result is an extension aggregate with a null extension part. !problem Type derivation is commonly used after a package instantation that declares a new type, to redeclare the new type in the scope enclosing the instantiation. For example package T_Sets is new Sets(T); type T_Set is new T_Sets.Set; Unfortunately, this paradigm does not work if the type is tagged and has one or more functions with controlling results. For example, this does *not* work: generic type Element is private; package Sets is type Set is tagged private; function Unit_Set(E : Element) return Set; ... end Sets; package T_Sets is new Sets(T); type T_Set is new T_Sets.Set with null record; -- This is illegal unless Unit_Set is overridden here This is unpleasant. The programmer just wants to use the type, but they want it declared in the current scope, rather than inside the instantiation. Because of this, they are forced to override each of the functions with controlling results even though they have not added any components nor changed the type in any significant way. !proposal There are various possible solutions. One would be a kind of "type rename" clause which would implicitly declare renames of all of the primitive subprograms of the type as well. However, that wouldn't address the more general issue of a null extension of a type, and the unnecessary burden of having to override all of the functions with controlling results. The more straightforward solution is to drop the requirement to override functions with controlling results when the derived type is a "null extension" -- no components in the extension part, and no new discriminant part. One could take it further to allow a new discriminant part, so long as the discriminants are associated one-for-one with the existing discriminants. It's not clear that this last capability would be worth the effort, so we haven't included it here. It would clearly be feasible to add this capability at a later date, or during ARG review. !wording Modify 3.4(27) as follows: ... If the result type of the inherited subprogram is the derived type, the result of calling the parent's subprogram is converted to the derived type{, or in the case of a null extension, extended to the derived type with an extension_aggregate of the form: (** with null record) } Add after 3.9.1(4): Static Semantics A record extension is a *null extension* if its declaration has no known_discriminant_part and its record_extension_part includes no component_declarations. Modify 3.9.3(4): For a derived type, if the parent or abstract type has an abstract formal subprogram, or a {type other than a null extension inherits a} primitive function with a controlling result, then: Modify 3.9.3(6): * Otherwise, the subprogram shall be overridden with a nonabstract subprogram {or, in the case of a private extension inheriting a function with a controlling result, have a full type that is a null extension}; ... !discussion This problem seems important to fix, especially as we are defining standard "container" packages which declare tagged types. Having to override functions with controlling results for null extensions is a nuisance. One could go farther and eliminate the requirement for overriding functions with controlling results if all new components had explicit defaults, but that seems a potential source of errors. With null extensions, the likelihood of an automatically provided overriding being inappropriate seems miniscule. Although we don't talk about a "wrapper," we anticipate that most implementations will need to provide them, since the caller will not be aware they need to change the tag of the result returned by the function when the function is reached via a dispatching call. Clearly the wrapper will be trivial, analogous to the kind of wrapper often created for a renaming-as-body. A null extension aggregate essentially just changes the tag of its ancestor_part. !example (** Missing **) !corrigendum 3.4(27) @drepl For the execution of a call on an inherited subprogram, a call on the corresponding primitive subprogram of the parent type is performed; the normal conversion of each actual parameter to the subtype of the corresponding formal parameter (see 6.4.1) performs any necessary type conversion as well. If the result type of the inherited subprogram is the derived type, the result of calling the parent's subprogram is converted to the derived type. @dby For the execution of a call on an inherited subprogram, a call on the corresponding primitive subprogram of the parent type is performed; the normal conversion of each actual parameter to the subtype of the corresponding formal parameter (see 6.4.1) performs any necessary type conversion as well. If the result type of the inherited subprogram is the derived type, the result of calling the parent's subprogram is converted to the derived type, or in the case of a null extension, extended to the derived type with an @fa of the form: @xcode<(@i<> @b)> !corrigendum 3.9.1(4) @dinsa A type extension shall not be declared in a generic body if the parent type is declared outside that body. @dinst @i<@s8> A record extension is a @i if its declaration has no @fa and its @fa includes no @fas. !corrigendum 3.9.3(4) @drepl For a derived type, if the parent or ancestor type has an abstract primitive subprogram, or a primitive function with a controlling result, then: @dby For a derived type, if the parent or abstract type has an abstract formal subprogram, or a type other than a null extension inherits a primitive function with a controlling result, then: !corrigendum 3.9.3(6) @drepl Otherwise, the subprogram shall be overridden with a nonabstract subprogram; for a type declared in the visible part of a package, the overriding may be either in the visible or the private part. However, if the type is a generic formal type, the subprogram need not be overridden for the formal type itself; a nonabstract version will necessarily be provided by the actual type. @dby Otherwise, the subprogram shall be overridden with a nonabstract subprogram or, in the case of a private extension inheriting a function with a controlling result, have a full type that is a null extension; for a type declared in the visible part of a package, the overriding may be either in the visible or the private part. However, if the type is a generic formal type, the subprogram need not be overridden for the formal type itself; a nonabstract version will necessarily be provided by the actual type. !ACATS test ACATS tests need to be constructed to test this feature. !appendix From: Dan Eilers Sent: Monday, November 15, 2004 1:54 PM > There are various possible solutions. One would be a kind > of "type rename" clause which would implicitly declare renames > of all of the primitive subprograms of the type as well. I'd like to expound a bit on this "type rename" idea, since it solves a broader class of problems, and is therefore a potentially viable idea whether or not Tuck's proposed solution is implemented. Derived types are intended to serve two purposes: 1) the Ada83 purpose of creating two distinct types (e.g., apples and oranges) that are incompatible although they have common representation and operations; and 2) the Ada95 purpose of type extension. However, derived types are commonly abused to serve the purpose of making a type declaration accessible outside the package in which it is declared. This purpose was supposed to be served by type renaming, as stated in Steelman requirement 3-5B: "Definitions that are made within an encapsulation and are externally accessible may be renamed before use outside the encapsulation." Apparently, type renaming was omitted from Ada as an economy measure, under the delusion that the same effect could be achieved via a constraint-less subtype declaration, as is claimed in RM 8.5(6). This claim is not completely true, since subtype declarations cannot be used to complete a private type declaration. The need for type renaming to provide the completion for a private type declaration stems from another unmet Steelman requirement, generic types, as stated in Steelman requirement 12D: "12D. Generic Definitions. Functions, procedures, types, and encapsulations may have generic parameters." If generic types were available (or class-like packages that exported a type), a private type could often be completed directly as the instantiation of a generic type (or class, resp), with no need for either type derivation or type renaming. Instead, the user must enclose such a type in a generic package and instantiate that package, creating the need to somehow reach into the instantiated package when completing the private type. For example, we would like to say: with sets; package p1 is type int_set is private; private type int_set is new sets(integer); -- illegal, no generic types end p1; Instead we say: with sets; package p2 is type int_set is private; private package int_sets_pkg is new sets(integer); -- subtype int_set is int_sets_pkg.set; -- illegal use of subtype -- type int_set renames int_sets_pkg.set; -- illegal, no type renaming type int_set is new int_sets_pkg.set; -- OK, but abuse of derivation end p2; This use of derivation to achieve type renaming is an abuse because the user doesn't want two distinct types, int_set and int_sets_pkg.set. Instead, the user wants int_set to be the same type as int_sets_pkg.set, just with a different name and scope. A reader/maintainer is forced to consider whether two distinct types are being created on purpose, or only as an unwanted side effect. The users also wants the type renaming to create renames of the primitive operations, so these operations are also visible in the scope of int_set. This is analogous to the way derived types work, only simpler, since there is no need for implicit conversions of operands and/or result types. As Tuck explains, this abuse of derivation causes a further complication when the type has one or more functions with controlling results, because such functions must additionally be overriden, even if they are never used. Fixing this need to overide such functions would make derived types somewhat somewhat more palatable as a substitute for type renaming, but it would still be an abuse. Adding type renaming would satisfy the original Steelman requirement 3-5B. It would improve understanding of the language, eliminating the silly restriction that other definitions can be renamed, just not types. The use of renaming for type completion would be directly analogous to the use of renaming-as-body for subprogram completion, and therefore easy to understand. Type renaming used for completing private types would improve the readability of programs, since it prevents creation of an unwanted incompatible type. **************************************************************** From: Tucker Taft Sent: Monday, November 15, 2004 3:17 PM I think type renames are interesting, but too big a change at this point. I am also made very nervous by a private type completed by a type rename, since you now have two distinct scopes where you could define primitives of the type, one where the type was originally defined, and one where it was renamed. I had imagined a type rename more like a combination of a subtype declaration and a set of renames of the primitive subprograms. That would solve the problem of making the type and its primitives visible, but it wouldn't allow you to complete a private type, nor add more primitives at the point of the renaming. I don't know how you weight the advantages of making the type and its primitives visible versus the ability to complete a private type. I don't see much alternative to the type derivation model if you are completing a private type, because of the possibility of adding more primitives. Since the type derivation model has been in use for the past 20 years, it seems hard to justify introducing a feature with so much overlapping functionality at this time. In any case, as I said above, this seems way too much for a last minute "quick fix." **************************************************************** From: Dan Eilers Sent: Monday, November 15, 2004 8:15 PM > I am also made very nervous by a private > type completed by a type rename, since you now have two > distinct scopes where you could define primitives of > the type, one where the type was originally defined, and > one where it was renamed. Yes, this makes me nervous too, now that you point it out. But in the case I'm trying to get to work (where a private type is completed by renaming a type created by an instantiation), the user doesn't intend to ever again reference the type created by the instantiation. So there may be a solution if this can be enforced. Here's a new proposal: Instead of the current way of: package int_sets_pkg is new sets(integer); type int_set is new int_sets_pkg.set; or my proposed way of: package int_sets_pkg is new sets(integer); type int_set renames int_sets_pkg.set; do something like: type int_set is new sets(integer).set; where sets(integer) is an anonymous instantiation. Since the instantiation is anonymous, there is no possibility of the user ever subsequently referencing the instantiation or types within it. > I don't know how you weight the advantages of making the > type and its primitives visible versus the ability to > complete a private type. I'm most concerned with completing a private type without out needing derived types, preferably in a way that resembles instantiation of generic types/classes. **************************************************************** From: Tucker Taft Sent: Monday, November 15, 2004 8:39 PM The idea of anonymous instantiations was floated during the Ada 9X process, with something pretty close to the syntax Dan proposes. We didn't pursue it, but I don't know all the reasons. I do know it seems like a lot of work just to avoid using a type derivation. And I think behind the scenes the implementor would have to do a type derivation to avoid a huge implementation effort. I can also imagine that handling anonymous instantiations might involve some pain in the debugger, etc., dealing with stepping through anonymous instantiations, coming up with unique link names, etc. Based on the C++ experience, anonymous instantiations are generally a pain, and the gain comes primarily from *implicit* instantiations for generic subprograms, something which is a very big language design problem, and I suspect also a big implementation problem. Now if we were talking about adding *iterators* I know a few ARG members who might get excited, but anonymous instantiations doesn't do it, at least not for me. **************************************************************** From: Randy Brukardt Sent: Monday, November 15, 2004 7:21 PM I'm working on the Abstract Types section today, and one of the AARM notes caused me to think a bit about Tucker's proposal. As written, it allows the creation of objects of an abstract type, which is a no-no. Consider: package P1 is type Something is tagged record ...; function Constructor return Something; type Acc_All is access all Something'Class; end P1; with P1; package P2 is type Tucker_Type is abstract new P1.Something with null record; -- Illegal in Ada 95, legal and inherits a concrete function Constructor -- with Tucker's proposal. Ptr : P1.Acc_All := new Tucker_Type'Class (Tucker_Type'(Constructor)); -- Now we have an object of an abstract type, with the tag of the -- abstract type. end P2; **************************************************************** From: Robert A. Duff Sent: Monday, November 15, 2004 7:21 PM Hmm. I think (I'm leaving out irrelevant words) "the type of an object created by an allocator shall not be abstract" at 3.9.3(8) saves us here. I.e. the above is still illegal, as it should be. **************************************************************** From: Randy Brukardt Sent: Monday, November 15, 2004 7:45 PM I don't think that applies: class-wide types are never abstract, and that's the type of the object that we're creating. Even if it does apply, but there are other ways to make such a call. Try: procedure Ugh (Obj : in Tucker_Type'Class); Ugh (Constructor); -- Tag-indeterminant call. Ugh could have the allocator, and there'd be no way for a legality rule to check it there! The problem is having a concrete function returning an abstract object. Once you have that, there's always a way to get that into an object. Clearly, Tucker's AI will need to avoid including them in his "null extension" definition. **************************************************************** From: Tucker Taft Sent: Monday, November 15, 2004 8:18 PM Bob's comment may save us, but it would be simpler to just fix the suggested wording of the AI to say: Modify 3.9.3(4) of AI-251: If a type inherits a subprogram corresponding to an abstract subprogram{, or a type other than a NONABSTRACT null extension inherits} [or to] a function with a controlling result, then: Modify 3.9.3(4): * Otherwise, the subprogram shall be overridden with a nonabstract subprogram {or, in the case of a private extension inheriting a NONABSTRACT function with a controlling result, have a full type that is a null extension}; ... The added word here is "NONABSTRACT" in both sentences. ****************************************************************