!standard 13.14(3/1) 07-04-04 AI05-0017-1/02 !class binding interpretation 06-11-09 !status work item 06-11-09 !status received 06-08-02 !priority High !difficulty Medium !qualifier Omission !subject Freezing and incomplete types !summary The rules that define how package bodies freeze Taft-Amendment types are modified to eliminate anomalies. !question The second sentence of 13.14(3/1) states that "A noninstance body other than a renames-as-body causes freezing of each entity declared before it within the same declarative_part". Consider the following: procedure Foo is package P1 is private type T1; end P1; package P2 is private type T2; end P2; package body P1 is separate; package body P2 is separate; begin ... end Foo; The rule here seems to say that the stub for P1 freezes both T1 and T2. T1 surely cannot be meant to be frozen here (else an incomplete type deferred to a body could never work - which would violate Dewar's rule). But freezing T2 is also problematic. Do we really want the stub for P1 to freeze T2 (and thereby make the above program illegal)? (No.) !recommendation (See Summary.) !wording Modify 13.14(3/1): The end of a declarative_part, protected_body, or a declaration of a library package or generic library package, causes freezing of each entity declared within it, except for incomplete types. {The end of a package_body, or a package_body_stub, cause freezing of the incomplete types declared within it.} A noninstance body other than a renames-as-body causes freezing of each entity declared before it within the same declarative_part {that is not an incomplete type; it only causes freezing of an incomplete type if the body is within the immediate scope of the incomplete type, or if the full_type_declaration and the incomplete_type_declaration occur in the same list of declarations.} !discussion We don't want an intervening body to freeze any deferred incomplete types, otherwise they'd be useless. Moreover, there is no need to freeze them -- their representation is not needed until their own body is encountered, because there are numerous restrictions on the usages of such types (see 3.10). So we adjust the wording to avoid unnecessary freezing. The new wording is supposed to have no effect for "ordinary" incomplete types, that is, types for which the incomplete_type_declaration and the full_type_declaration occur in the same list of declarations. For such types, a noninstance body causes freezing. The new wording is also supposed to have no effect for bodies that are within the immediate scope of the (incomplete) type. For instance, a subprogram body nested within the body of the package that declares the type causes freezing. The new wording says, however, that bodies that don't have visibility to a Taft-Amendment type do not cause freezing of this type. This means that, in the example, the stub for P1 doesn't freeze P2.T2. Similarly the beginning of the body of P1 doesn't freeze P1.T1, otherwise it would be impossible to write a Taft Amendment type. Now that the beginning of the body of P1 doesn't freeze T1, we must at a minimum ensure that the *end* of this body freezes T1. This ensures that T1 gets frozen at some point, and it preserves the equivalence between proper bodies and stubs. !corrigendum 13.14(3/1) @drepl The end of a @fa, @fa, or a declaration of a library package or generic library package, causes freezing of each entity declared within it, except for incomplete types. A noninstance body other than a renames-as-body causes freezing of each entity declared before it within the same @fa. @dby The end of a @fa, @fa, or a declaration of a library package or generic library package, causes freezing of each entity declared within it, except for incomplete types. The end of a @fa, or a @fa, cause freezing of the incomplete types declared within it. A noninstance body other than a renames-as-body causes freezing of each entity declared before it within the same @fa that is not an incomplete type; it only causes freezing of an incomplete type if the body is within the immediate scope of the incomplete type, or if the @fa and the @fa occur in the same list of declarations. !ACATS test Existing ACATS tests cover the basic cases. A C-test similar to the example in the question should be added. !appendix From: Pascal Leroy Date: Wednesday, August 2, 2006 4:36 AM The second sentence of 13.14(3/1) states that "A noninstance body other than a renames-as-body causes freezing of each entity declared before it within the same declarative_part". Taken at face value this rule would mean that the following is illegal: procedure Foo is package P is private type T; end P; package body P is type T is new Boolean; end P; begin ... end Foo; because the body of P would freeze type T, which is incompletely defined. But I am willing to abide by Dewar's Law and to pretend that the RM doesn't say this because it's absurd. However I am troubled by the effect of this same rule if two packages are involved: procedure Foo is package P1 is private type T1; end P1; package P2 is private type T2; end P2; package body P1 is separate; package body P2 is separate; begin ... end Foo; The rule here says that the stub for P1 freezes both T1 and T2. Ignore T1 since it's surely not meant to be frozen by the stub for P1. Do we really want the stub for P1 to freeze T2 (and thereby make the above program illegal)? The freezing problem can be circumvented by moving the stub for P1 before the specification of P2, but this doesn't help if the proper body of P1 needs visibility to P2. It seems to me that the wording is unnecessarily restrictive here, to the point where it might be incompatible with Ada 83 (although it's always hard to know how freezing works in Ada 83). I think that the above rule should make an exception for Taft-amendment types, although I must admit that I am nervous with the notion of tweaking the freezing rules. Comments? **************************************************************** From: Tucker Taft Date: Wednesday, August 2, 2006 6:55 AM My assumption is that freezing an incomplete view of a type has no effect. **************************************************************** From: Pascal Leroy Date: Wednesday, August 2, 2006 10:08 AM Nice theory, but there is no support for it in the words of the RM. Note in particular 13.14(17) which says "A type shall be completely defined before it is frozen". I see nothing in the freezing rules that makes special provisions for incomplete types, except for the first sentence of 13.14(3/1) which is intended to support Taft-amendment types. I don't think that you can rely on the restrictions on the usage of incomplete types in 3.10.1 to ensure that you don't have problems: you really need the freezing rules. **************************************************************** From: Tucker Taft Date: Wednesday, August 2, 2006 10:46 AM > Nice theory, but there is no support for it in the words of the RM. Note > in particular 13.14(17) which says "A type shall be completely defined > before it is frozen". I see nothing in the freezing rules that makes > special provisions for incomplete types, except for the first sentence of > 13.14(3/1) which is intended to support Taft-amendment types. Yes, sorry, I didn't mean to imply the wording was already correct. I was suggesting that what it *should* says is that incomplete views are never frozen, or freezing has no effect. And 13.14(17) should probably restrict itself to requiring that freezing a non-incomplete view shall happen only after the type is completely defined. > I don't think that you can rely on the restrictions on the usage of > incomplete types in 3.10.1 to ensure that you don't have problems: you > really need the freezing rules. Can you explain this further? What do you expect it to mean to freeze an incomplete view? Or are you saying that there should never be an incomplete view visible inside a body? That is already possible in Ada 95, because a child has visibility on the private part of its parent where there might be an incomplete type declaration. Freezing is supposed to ensure that the layout has been decided before you start generating code that depends on the layout. But for incomplete views, we don't know anything about the type, except perhaps that it is tagged, and all we can do with it is manipulate an access value designating the incomplete view. Is freezing relevant here? **************************************************************** From: Pascal Leroy Date: Wednesday, August 2, 2006 11:09 AM > Yes, sorry, I didn't mean to imply the wording was already > correct. I was suggesting that what it *should* says is that > incomplete views are never frozen, or freezing has no effect. > And 13.14(17) should probably restrict itself to requiring > that freezing a non-incomplete view shall happen only after > the type is completely defined. I'd be happy to go that way for Taft-amendment types, which come with strong restrictions, and are mostly invisible except for child units. I'd be reluctant to do it for all incomplete types, see below. > Can you explain this further? What do you expect it to > mean to freeze an incomplete view? Or are you saying that > there should never be an incomplete view visible inside a > body? That is already possible in Ada 95, because a child > has visibility on the private part of its parent where there > might be an incomplete type declaration. > > Freezing is supposed to ensure that the layout has > been decided before you start generating code that depends > on the layout. But for incomplete views, we don't > know anything about the type, except perhaps that it > is tagged, and all we can do with it is manipulate an access > value designating the incomplete view. Is freezing relevant here? One delicate problem with (tagged) incomplete tags is that you need to know the location of the tag in order to generate code for a dispatching call. This is similar to a dereferencing, except that there is stricto sensu no implicit or explicit dereferencing. In fact we added a number of restrictions in the Amendment to plug holes in this area (see 3.10.1(9.3/2) for instance, and the discussion of AI 326 in the minutes of the San Diego meeting). If a body were allowed to see an incomplete view, I would be concerned about similar problems. It seems simpler and safer to maintain the invariant that a body never sees an incomplete view. **************************************************************** From: Tucker Taft Date: Wednesday, August 2, 2006 11:36 AM But I don't see how using the freezing rules to enforce that makes sense. The only non "Taft-amendment" incomplete types potentially visible in a body would be those made visible via limited with (or am I missing something?), presumably inherited from the spec. For those, how can you use freezing rules to freeze something declared in a different library unit? My belief is that we need to have rules that properly restrict use of limited tagged types, and in particular, don't allow controlling operands to be incomplete. I don't see this as related to the freezing rules. **************************************************************** From: Pascal Leroy Date: Wednesday, August 2, 2006 11:56 AM > But I don't see how using the freezing rules to enforce > that makes sense. The only non "Taft-amendment" incomplete > types potentially visible in a body would be those made > visible via limited with (or am I missing something?), One of us is confused. Consider: procedure Main is type T is tagged; procedure P is separate; type T is tagged null record; ... end Main; I believe you said earlier that the stub for P should not freeze T. In this case, I'd be concerned about someone in the body of P having to know the location of the tag to perform a dispatching call (I don't have a precise example, though). > My belief is that we need to have rules that properly > restrict use of limited tagged types, and in particular, > don't allow controlling operands to be incomplete. I don't > see this as related to the freezing rules. (You mean "incomplete tagged types" above, or else I am lost.) Currently the only rule that tries to prevent incomplete controlling operands is 3.10.1(9.3), which is specific to Taft-amendment types. When this rule was designed, I was under the impression that Taft-amendment types were the only possible cause of trouble, because the freezing rules would catch any attempt to make a dispatching call for other incomplete types. This may have been wrong, and it is possible that there are problems specific to limited views, but for vanilla incomplete types I think that the freezing rules help a lot. **************************************************************** From: Robert I. Eachus Date: Wednesday, August 2, 2006 1:34 PM Summary: Probably not worth reading all of this, but I wrote it to get my thoughts straight. It turns out that the only real problems occur with Taft amendment types, and they can be resolved by saying that a package body causes freezing, but at the end of the body itself. Things inside the package body can cause earlier freezing. Pascal Leroy wrote: >The second sentence of 13.14(3/1) states that "A noninstance body other >than a renames-as-body causes freezing of each entity declared before it >within the same declarative_part". > >Taken at face value this rule would mean that the following is illegal: > > procedure Foo is > package P is > private > type T; > end P; > > package body P is > type T is new Boolean; > end P; > begin > ... > end Foo; > >because the body of P would freeze type T, which is incompletely defined. >But I am willing to abide by Dewar's Law and to pretend that the RM >doesn't say this because it's absurd. > >However I am troubled by the effect of this same rule if two packages are >involved: > > procedure Foo is > package P1 is > private > type T1; > end P1; > package P2 is > private > type T2; > end P2; > > package body P1 is separate; > package body P2 is separate; > begin > ... > end Foo; > >The rule here says that the stub for P1 freezes both T1 and T2. Ignore T1 >since it's surely not meant to be frozen by the stub for P1. Do we really >want the stub for P1 to freeze T2 (and thereby make the above program >illegal)? The freezing problem can be circumvented by moving the stub for >P1 before the specification of P2, but this doesn't help if the proper >body of P1 needs visibility to P2. > >It seems to me that the wording is unnecessarily restrictive here, to the >point where it might be incompatible with Ada 83 (although it's always >hard to know how freezing works in Ada 83). I think that the above rule >should make an exception for Taft-amendment types, although I must admit >that I am nervous with the notion of tweaking the freezing rules. I understand the rest of the discussion, but let's not lose the original reason for the question. It seems to me that there are two questions here. The first example can easily be dealt with by stating explicitly (or assuming) that the freezing caused by a package body is an effect of the unit as a whole. Thus a freezing, or a definition, or a representation pragma can occur within a body. However a better rule would be that a package body freezes any objects named in the body. The issue in the second case is more difficult. Ignoring the Taft amendment issues for the moment, we have a separate unit causing freezing. Where separate units are involved, the 'better rule' above would not work. So the issue, at least to me, seems to be a tradeoff between a desire to have separate unints not affect semantics, and the advantages (if any) of avoiding gratuitous freezing. Let me create an example that doesn't involve Task amendment types: package Foo is type T1 is private; package P1 is...; --left undefined for now. ... private ... type T1 is ... -- legal? (not illegal due to P1. end Foo; I can think of lots of software that looks exactly like this where P1 is used to export operations on the (non-Ada) abstract type T1. The declarations in P1 won't freeze T1, and everything is okay--until we put in a a package body. This can't occur in a package specification, but it can in a package body or subprogram body: package body Foo is type T2; ... package body P1 is...; -- operations on T1 type T2 is... -- illegal? (seems that way) end Foo; Can gratuitously freezing T2 here cause problems? Yes, but I think the right solution may be to move one declaration of T2, either the incomplete declaration or the completion. First the potential problem. The body of P1 may need to occur early so that it can make calls to subroutines that will either be hidden by the full declaration of T2, or more likely, to avoid ambiguity and name resolution issues. Why not move the incomplete declaration after the body? Taft amendment types create one situation where this is not possible, but moving the completion earlier seems to work in all cases. As for the 'better solution': There are many statements or declarations that could occur inside P1 to cause freezing of T1, I think that those rules are right and necessary, and in any case are not at issue here. In cases where P1 does not depend on T1, reording the declarations should always work. If we were back in 2003 or so, I might argue for the 'better solution' above. But since the standard has been approved as written, I think a ramification that the freezing occurs at the end of the body is the best solution. **************************************************************** From: Edmond Schonberg Date: Wednesday, August 2, 2006 2:11 PM > Summary: Probably not worth reading all of this, but I wrote it to > get my thoughts straight. It turns out that the only real problems > occur with Taft amendment types, and they can be resolved by saying > that a package body causes freezing, but at the end of the body > itself. Things inside the package body can cause earlier freezing. Changing the freezing rules in such a radical way at this point is totally out of the question. For the last 10 years (make that 11) we have read the freezing rules to mean that before analysis of a body can start, all previous entities in the current declarative part are frozen. The current problem involves only incomplete types that are limited views, and I have not yet seen an example that would not be illegal because of other rules. If there is a problem, it must have a very local fix. **************************************************************** From: Pascal Leroy Date: Thursday, August 3, 2006 3:05 AM I actually think that the problems have to do with Taft-amendment types, not with types in limited views. But other than that, I entirely agree with Ed. We want localized fixes to plug holes, not a grandiose redesign of the freezing rules (such a redesign was already out of bounds for the 2005 Amendment, btw, so I would have taken the same position in 2003). **************************************************************** From: Tucker Taft Date: Wednesday, August 2, 2006 5:01 PM > One of us is confused. I think I was. > ... Consider: > > procedure Main is > type T is tagged; > procedure P is separate; > type T is tagged null record; > ... > end Main; > > I believe you said earlier that the stub for P should not freeze T. In > this case, I'd be concerned about someone in the body of P having to know > the location of the tag to perform a dispatching call (I don't have a > precise example, though). I agree that there is no need to rock the boat on this one, and the above, if you omit the "is tagged" would clearly be syntactically possible in Ada 95, and it would presumably freeze T, and hence violate the freezing rules. So perhaps all we are worried about are "Taft-amendment" (TA) types, and a desire that they *not* be frozen by bodies occurring outside the package body. We certainly know that that if a TA type is declared in the private part of P, then the start of the body of P itself doesn't freeze TA, or else how could TAs work at all? The last sentence of 13.4(3/1) [he means 13.14(3/1) - Ed.] is currently: A noninstance body other than a renames-as-body causes freezing of each entity declared before it within the same declarative_part. This is clearly broken as written, given the following piece of Ada 95 code: procedure Main is package P is type A is private; private type T; type A is access T; end P; package body P is -- does this freeze P.T? type T is new Integer; end P; begin null; end Main; Clearly we need to augment the last sentence of 13.4(3/1) with something like: ... of each entity declared before it within the same declarative_part, other than an incomplete type declared within a preceding package specification. (or perhaps "nested" rather than "preceding"?) >> My belief is that we need to have rules that properly >> restrict use of limited tagged types, and in particular, >> don't allow controlling operands to be incomplete. I don't >> see this as related to the freezing rules. > > (You mean "incomplete tagged types" above, or else I am lost.) > > Currently the only rule that tries to prevent incomplete controlling > operands is 3.10.1(9.3), which is specific to Taft-amendment types. When > this rule was designed, I was under the impression that Taft-amendment > types were the only possible cause of trouble, because the freezing rules > would catch any attempt to make a dispatching call for other incomplete > types. This may have been wrong, and it is possible that there are > problems specific to limited views, but for vanilla incomplete types I > think that the freezing rules help a lot. Fair enough. Do you agree with the above augmentation (or something like it) for the last sentence of 13.4(3/1)? I think my concern about "limited-with" types is a red herring, as these are not declared within the "same" declarative_part. **************************************************************** From: Pascal Leroy Date: Thursday, August 3, 2006 5:27 AM > I agree that there is no need to rock the boat on this > one, and the above, if you omit the "is tagged" would > clearly be syntactically possible in Ada 95, and it would > presumably freeze T, and hence violate the freezing rules. So > perhaps all we are worried about are "Taft-amendment" (TA) > types, and a desire that they *not* be frozen by bodies > occurring outside the package body. We certainly know that > that if a TA type is declared in the private part of P, then > the start of the body of P itself doesn't freeze TA, or else > how could TAs work at all? I agree with all this. > Clearly we need to augment the last sentence of 13.4(3/1) > with something like: > > ... of each entity declared before it within the same > declarative_part, other than an incomplete type declared > within a preceding package specification. Yes, that change, combined with the existing completion rules of 3.10.1(3), seem to achieve what we want. > I think my concern about "limited-with" types is a red herring, > as these are not declared within the "same" declarative_part. I think so too. However, you got me thinking on the topic of dispatching calls, and I ended up with the following example: package Stt is type T is tagged; function F return access T'Class; function G (X : access T) return Integer; I : Integer := G (F); type T is tagged null record; end Stt; The call to G is dispatching, so we better know the location of the tag for type T. But I am not sure if 13.14(14) helps, because I have always interpreted "subtype of its profile" to mean the subtypes of the parameters or result (access T or access T'Class here) not any random subtype that happens to appear in the profile (like T here). And 3.10.1(10/2) surely doesn't help because there is no prefix of an incomplete view in sight. It seems to me that the latter paragraph should be augmented to deal with the "very implicit dereference" associated with fetching the tag in a dispatching call. **************************************************************** From: Robert I. Eachus Date: Thursday, August 3, 2006 10:33 AM >The call to G is dispatching, so we better know the location of the tag >for type T. > As written compilers will probably replace the call to G to initialize I with a raise of Program_Error. I can't think offhand of an example where G is a generic instance and there is a problem, but there may be. **************************************************************** From: Pascal Leroy Date: Friday, August 4, 2006 2:22 AM > As written compilers will probably replace the call to G to > initialize I with a raise of Program_Error. Maybe, maybe not, but that's irrelevant. The rules of the language should not depend on a compiler performing (or not performing) an optimization. Furthermore, even if a compiler performs the optimization that you describe, it might well do it in two phases: first generate the call, then do some analysis of elaboration checks. If the code generator fails during the first phase because it cannot locate the tag, we have a problem. **************************************************************** From: Edmond Schonberg Date: Friday, August 4, 2006 9:25 AM Which is exactly a problem I found in GNAT when trying your example. We now detect the illegal use of the access parameter further upstream, when analyzing the dispatching call. An instructive example!! Fortunately the fix is extremely localized, and the freezing rules are unaffected. **************************************************************** From: Robert A. Duff Date: Friday, August 4, 2006 9:18 AM > > > package Stt is > > > type T is tagged; > > > function F return access T'Class; > > > function G (X : access T) return Integer; > > > I : Integer := G (F); > > > type T is tagged null record; > > > end Stt; > > > > As written compilers will probably replace the call to G to > > initialize > > I with a raise of Program_Error. > > Maybe, maybe not, but that's irrelevant. The rules of the language should > not depend on a compiler performing (or not performing) an optimization. There were cases like that in Ada 83. I think X.all was legal, where X is of an incomplete type, and the compiler had to deduce that X must be null to avoid tripping over itself. Or something like that. Anyway, we (tried to) eliminate such cases. General principle: it's silly to require a compiler to deduce some fact, and respond by generating code to raise an exception. If we're going to require such a deduction, the response should be a (legality) error message. And it's our duty to write down what needs to be deduced, rather than leaving it implicit. **************************************************************** From: Tucker Taft Date: Thursday, August 3, 2006 10:42 AM > The call to G is dispatching, so we better know the location of the tag > for type T. But I am not sure if 13.14(14) helps, because I have always > interpreted "subtype of its profile" to mean the subtypes of the > parameters or result (access T or access T'Class here) not any random > subtype that happens to appear in the profile (like T here). The "subtypes of a profile" are defined in 6.1(26-29) to include the designated subtype of an access parameter or result. So calling a subprogram with an access parameter actually freezes the designated subtype as well (which is a bit of a surprise to me -- not clear how this relates to T.A. types, or limited with's for that matter). **************************************************************** From: Pascal Leroy Date: Friday, August 4, 2006 2:12 AM Ah, good point, I missed that. Yes, it's a bit surprising, but I guess it helps a lot with dispatching. **************************************************************** From: Randy Brukardt Date: Friday, August 4, 2006 2:08 PM Absolutely. It's surely necessary that any type that can be dispatched upon is frozen (else we don't necessarily know where the tag is), and this rule ensures that. We have rules that make dispatching calls illegal if we can't locate the tag (T.A. types, for instance). I think a more interesting question is how this relates to non-dispatching calls (i.e. classwide calls), where we explicitly allowed access-to-incomplete. (This means that you don't have to have junk withs for packages that were limited withed just to see the full type so you can make a call that doesn't depend on the full type anyway.) OTOH, the interesting case is types declared elsewhere (limited with), and such types are presumed frozen already; if such calls are illegal for T.A. types, it's not a huge deal. ****************************************************************