!standard 4.3.1(14) 08-10-16 AI05-0115-1/02 !standard 4.3.2(5/2) !class binding interpretation 08-10-15 !status work item 08-10-15 !status received 08-07-25 !priority Medium !difficulty Medium !qualifier Omission !subject Aggregates with components that are not visible !summary An aggregate is illegal if it has components that are not visible. !question Consider the following: package Pak1 is type T1 is tagged private; private type T1 is tagged record C1 : Integer; end record; end Pak1; with Pak1; package Pak2 is type T2 is new Pak1.T1 with record C2 : Integer; end record; end Pak2; with Pak2; package Pak1.Pak3 is type T3 is new Pak2.T2 with record C3 : Integer; end record; procedure Foo; end Pak1.Pak3; package body Pak1.Pak3 is procedure Foo is R : T3; N : Integer; begin N := R.C1; -- (A: Error.) R := (C1 => 1, C2 => 2, C3 => 3); -- (B: Legal? No.) R := (C2 => 2, C3 => 3, others => 1); -- (C: Legal? No.) R := (others => 4); -- (D: Legal? No.) end Foo; end Pak1.Pak3; (A) is illegal by 7.3.1(4). Although the component F1 of the grandparent type has become visible in the body of Pak1.Pak3, 7.3.1(4) speaks about "additional characteristics of the parent type" becoming visible, and there isn't any place where the hidden component F1 of the type T2 becomes visible. AI95-0157 confirms this interpretation. That means that (B) is illegal, because one of the component names is not visible. But what about (C) and (D), which define a value for the component without mentioning its name? !recommendation (See Summary.) !wording Modify 4.3.1(14): If the type of a record_aggregate is a record extension, then it shall be a descendant of a record type, through one or more record extensions (and no private extensions). {Moreover, for each of those record extensions R, there shall exist some place in the immediate scope of the type R where the parent type of R is a record or record extension.} AARM Note: The last sentence prevents aggregates where not all of the components are visible, even though all of the types are full views at the point of the aggregate. For example: package Pak1 is type T1 is tagged private; private type T1 is tagged record C1 : Integer; end record; end Pak1; with Pak1; package Pak2 is type T2 is new Pak1.T1 with record C2 : Integer; end record; end Pak2; with Pak2; package Pak1.Pak3 is type T3 is new Pak2.T2 with record C3 : Integer; end record; procedure Foo; end Pak1.Pak3; package body Pak1.Pak3 is procedure Foo is R : T3; begin R := (others => 4); -- (Illegal.) end Foo; end Pak1.Pak3; By requiring that all of the parent types are record types or record extensions at the point of the derivation, we avoid this problem. Modify 4.3.2(5/2): ... The type of the extension_aggregate shall be derived from the type of the ancestor_part, through one or more record extensions (and no private extensions). {Moreover, for each of those record extensions R, there shall exist some place in the immediate scope of the type R where the parent type pf R is a record or record extension.} !discussion Logically, an aggregate is a shorthand for setting each component individually. If setting a component explicitly is illegal because of a visibility issue, then the same rule ought to apply to the equivalent aggregate. Thus all of the example aggregates are illegal. The best way to fix this is to fix 4.3.1(14) to make it clear that what matters whether the parent type was a partial view at the point of the derivation (whether the parent type is a partial view at the point of the aggregate is irrelevant). Note that a similar case can be constructed for extension aggregates: Make type T1 in the example derived from a root type, then aggregates like: R := (Root with C1 => 1, C2 => 2, C3 => 3); -- (Illegal.) R := (Root with C2 => 2, C3 => 3, others => 1); -- (Illegal.) R := (Root with others => 4); -- (Illegal.) [Editor's Note: I'm not completely convinced that there isn't some weird case where the components get declared in the body. There are such cases for operations that are declared in nested packages, but I *think* that components don't have any such cases because a nested package can't add any components to an outer type. Steve Baird may prove me wrong... The proposed wording is completely opaque. I originally proposed Moreover, the parent type of each of those record extensions shall have been a record type or record extension at the point of the record extension declaration. However, Steve Baird pointed out that this wording doesn't work for a record extension declared in the visible part of a child unit if the aggregate is given in the body of that unit -- in that case there is no problem, but this wording would make that illegal. I've attempted to patch this up by echoing the words of 7.3. But I'm not sure that that actually works, either. And it is completely inpenatrable: it doesn't explain the actual problem in any way. An alternative way of wording this would simply require that all components are visible. However, that doesn't work, because that would allow aggregates if the hidden parent type was a null record. That would be privacy breaking, as if any components were added later, the aggregate would then become illegal. One could try to work around that with wording like: A record_aggregate is illegal if any possible components of the composite value defined by the type of the aggregate are not visible or not declared. It's not clear that this is any actual improvement; it's pretty vague about "possible components". It also doesn't work for the extension aggregate case, where we only need to consider a subset of the components. A last way to solve this problem would be to allow the components to be visible in cases like those given in the question. In that case, no legality rule would be needed here. But conflicting components would have to be illegal when a type is declared, so there is some incompatibility. [Such a rule also would eliminate the issue raised in an AI that hasn't been written yet if it also applied to inherited subprograms. But that would be more incompatible.] End Editor's Note.] !corrigendum 4.3.1(14) @drepl If the type of a @fa is a record extension, then it shall be a descendant of a record type, through one or more record extensions (and no private extensions). @dby If the type of a @fa is a record extension, then it shall be a descendant of a record type, through one or more record extensions (and no private extensions). Moreover, for each of those record extensions @i, there shall exist some place in the immediate scope of the type @i where the parent type of @i is a record or record extension. !corrigendum 4.3.2(5/2) @drepl If the @fa is a @fa, it shall denote a specific tagged subtype. If the @fa is an @fa, it shall not be dynamically tagged. The type of the @fa shall be derived from the type of the @fa, through one or more record extensions (and no private extensions). @dby If the @fa is a @fa, it shall denote a specific tagged subtype. If the @fa is an @fa, it shall not be dynamically tagged. The type of the @fa shall be derived from the type of the @fa, through one or more record extensions (and no private extensions). Moreover, for each of those record extensions @i, there shall exist some place in the immediate scope of the type @i where the parent type of @i is a record or record extension. !ACATS Test B-Tests for examples like these should be constructed. !appendix !topic Component visibility question !reference 4.3.1(14), 7.3.1(4), AI95-157 !from Adam Beneschan 08-07-25 !discussion I'm having trouble figuring out how the visibility and private-operation rules apply in this case: package Pak1 is type T1 is tagged private; private type T1 is tagged record F1 : Integer; end record; end Pak1; with Pak1; package Pak2 is type T2 is new Pak1.T1 with record F2 : Integer; end record; end Pak2; with Pak2; package Pak1.Pak3 is type T3 is new Pak2.T2 with record F3 : Integer; end record; procedure Foo; end Pak1.Pak3; package body Pak1.Pak3 is procedure Foo is R : T3; N : Integer; begin N := R.F1; -- (A) R := (F1 => 1, F2 => 2, F3 => 3); -- (B) R := (F2 => 2, F3 => 3, others => 1); -- (C) R := (others => 4); -- (D) end Foo; end Pak1.Pak3; I'm pretty sure that the component selection in (A) is illegal, based on the wording in 7.3.1(4). Although the component F1 of the grandparent type has become visible in the body of Pak1.Pak3, 7.3.1(4) speaks about "additional characteristics of the parent type" becoming visible, and there isn't any place where the hidden component F1 of the type T2 becomes visible. I think this case is covered by AI95-157 also. (B) is a rather different case. Normally, an aggregate would be illegal for a derived type if the ultimate ancestor were a private type (rather than a record type), or if any private extensions were involved (4.3.1(14)). In this case, though, there are no private extensions; and at the point where statement (B) occurs, the type is derived from a record type, not a private type, since the full view of T1 is available and T1 is a record type. Should 4.3.1(14) somehow be interpreted so that, in this case, T3 is still considered a descendant of a "private type" rather than a "record type" since there's an intermediate parent type for which "additional characteristics" have not become visible? Even if the aggregate in (B) is not illegal by 4.3.1(14), it would still seem to be illegal since one of the component names, F1, is not actually a visible component of the type (for the same reason as in (A)). Assuming that's the case, then what about (C) and (D)? Are they illegal or not? I'm guessing that AI95-157 applies to (B), (C), and (D) also, and that any record aggregate of type T3 is illegal regardless of what components are or are not named using named notation. [Extension aggregates are still OK.] But it's not 100% clear to me that the principle applies, given that the wording of 4.3.1(14) doesn't seem to use any of the terms that 7.3.1(4) or AI95-157 discuss. (Is the ability to specify an aggregate an "operation" of a record type? I don't think the RM answers that definitively.) **************************************************************** From: Adam Beneschan Sent: Friday, August 8, 2008 5:25 PM Since I posted this, I found that this question affects a publicly available open-source Ada suite, PolyORB. If I'm right, then the Ada source for that suite is illegal (but GNAT apparently does not catch it). If possible, I'd appreciate some opinion on whether this code is actually illegal, before I make a bug report to the maintainers. Here are the relevant constructs from the source: package PolyORB.SOAP_P.Message is type Object is tagged private; private type Object is tagged record Name_Space : Unbounded_String; Wrapper_Name : Unbounded_String; P : SOAP_P.Parameters.List; end record; end PolyORB.SOAP_P.Message; package PolyORB.SOAP_P.Message.Response is type Object is new Message.Object with null record; end PolyORB.SOAP_P.Message.Response; with PolyORB.SOAP_P.Message.Response.Error; package body PolyORB.SOAP_P.Message.XML is (... in a Load_Response function:) return new Message.Response.Object' -- LEGAL??? (Null_Unbounded_String, S.Wrapper_Name, S.Parameters); end PolyORB.SOAP_P.Message.XML; If my interpretation is correct, then the components of the derived type Message.Response.Object (and, indeed, the fact that it's a record) do not become visible until a point in PolyORB.SOAP_P.Message.Response where the private part of PolyORB.SOAP_P.Message is visible, i.e. the private part of PolyORB.SOAP_P.Message.Response. And, since the private part of PolyORB.SOAP_P.Message.Response is not at all visible inside PolyORB.SOAP_P.Message.XML, the inherited components of Message.Response.Object are not visible either, and therefore the aggregate should not be legal. Thoughts? **************************************************************** From: Randy Brukardt Sent: Friday, August 8, 2008 7:34 PM Without considering the case very fully, it seems clear that the intent of 4.3.1(14) is that a record aggregate is not allowed for a record extension if any of the ancestors is a partial view rather than a record of some sort. And clearly this needs to be "frozen" once an extension is done which is out of scope of the original type, just as for components. So I think that an aggregate should be illegal (given the current rules; I would have preferred that we find a way to allow aggregates for private types, but since that failed we shouldn't be allowing funny holes). But there clearly is no current reason to think that the Standard answers this question at all. The only reason to think this way is that the model of AI95-0157 should apply here, too, and that will require a new rule of some sort. So it's probably impossible to say definitely that a compiler that works some other way is wrong. Whether to send in a bug report is therefore impossible to answer. **************************************************************** A brief summary of a private e-mail discussion between Steve Baird, Tucker Taft, and Randy Brukardt in October 2008 [discussing version /01 of this AI]. Steve: I think the following should be legal. Would your wording cause it to be rejected? package Parent is type T0 is tagged null record; type T1 is new T0 with private; private type T1 is new T0 with record F1 : Integer; end record; end Parent; package Parent.Child is type T2 is new T1 with null record; private X2 : T2 := (F1 => 37); end Parent.Child; The point is that, at the point of derivation, T2 was derived from a private extension. Randy: You are correct, so my proposed wording doesn't work. Apparently, there isn't any idea that works (basing it on component visibility breaks privacy, because it would allow null records, and basing it on potential component visibility is just too goofy for words). Tucker: One interesting way to illustrate the problem would be for Pak2.T2 to have its *own* "C1" component, which would be legal. Clearly the C1 component from T1 should never be visible in T3, even in its own body. Randy: True. That would be a bad example here, though, because it would make the named notation aggregate illegal. I suppose one alternative would be to step back further and think about if we want to eliminate the privacy instead -- that is (given the other issues that Adam raised), perhaps we need to make the components visible in this case (and then we don't need a legality rule). Similarly with the inherited operations. But that would be incompatible. In an example like the one Tucker mentions, the declaration of T3 would have to be illegal as it would declare two components with the same name. That seems OK to me (it would be a better incompatibility than some sort of boujalias effect), but it clearly would break some code. ****************************************************************