Version 1.2 of ai05s/ai05-0115-1.txt
!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; --
R := (C1 => 1, C2 => 2, C3 => 3); --
R := (C2 => 2, C3 => 3, others => 1); --
R := (others => 4); --
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); --
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); --
R := (Root with C2 => 2, C3 => 3, others => 1); --
R := (Root with others => 4); --
[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)
Replace the paragraph:
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).
by:
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.
!corrigendum 4.3.2(5/2)
Replace the paragraph:
If the ancestor_part is a subtype_mark, it shall denote a specific
tagged subtype. If the ancestor_part is an expression, it shall
not be dynamically tagged. 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).
by:
If the ancestor_part is a subtype_mark, it shall denote a specific
tagged subtype. If the ancestor_part is an expression, it shall
not be dynamically tagged. 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 of R 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.
****************************************************************
Questions? Ask the ACAA Technical Agent