!standard 3.9.3(8/2) 07-10-24 AI05-0073-1/02 !standard 3.9.3(10/2) !class binding interpretation 07-10-24 !status work item 07-10-24 !status received 07-10-09 !priority Medium !difficulty Medium !qualifier Error !subject Questions about functions returning abstract types !summary Generic functions cannot have abstract result types or access result types designating an abstract type. A function with an access result type designating an abstract type must be abstract. There is a check that a function with an access result type designating a specific tagged type returns a value which designates a value of that specific tagged type. For a tagged type declared in a visible part, a primitive function with a controlling access result cannot be declared in the private part. !question (1) Consider: generic type T (<>) is abstract tagged private; function Gft (X : T) return T; This appears to be legal, because a generic function is not a function and 3.9.3(8/2) only talks about functions. Moreover, it is possible to write a body for this generic by using an abstract formal subprogram. But it is not useful. Also note that a similar package: generic type T (<>) is abstract tagged private; package GP is function Ft (X : T) return T; end GP; Is illegal because it violates 3.9.3(8/2). Should the generic also be illegal? (Yes.) (2) Consider: package Pkg is type T (<>) is abstract tagged private; function Ft (X : in T) return access T; end Pkg; This is legal, as "access T" is surely not abstract. However, this function can only return null or values designating types derived from T. If the latter is meant, it would have been better to write: function Ft (X : in T) return access T'Class; Also note that this function has a controlling access result. It would be obnoxious for it to return a result designating an object with a different type. For instance, if we have: function Empty return access Set; function Union(S1, S2 : access Set) return Access Set; procedure Assign(S1, S2 : access Set); ... Assign(X'Access, Union(Empty, Y'Access)); we don't want Empty returning a value that designates some type derived from Set, as that would imply that we should have a dispatching tag check failure -- but this call is statically bound! What is the intent here? (3) 3.9.3(10) has rules that prevent a visible tagged type from having "hidden" routines that would require overriding for a derived type. The Amendment changed the rules for "require overriding" [3.9.3(4-6/2)] to include functions with controlling access results. But it doesn't make a corresponding change in 3.9.3(10). What is supposed to happen when such types are derived? !recommendation (See Summary.) !wording Add to the end of 3.9.3(8/2): If a function has an access result type designated an abstract type, then the function shall be abstract. A generic function shall not have an abstract result type or an access result type designating an abstract type. Modify 3.9.3(10) as follows: For an abstract type declared in a visible part, an abstract primitive subprogram shall not be declared in the private part, unless it is overriding an abstract subprogram implicitly declared in the visible part. For a tagged type declared in a visible part, a primitive function with a controlling result {or a controlling access result} shall not be declared in the private part, unless it is overriding a function implicitly declared in the visible part. Add a new paragraph after 6.5(8/2): If the result subtype of the function is defined by an access_definition designating a specific access type T, a check is made that the result value is null or the tag of the object designated by the result value identifies T. Constraint_Error is raised if this check fails. !discussion In order to fix the tag-indeterminate dispatching problem, we require that the designated type of the result of such a function has the tag of the designated specific tagged type. This tag check on return is similar to the one that was be made for return-by-reference types in Ada 95. This makes sense, since access result types are essentially a replacement for that functionality. With this tag check, the only thing that can be returned from a function with an access result type designating an abstract type is null. That's not useful, so we make such functions illegal, just like functions that directly return an abstract type. --!corrigendum 7.6.1(17.1/1) !ACATS Test !appendix From: Randy Brukardt Sent: Tuesday, October 9, 2007 2:25 PM I received a complaint about a test case in a new ACATS test. The example was generic type T (<>) is abstract tagged private; function Gft (X : T) return T; The complaint said that this violates 3.9.3(8): "If the result type of a function is abstract, then the function shall be abstract". Seems straightforward. But then in the interests of completeness he continued... "Even if you argued that it 3.9.3(8) didn't apply because a "generic function" is not a function, it seems like this ought to be illegal anyway, because I don't see how you could legally write a body for this generic function since you couldn't write a legal RETURN statement, right?" That makes this a *whole lot* more interesting. First of all, he surely is right that a "generic function" is not a "function". So it doesn't appear that 3.9.3(8) applies directly (although it does apply to any instances). Second, his contention that you can't write a body is wrong; you could use an abstract formal subprogram to provide the value for the return statement. Consider: generic type T (<>) is abstract tagged private; with function Constructor (X : in T) return T is abstract; function Gft (X : in T) return T; function Gft (X : in T) return T is begin return Constructor(X); end Gft; Since Constructor is dispatching, it is legal for it to be abstract. So this doesn't seem to violate any rules. But it does seem to be useless (you could just call Constructor directly), and you'd have to instantiate with a specific type (else the instantiation would violate 3.9.3(8)), and Constructor would have to be primitive. It seems like it would be a lot of work to implement for little gain, especially since only concrete types would work (in which case, why the type is declared abstract has to be questioned). Third, note that a similar package is clearly illegal: generic type T (<>) is abstract tagged private; with function Constructor (X : in T) return T is abstract; package GPkg is function Ft (X : in T) return T; -- Error: Not abstract. end GPkg; ...as Ft is clearly a function. So it's pretty clear that 3.9.3(8) should apply to generic functions; probably we just need to clarify 3.9.3(8) to make that clear. Or is there some way to say that this applies anyway? --------- In thinking about ways to fix the test, the first thought that came to mind was to make the result an anonymous access type: generic type T (<>) is abstract tagged private; function Gft (X : T) return access T; or the more usual case: generic type T (<>) is abstract tagged private; package GPkg is function Ft (X : in T) return access T; end GPkg; and even the basic case: package Pkg is type T (<>) is abstract tagged private; function Ft (X : in T) return access T; end Pkg; That clearly does not violate 3.9.3(8): "access T" is not abstract. And the subprogram is still primitive for "access T". But should we be allowing this? You cannot get an object of type access T, because allocators and object declarations are illegal for T. You could convert an access to class-wide to this result type, but the tag check would necessarily fail. And you could return null, of course. But what good are those possibilities? So it looks like any legitimate use is impossible. Given that we want to detect errors early, it seems to me that 3.9.3(8) should also apply to access result types that designate abstract types. Most likely, this is something that we didn't think about when we added access result types. I don't see anything about abstract operations in AI-318-2 or AI-416. Thoughts? **************************************************************** From: Randy Brukardt Sent: Tuesday, October 9, 2007 3:59 PM I think it should be illegal, because inside the generic body, "Gft" denotes the current instance, which in this case would violate the rule that a function not have an abstract result type. As far as access T, converting from access T'Class to access T doesn't involve a tag check, and there is no tag check associated with returning a value of type "access T." I *do* think there might be a problem in general here in the general vicinity of tag-indeterminate calls on functions with result type "access T." There is a normal assumption that tag indeterminate calls are guaranteed to return an object whose tag matches that of the controlling tag used to perform the dispatching. Since we don't do a tag check on functions returning access T, that seems like a bit of a problem: function Empty return access Set; function Union(S1, S2 : access Set) return Access Set; procedure Assign(S1, S2 : access Set); ... Assign(X'Access, Union(Empty, Y'Access)); Is there any guarantee that the Empty associated with type Hashed_Set, say, returns a value designating an object with tag identifying Hashed_Set? If we were to require that functions with return type "access T" (with T tagged) actually return a value designating an object with tag = T'Tag, then it would make sense to disallow non-abstract functions returning access . **************************************************************** From: Randy Brukardt Sent: Tuesday, October 9, 2007 7:06 PM ... > As far as access T, converting from access T'Class to > access T doesn't involve a tag check, and there is > no tag check associated with returning a value of > type "access T." Humm, I guess you are right. I was thinking that T'Class => T involved a tag check, but upon rereading the manual, I see it does not. As you imply below, though, returning a tagged object from a function of a specific type implies a tag check, and the fact that we don't have one here seems to imply that something is wrong. > I *do* think there might be a problem in general here > in the general vicinity of tag-indeterminate calls on > functions with result type "access T." There is > a normal assumption that tag indeterminate calls > are guaranteed to return an object whose tag matches > that of the controlling tag used to perform the > dispatching. Since we don't do a tag check on > functions returning access T, that seems like a > bit of a problem: > > function Empty return access Set; > function Union(S1, S2 : access Set) return Access Set; > procedure Assign(S1, S2 : access Set); > ... > Assign(X'Access, Union(Empty, Y'Access)); > > Is there any guarantee that the Empty associated with > type Hashed_Set, say, returns a value designating an object > with tag identifying Hashed_Set? No. In any case, it is inconsistent with directly returned objects. I'm not sure if that is a real problem or not; I suppose it again comes down to tag checks. Compilers don't expect to have to make a tag check on Empty, but this example seems to imply one is necessary on the call. And that's different than regular checks. We could move the check into the function (there used to be a similar check for limited types in Ada 95; it was removed in the Amendment since build-in-place eliminated the need). Not sure if that is much of a difference; the only issue seems to be where the check is done. (And doing the check at the call site is likely to allow more flexibility in non-dispatching contexts.) But I'm not sure what makes sense here. > If we were to require that functions with return type > "access T" (with T tagged) actually return a value > designating an object with tag = T'Tag, then it > would make sense to disallow non-abstract functions > returning access . Yes, that makes sense. **************************************************************** From: Tucker Taft Sent: Wednesday, October 10, 2007 12:42 PM > I'm not sure if that is a real problem or not; I suppose it again comes down > to tag checks. Compilers don't expect to have to make a tag check on Empty, > but this example seems to imply one is necessary on the call. And that's > different than regular checks. We could move the check into the function > (there used to be a similar check for limited types in Ada 95; it was > removed in the Amendment since build-in-place eliminated the need). Not sure > if that is much of a difference; the only issue seems to be where the check > is done. (And doing the check at the call site is likely to allow more > flexibility in non-dispatching contexts.) But I'm not sure what makes sense > here. I think we need to reintroduce a tag check on return for functions with return-type "access T" if T is tagged. It would be very similar to the check we had in Ada 95 on return-by-reference. This also makes sense because we have to some extent said that what was "return Lim_Type" in Ada 95 becomes "return access Lim_Type" in Ada 2005. Hence, one should not be surprised if the tag check is carried over. **************************************************************** From: Randy Brukardt Sent: Thursday, October 25, 2007 12:42 PM I opened the test objectives for 3.9.3 in order to check that I had remembered to retest them for interface type (I had), and happened to notice another problem. 3.9.3(10) has rules that prevent a visible tagged type from having "hidden" routines that would require overriding for a derived type. We changed the rules for "require overriding" [3.9.3(4-6/2)] to include functions with controlling access results. But we didn't make a corresponding change in 3.9.3(10). Oops. I'll add this to the already open AI for abstract screwups, AI05-0073-1. ****************************************************************