From bobduff@dsd.camb.inmet.com Thu Sep 22 10:46:45 1994
Return-Path:
Received: from inmet.camb.inmet.com by dsd.camb.inmet.com (4.1/SMI-4.1)
id AA15331; Thu, 22 Sep 94 10:46:45 EDT
Received: from dsd.camb.inmet.com by inmet.camb.inmet.com (4.1/SMI-4.1)
id AA10276; Thu, 22 Sep 94 10:46:44 EDT
Received: by dsd.camb.inmet.com (4.1/SMI-4.1)
id AA15317; Thu, 22 Sep 94 10:46:20 EDT
Date: Thu, 22 Sep 94 10:46:20 EDT
From: bobduff@dsd.camb.inmet.com (Bob Duff)
Message-Id: <9409221446.AA15317@dsd.camb.inmet.com>
To: bertier@alsys.fr
Cc: ada9x-mrt@inmet.com
In-Reply-To: <9409140817.AA17228@altair.alsys_sa> (bertier@alsys.fr)
Subject: Re: undeclared inherited operations
!topic undeclared inherited operations
!reference AARM-7.3.1(7.p); 5.0
!reference AARM-7.3.1(6);5.0
!reference AARM-12.3(18.a);5.0
!reference 94-4678.a Antoine Bertier
!reference 94-4683.a Bob Duff 94-9-2
!reference 94-4742.a Antoine Bertier 94-9-14
!from Bob Duff
<>
!discussion
> > > So at the place where (1) occurs, the inherited Op2 is NOT DECLARED but yet
> > > you say that it is OVERRIDDEN (which implies that it is visible... so it
> > > should be callable too...).
>
> > But the inherited Op2 *is* implicitly declared *somewhere*. Again,
> > perhaps "never declared" or some such thing would be clearer.
>
> The next question is: can the earliest place be the body of an instance ?
>
> So in practice is there a difference between:
>
> package P is
> package Inner is
> type T is null record;
> private
> procedure Op (X : T); -- (1)
> end Inner;
>
> type NT is new Inner.T;
> procedure Op(X : NT); -- (2) overrides (1)
> end P;
>
> and
> generic
> package G is
> type T is null record;
> private
> procedure Op(X : T); -- (1 g)
> end G;
>
> with G;
> package P is
> package Inner is new G; -- (1 i)
>
> type NT is new Inner.T;
> procedure Op(X : NT); -- (2) overrides (1 i) ????
No, this one does not override.
> end P;
Yes, there is a difference between the above two examples.
> Somehow I would be surprised if in the second example (2) did not override
> (1 i)
Yes, that is somewhat surprising. However, there's a difference between
the two examples: In the first (non-generic) example, the body if Inner
is physically inside the body of P, so the programmer sees them both
together -- Inner and P "know about" each other. In the second
(generic) example, on the other hand, the body of G and the body of P
are physically separate from each other, and are likely to be written by
two different programmers. The programmer of P shouldn't have to know
about the private part of G in order to know whether he is accidentally
overriding something.
> ...On the other hand it is not clear that the body of the instance is
> _after_ the declaration of NT, nor that implicit declarations occur
> within the body of the instance. Add to this that the ramification
> 12.3(18.a) gives a similar example where the private operations declared
> in the generic do not override an inherited operation...
All true. The body of the instance is conceptually at the same place as
the instantiation (never mind that that would be syntactically illegal
if it wasn't an instance).
> So when I stated my understanding as:
>
> > - if the parent type is declared in an ancestor unit or in an inner
> > package then the inherited operation which are not implicitly
> > declared immediately after the derived type are nevertheless
> > overridable (but still not callable)
> >
> > - otherwise the non declared inherited operations cannot be
> > overridden (nor called) and they will never be declared when
> > inherited.
>
> was I correct in considering that it did not matter whether the inner
> package was an instance or not, or should I rather say "in an inner
> non-instance package" ?
"non-instance" is more correct, I guess.
> No matter what I said before, I don't want a complete rewriting of the
> private operations rules ;-)
And I *certainly* don't want to rewrite them! ;-)
> Antoine Bertier
> bertier@alsys.fr
- Bob
From bobduff@dsd.camb.inmet.com Thu Sep 22 10:53:37 1994
Return-Path:
Received: from inmet.camb.inmet.com by dsd.camb.inmet.com (4.1/SMI-4.1)
id AA16381; Thu, 22 Sep 94 10:53:37 EDT
Received: from dsd.camb.inmet.com by inmet.camb.inmet.com (4.1/SMI-4.1)
id AA10511; Thu, 22 Sep 94 10:53:36 EDT
Received: by dsd.camb.inmet.com (4.1/SMI-4.1)
id AA16370; Thu, 22 Sep 94 10:53:11 EDT
Date: Thu, 22 Sep 94 10:53:11 EDT
From: bobduff@dsd.camb.inmet.com (Bob Duff)
Message-Id: <9409221453.AA16370@dsd.camb.inmet.com>
To: bertier@alsys.fr
Cc: ada9x-mrt@inmet.camb.inmet.com
In-Reply-To: <9409151534.AA28414@altair.alsys_sa> (bertier@alsys.fr)
Subject: Re: Private operations: questions on examples
!topic Private operations: questions on examples
!reference RM9X-7.3.1;5.0
!reference 94-4750.a Antoine Bertier 94-9-15
!from Bob Duff
<>
!discussion
> Consider the following
OK, if I must... ;-)
> package P1 is
> type T1 is null record;
>
> procedure P(X:T1; Y : integer := 1);
> end P1;
>
> package P1.P2 is
> type T2 is new T1;
> private
> procedure P(X : T2; Y : integer := 2);
> end P1.P2;
>
> package P1.P2.P3 is
> type T3 is new T2;
> end P1.P2.P3;
>
> package body P1.P2.P3 is
> O3 : T3;
> begin
> P(O3); -- (a)
> end P1.P2.P3;
>
> with P1.P2.P3; use P1.P2.P3;
> procedure test is
> X3 : T3;
> begin
> P(X3); -- (b)
> end test;
>
> The questions (and my tentative answers) are:
> - is this example legal (yes)
Yes.
> - how many primitive subprograms has T2 (1 ???)
Well, "=" and "/=" are primitive, but I guess that's not what you meant.
T2 has two primitive P's, one of which overrides the other.
> - how many subprograms are in herited by T3 (2 ???)
Two P's.
> - what is the default parameter in the call a (2)
Correct.
> - what is the default parameter in the call b (1)
Correct.
> - what would be different if T1 was tagged and T2, T3 null extensions
> (nothing except that both calls a and b would execute the
> operation declared in P1.P2).
Correct.
> What is not clear to me is what exactly is the primitive subprogram.
> Do you have one primitive subprogram with two declarations (a public
> and a private view for instance) and two bodies, but still one
> primitive subprogram since only one of them can be called at any given
> point (and for tagged types they both dispatch to the same body). Or
> do you have two primitive subprograms, one overriding the other at
> some places, and sharing the same dispatching slot (for tagged types) ?
The second model is correct.
> Please help me or in a few days I won't even remember what a subprogram
> or a type is :-)
Have a nice vacation. :-)
> Antoine Bertier
> bertier@alsys.fr
- Bob
From barnes@alsys.co.uk Thu Sep 22 12:58:08 1994
Return-Path:
Received: from inmet.camb.inmet.com by dsd.camb.inmet.com (4.1/SMI-4.1)
id AA22939; Thu, 22 Sep 94 12:58:08 EDT
Received: from eros.britain.eu.net by inmet.camb.inmet.com (4.1/SMI-4.1)
id AA15789; Thu, 22 Sep 94 12:57:16 EDT
Received: from alsys.co.uk by eros.britain.eu.net with UUCP
id ; Thu, 22 Sep 1994 17:48:27 +0100
Received: by alsys.alsys.co.uk (4.1/SMI-4.1.1) id AA01485;
Thu, 22 Sep 94 16:57:44 BST
Date: Thu, 22 Sep 94 16:57:44 BST
From: barnes@alsys.co.uk (John Barnes)
Message-Id: <9409221557.AA01485@alsys.alsys.co.uk>
To: ada9x-mrt@inmet.com
Subject: accessibility
!topic accessibility rules
!reference RM9X-3.10.2;5.0
!from John Barnes 94-09-22
<>
!discussion
Dear Reader
Some time ago I promised to mail a further consideration of
the accessibility rules. I welcome any comments on the
following. If anyone would like a nice copy where all the
subscripts are subscripts and things are in italics then I
could fax the original. Meanwhile here is an ASCII version.
Accessibility
The purpose of this note is to try to prove something about
the accessibility rules. Essentially to show that
accessibility can be checked by static rules in most
circumstances.
Notation
A block structured program can be labelled as a number of
nested regions. We will ignore packages which have no part
in the accessibility rules. It will suffice to consider a
single unit containing nested blocks and subprograms.
The various regions are labelled by an ordered sequence S
of d integers which we can denote by (l1, l2, ..., ld).
Each region R has a unique sequence S(R). d(R) is the depth
of the region and the li(R) are its level numbers, one for
each level.
The sequence for a region immediately inside a region
(its parent) is the sequence for the parent plus one
additional level number. Siblings have all but the last
level number in common. We can number these last levels in
lexical order but that is merely a convenience, all that
matters is that they are distinct. The outermost region has
the sequence (1). The figure shows an example of labelled
regions
procedure Main -- (1)
...
procedure S -- (1, 1)
...
end S;
procedure P -- (1, 2)
procedure Q -- (1, 2, 1)
...
end Q;
begin
...
declare -- (1, 2, 2)
...
end;
...
end P;
declare -- (1, 3)
...
end;
end Main;
So if the parent region P includes the child region C
statically, then
d(C) >= d(P) ...(1)
li(C) = li(P) i = 1 .. d(P)
An entity E declared in region P is in scope for all regions
C satisfying the above relationship.
Procedure calls
A procedure P can be called throughout its scope, that is in
any region inside it, from immediately within itself and
from anywhere in the region where it is declared (ignore
silly linear elaboration). So denoting the region of P by
the sequence S(P) then P can be called from any region R
such that
d(R) >= d(P) - 1 ...(2)
li(R) = li(P) i = 1 .. d(P) - 1
So a procedure call can cause a transition from a region to
one with upto one more level, the same number of levels or
with several levels less. The called procedure must have
all but its last level number the same as that of the
calling region.
In the example above the procedure P with region (1, 2)
can be called from any region commencing (1) (that is
anywhere) and procedure Q with region (1, 2, 1) can be
called from any region commencing (1, 2).
Dynamic nesting
In the dynamic execution, an ordered set of regions will be
active at any time. The current activity state can thus be
represented by a chain of regions
C = (R1, R2, ...)
This chain can change by having the last item deleted
(popping up a frame) or having another item added (pushing
on another frame).
The relationship between successive regions in such a
chain is that one must be a block nested in the previous
region or a procedure called from the previous region.
In the first case of a nested block Rn+1 nested in the
region Rn we have
d(Rn+1) = d(Rn) + 1 ...(3)
li(Rn+1) = li(Rn) i = 1 .. d(Rn)
In the second case of a procedure Rn+1 called from region Rn
we have
d(Rn+1) s d(Rn) + 1 ...(4)
li(Rn+1) = li(Rn) i = 1 .. d(Rn+1) - 1
The first (3) is just a special case of the second (4); so
equation (4) is the general rule which must apply to
adjacent regions in a dynamic sequence.
An informal description of this is that the transition
from one region to the next can (i) delete as many items as
you like from the end of the sequence and can then
optionally change the last one, (ii) change the last item of
the sequence (or leave it the same, a recursive procedure
immediately calling itself), this is really case (i) but
with zero removed, (iii) can add one item to the sequence.
More informally the successor region can be any static
ancestor region or a sibling thereof or an immediate child.
Thus the successor of (1, 2, 3) can be
(1), (1, 2), (1, 1) deleting items and maybe changing
the last
(1, 2, 1), (1, 2, 2), (1, 2, 3) changing the last item
(1, 2, 3, 1), (1, 2, 3, 2) adding an item
but not (1, 1, 1) (it could go through (1, 1) first) or (1,
2, 4, 1) (it could go through (1, 2, 4) first).
Static and Dynamic relationships
We have just seen the rules for the relationship between
successive regions in a dynamic activity state.
The static chain of in scope regions from any point is
deduced from (1). We see that the static chain leading to a
region R is just the immediate ncemstors of R.
So a possible dynamic chain might be
(1), (1, 2), (1, 1), (1, 1, 2)
and the static chain is
(1), (1, 1), (1, 1, 2)
An important theorem is
Theorem 1
The dynamic chain includes every region of the
corresponding static chain.
The proof is simple and is based on the fact from (4), that
whenever we move dynamically from a region to one of greater
depth, then the move must be from parent to immediate child.
In detail, suppose Rn = (l1, l2, ..., ld-1, ld), then we
need to show that Rx = (l1, l2, ..., ld-1) is also in the
chain.
Since a transition downward can only be one level down
there must be a transition from level d-1 to level d.
Consider the last such transition from Ry (of level d-1) to
Rz say. So Rz has depth d. Any regions in the chain
between Rz and Rn must have depth not less than d since Ry
was the last region of level d-1. All transitions between
the regions of level d and more do not change the value of
levels d-1 and less by (4). So Rz must be a sibling of Rn
and Ry must be their static parent Rx. The argument can now
be applied in turn to Rx and its parent and the result
follows.
Q E D
Dynamic accessibility
The dynamic rule we wish to impose is that the lifetime of
any object X must be at least that of any object of access
type A that could refer to it. We consider the approach
that this will follow if we apply the stronger requirement
that the lifetime of any object X must be at least that of
the access type A.
Lifetime is associated with dynamic region. We are
considering that the region of X must be a dynamic ancestor
of the region containing the access type A.
We consider the hypothesis that this will be the case if
the region of X is a static ancestor of that of A. The
reason for considering this is that if we can show that the
static and dynamic relationships are equivalent in practice
then the static test implies no overhead. This seems
perhaps unlikely because the dynamic and static chains are
not equivalent. We have seen that the dynamic chain
includes all regions of the static chain but the reverse is
obviously not true.
To gain insight, consider the following
procedure Example1 is -- (1)
type T is ...;
procedure P is -- (1, 1)
type A is access all T;
Ptr: A := null;
begin
Ptr := X'Access;
end P;
begin
declare -- (1, 2)
X: aliased T;
begin
P; -- call P
end;
end Example1;
This is actually illegal because at the point where we have
written X'Access, X is out of scope. We ignore this for the
moment. The dynamic chain at this point is
(1), (1, 2), (1, 1)
and the region of A, R(A) = (1, 1) and that of X, R(X) is
(1, 2).
However note that dynamically the type A is in a region
descended from that of X. So nothing could have gone wrong
in this dynamic chain. So just because X is a dynamic
ancestor does not imply that it is a static ancestor and it
would appear that a check for static ancestry would be too
severe.
But as noted, this example is not a possible case because
we cannot actually write X'Access because of the scope
rules. This is vital, so what we want to show is
Theorem 2
If an object X and access type A are such that the
scope rules allow X'Access to be written and to
deliver a value of A, then if the region R(A) is a
static descendant of R(X) then it is also a
dynamic descendant and vice versa.
If we can show this then the static and dynamic checks are
equivalent.
We will denote the region where the check is by R(C);
that is the region where we are writing X'Access. We
consider the chains leading to this point. We have
R(C) is a static descendant of R(X) scope of 'Access
R(C) is a static descendant of R(A) scope of 'Access
This implies that both regions are in the static chain
leading to C. If in addition, R(A) is a static descendant
of R(X) then they must be in the order
(1), ... R(X), ... R(A), ... R(C)
where of course two or more could coincide. But by Theorem
1, all regions in the static chain are also in the dynamic
chain. This means that the dynamic chain leading to R(A)
must include R(X). In other words, the region R(A) is a
dynamic descendant of R(X). On the other hand if R(A) is
not a static descendant of R(X) then they must be in static
order
(1), ... R(A), ... R(X), ... R(C)
and so the dynamic chain leading to R(A) does not include
(the relevant incarnation of) R(X) so that R(A) is not
dynamically descended from R(X).
This proves Theorem 2.
Q E D
So applying a static rule is equivalent to a dynamic rule in
these circumstances.
Note that a region could occur twice in a dynamic chain
(corresponding to recursion); it is the last one that
matters regarding dynamic descendency.
Type conversion
We can convert a value of a named access type to another
named access type under certain circumstances. (Access
parameters are discussed later.) Consider
type A is access all T;
type B is access all S;
In the case of a non tagged type the type A can be converted
to B only if the types T and S are the same. In the case of
a tagged type the type A can be converted to B provided T
can be converted to S. However since tagged type extension
is not allowed at an inner level this means that the regions
of T and S must be the same. Since we are only interested
in regions this means that we can consider T and S to be the
same without loss of generality. So we now consider
type A is access all T;
type B is access all T;
The question we now have to consider is whether the
additional consideration of type conversion modifies the
conclusion of Theorem 2.
Note that a conversion can only be performed at places
where types A and B are both in scope.
The envisaged static check is that a conversion from A to
B is permitted provided the region of B is a static
descendant of the region of A. (See RM 4.6(17)). But what
we really want to ensure is that the conversion cannot
extend the dynamic lifetime of the value so that it might
outlive the original accessed object.
An identical argument to that of Theorem 2 follows. The
requirement that the conversion can only take place in a
region R(C) which is statically descended from both R(A) and
R(B) is analogous to that for X'Access which required both X
and A to be in scope.
R(B) is a static descendant of R(A) given
R(C) is a static descendant of R(A)scope of conversion
R(C) is a static descendant of R(B)scope of conversion
and we again have a static chain
(1), ... R(A), ... R(B), ... R(C)
and again we conclude that the region R(B) is dynamically
descended from R(A). And since R(A) is dynamically
descended from R(X), this means that R(B) is dynamically
descended from R(X) and thus the correct lifetime is
preserved. In fact conversion can only make it "safer". We
have therefore proved once more that the static check is
equivalent to the dynamic check. That is
Theorem 3
If one named access type A is converted to another
named access type B, then the static accessibility
check is equivalent to the implied dynamic check.
Summary 1
We have now considered a model containing blocks and
subprograms, named access types and type conversion. We
have concluded that for this model, static accessibility
checks impose precisely the same restrictions as dynamic
accessibility checks. We have not explicitly considered
packages but simply dismissed them as irrelevant to
accessibility.
The rules can thus be framed in terms of static levels
with only a relationship such as deeper being involved.
We now plunge into a consideration of access parameters.
Access parameters
The passing of normal parameters has no impact on the
discussion so far because we can simply consider a procedure
call as a parameterless call with a series of assignments to
and from parameters (which are essentially variables) around
it. For example, two notional assignments are required for
an in parameter; one assignment of the actual parameter to a
dummy variable declared in the same region as the procedure,
this assignment is written at the point of the call; and
then a second assignment from this dummy variable to the
formal parameter, this second assignment is written inside
the procedure. Both these assignments are allowed in the
general model and so normal parameters have no impact on the
consideration of regions and accessibility. (This equally
applies to entry parameters where the second assignment is
inside the accept statement; incidentally this shows that it
does not matter that the accept statement can be at a
greater depth to the entry declaration.)
Access parameters are different because they introduce
additional forms of type conversion. The general idea is
that we can pass access values (addresses) plus an
indication of dynamic accessibility and then get more
flexibility at the cost of some runtime checks. The true
dynamic situation at any point is captured by the dynamic
chain at that point and somehow we need to pass the essence
of that chain in a more manageable form.
An important property of access parameters is that being
of an anonymous type there can be no other objects of that
type and so they cannot be assigned without first being
converted to a named type.
Consider a variation on the example above
procedure Example2 is -- (1)
type T is ...;
procedure P(XP: access T) is -- (1, 1)
type A is access all T;
Ptr: A := null;
begin
Ptr := A(XP); -- conversion
end P;
begin
declare -- (1, 2)
X: aliased T;
begin
P(X'Access); -- call P
end;
end Example2;
This is a bit like the previous example where we aim to
pass the "address" of X to Ptr. We want this to be "OK"
because X has a longer lifetime than the type of Ptr. In
the previous example we could not actually do it because
there was nowhere we could legally do the 'Access; this
rather obscured the fact that a static check would have
prevented us from doing something which was otherwise
dynamically OK.
In this example we can do the 'Access when calling the
procedure and now we have to consider the checks at the
conversion.
The consequence we would like is that the conversion is
legal provided the converted reference has a longer lifetime
than the access type to which it is converted.
The indication of accessibility that we will pass is the
depth d of the accessed object. The checks that we will
impose on conversion of the access parameter to a named
access type are (a), if the region of the target type is
statically descended from the region of the parameter then
OK, otherwise (b), we check that the region of the target
type has a depth not less than the depth passed with the
parameter.
Dynamically there are three interesting cases. The
regions of interest are R(X), R(A) and R(P), the region of
the formal access parameter XP. Dynamically, R(X) must
precede R(P) because of the procedure call itself. The
three regions could then be in the following orders
R(X), ... R(P), ... R(A) case i
R(X), ... R(A), ... R(P) ii
R(A), ... R(X), ... R(P) iii
The first two orders should be allowed (and also with
R(X) = R(A)), but the third order must be forbidden. We
consider these three cases in turn and show that the checks
just proposed satisfy our requirements.
Case (i) is illustrated by Example 2. The region of the
type A is equal to or descended both statically and
dynamically from that of the formal parameter and so is
covered by part (a) of our proposed rule. The conversion
A(XP) must be inside the procedure P since no other objects
of the anonymous type exist. We know that the object X was
in existence when passed as a parameter and so will remain
in existence throughout the lifetime of the type T. The
conversion rule is in fact the same as the conversion of
named types considered above; the target region has to be
descended from the source region. The proof is identical
because the conversion can only occur at places in the scope
of the target type A and the source type has the scope of
the procedure itself.
In terms of the dynamic chain, we know that R(P)
dynamically precedes R(A) and so R(X) must also precede
R(A).
The other two cases are where part (a) of the rule does
not apply; in other words the type A is in a region not
descended from that of the procedure P. Without loss of
generality we can assume that A and P are declared in the
same region. (A cannot be in a deeper region because the
conversion would not be in scope, and if P is in a deeper
region then it just means that the call of P has to be in a
deeper region as well as will be seen in a moment.)
Case (ii) applies when the target type A is outside the
procedure and in a region equal to or descended from that of
X. Consider
procedure Example3 is -- (1)
type T is ...;
type A is access all T;
Ptr: A := null;
procedure P(XP: access T) is -- (1, 1)
begin
Ptr := A(XP); -- conversion
end P;
X: aliased T;
begin
P(X'Access); -- call P
end Example3;
In this case we apply part (b) of the rule and dynamically
compare the depths of the regions R(A) and R(X). They are
both 1 and the check passes as it should.
The final case (iii) is illustrated by
procedure Example4 is -- (1)
type T is ...;
type A is access all T;
Ptr: A := null;
procedure P(XP: access T) is -- (1, 1)
begin
Ptr := A(XP); -- conversion
end P;
begin
declare -- (1, 2)
X: aliased T;
begin
P(X'Access); -- call P
end;
end Example4;
and here the depth of R(X) is greater than that of R(A) and
the check fails as it should.
In order to be convinced that rule (b) does cover all
situations properly, observe that the region of the
declaration of procedure P must be the same as or statically
descended from R(A) (for the conversion to be possible) and
moreover the region of the call of P must be the same as or
descended from that containing the declaration of P (for the
call to be possible). So the call of P must be in a region
descended from R(A). Equally of course the call of P must
be in a region descended from R(X). So here once more we
have an identical situation to when we were considering
Theorem 2. The call of P is statically descended from both
R(A) and R(X) just as the expression X'Access was in Theorem
2 and hence the dynamic and static relationships are the
same. Hence comparing the static depths is equivalent to
the required dynamic check.
In summary the checks for rule (b) work rather as if the
procedure were not present. In other words, any checks
performed correspond just to those which would apply if we
directly assigned X'Access to Ptr.
We have proved the following
Theorem 4
In the case of an access parameter with target
type T where the actual parameter is the Access of
a variable X, then conversion of the formal
parameter to a named access type A is correctly
allowed if (a) the region R(A) is statically
descended from that of the parameter, or failing
that, if (b) the region R(A) has a depth not less
than that of R(X).
Chained calls
We have just seen that the passing of an access to an access
parameter and the subsequent conversion of that parameter to
a named type are catered for satisfactorily by Theorem 4.
The final analysis relied upon the fact that the conversion
was in the called procedure P and this allowed the same
argument as for Theorem 2 to be applied.
We now have to consider the possibility where this does
not apply; that is that the final conversion is not in the
procedure that is called with the parameter X'Access. (This
is the rule AARM 3.10.2 (17h)). This arises where one
access parameter is passed to another access parameter (it
can arise in no other way since as mentioned earlier there
is no possibility of assignment of values of an anonymous
access type). There are a number of cases to be considered
according to the regions of the procedures concerned. We
will first consider the case of two procedures P1 and P2.
We will call P1 with X'Access, then pass the access
parameter to P2 and finally do the conversion in P2.
The regions of P1 and P2 can be related in three ways (i)
they can be siblings or (ii) P1 is at a deeper level than
P2, or (iii) P2 is at a deeper level than P1 (which implies
it is nested inside P1).
The rule we are going to postulate is that on the call of
P2 by P1, the depth level passed on is unchanged unless P2
is nested inside P1 in which case the depth level passed is
the least of that of the formal parameter of P1 and the
original actual parameter.
We will now consider the three cases and see how they
impact on the discussion of the previous section. We need
not bother about the situation where the target access type
A is declared inside P2 since that is catered for by the
static part (a) of the rule of Theorem 4 and does not
consider the depth information passed with the parameter.
Consider case (i) where the procedures P1 and P2 are
siblings. The procedure P of Examples 3 and 4 can be
replaced by
procedure P2(XP2: access T) is -- (1, 1)
begin
Ptr := A(XP2); -- conversion
end P2;
procedure P1(XP1: access T) is -- (1, 3)
begin
P2(XP1); -- chained call
end P1;
and the call of P is now replaced by P1(X'Access). (The
region of P1 is numbered (1, 3) so that it does not clash
with the existing region (1, 2) of Example 4.)
This transformation clearly has no impact on the
arguments of the previous section since the relationships
between the regions are unchanged. Thus the proposed rule
that the chained call passes on the depth unchanged is as
required for this case.
We now consider case (ii) where P1 is at a deeper level
than P2. The following example shows both successful and
unsuccessful calls
procedure Example5 is -- (1)
type T is ...;
type A is access all T;
Ptr: A := null;
procedure P2(XP2: access T) is -- (1, 1)
begin
Ptr := A(XP2); -- conversion
end P2;
X: aliased T;
begin
declare -- (1, 2)
procedure P1(XP1: access T) is -- (1, 2, 1)
begin
P2(XP1); -- chained call
end P1;
Y: aliased T;
begin
P1(X'Access); -- call P1 OK
P1(Y'Access); -- call P1 fails
end;
end Example5;
The proposed rule does not change the level at the
conversion and the two cases shown behave satisfactorily. A
similar analysis to before can be made. The region of the
declaration of P2 must be the same as or statically
descended from R(A), the region of the declaration of P1 is
consequently statically descended from R(A). So finally the
call of P1 is statically descended from both R(A) and R(X)
or R(Y) and so the dynamic and static relationships are the
same as in Theorem 2. Comparing the depth of the ultimate
actual parameter X or Y with that of A is thus correct.
Finally consider case (iii) where P2 is nested inside P1.
The big difference here is that the type A can be external
to P2 (so that the dynamic part (b) of Theorem 4 applies)
and yet internal to P1 and thus not visible to the call of
P1. The type A could also be external to both P1 and P2.
Consider first the interesting case where A is internal
to P1.
procedure Example6 is -- (1)
type T is ...;
procedure P1(XP1: access T) is -- (1, 1)
type A is access T;
Ptr: A := null;
procedure P2(XP2: access T) is -- (1, 1, 1)
begin
Ptr := A(XP2); -- conversion
end P2;
begin
P2(XP1); -- chained call
end P1;
X: aliased T;
begin
declare -- (1, 2)
Y: aliased T;
begin
declare
Z: aliased T;
begin
P1(X'Access); -- call P1 OK
P1(Y'Access); -- call P1 OK
P1(Z'Access); -- call P1 OK
end;
end;
end Example6;
Externally, all that matters is that the type A is
internal to P1 and therefore has a lesser lifetime than any
object passed as parameter to P1. This is thus similar to
rule (a) of Theorem (4) and thus we require this to be
satisfactory. However, the conversion is not in P1 at all
but in the deeper nested P2. So it is in P2 that the check
of Theorem 4 is to be applied but for P2 the type A is
external and so the dynamic rule (b) has to be applied. For
it to pass this means that the region of the target type A
has a depth not less than that passed by the parameter. The
proposed rule satisfies this because if the actual is deeper
than that of XP1 then it is replaced by that of XP1 which in
turn is of course less than or equal to that of A. Note
that in Example 6, the call of Z would unnecessarily fail
without this transformation.
The other part of case (iii) is where the type A is
external to both P1 and P2. The call should succeed if the
depth of the object is not greater than that of A. This is
satisfactory as well. If the depth of the object is greater
than XP1 then it is converted to be equal to XP1 but this
still correctly fails because that of XP1 is greater than
that of A. If the depth of the object is not greater than
that of XP1 then no change is made and the natural depths of
A and the object are used in the normal application of rule
(b) of Theorem 4 to the conversion.
We have now considered all circumstances where an access
parameter is passed to another access parameter. We have
now shown that
Theorem 5
The rules of Theorem (4) work correctly in the
case of chained access parameters provided that in
the situation where a call passes on one formal
access parameter to a deeper access parameter, the
depth passed is the lesser of that of the depth
passed (dynamically) to the first call and the
(static) depth of the first formal parameter.
is valid for the case of two calls.
A little thought shows that more elaborate chains of
calls do not introduce further complexity; only at most one
call causes the passed on level to be changed. The full
conclusion is thus reached.
Q E D
Summary 2
We have now shown that the additional rules for access
parameters are both necessary and sufficient for checking
accessibility. We conclude that the AARM model precisely
implements the intuitive model of dynamic accessibility.
Moreover, much of the implementation is static despite the
inherent dynamic nature of accessibility. This happy
conclusion is reached because Ada has named access types and
the static visibility requirements ensure that accessibility
is effectively static also. Dynamic checking is only
required with access paramters as a consequence of their
being of anonymous access types.
Note
I have of course not considered access to subprograms or
generics or anything else but I feel much happier that they
all follow.
I am afraid that the level of mathematics has degenerated
as I progressed. Indeed my consideration of chains of more
than two access parmater calls was done with strange shapes
that do not easily translate onto this linear format;
nevertheless I am now personally convinced. Proof is all a
matter of comfort between the reader and the writer and I
now feel OK. Maybe I might spend some effort on making it
more rigorous and with less appeal to consideration of cases
but right now I await your comments.
Congratulations to whoever came up with the AARM rules.
John Barnes
From ncohen@watson.ibm.com Thu Sep 22 21:41:48 1994
Return-Path:
Received: from inmet.camb.inmet.com by dsd.camb.inmet.com (4.1/SMI-4.1)
id AA00362; Thu, 22 Sep 94 21:41:48 EDT
Received: from watson.ibm.com by inmet.camb.inmet.com (4.1/SMI-4.1)
id AA03559; Thu, 22 Sep 94 21:41:45 EDT
Message-Id: <9409230141.AA03559@inmet.camb.inmet.com>
Received: from YKTVMV by watson.ibm.com (IBM VM SMTP V2R3) with BSMTP id 4633;
Thu, 22 Sep 94 21:41:48 EDT
Date: Thu, 22 Sep 94 21:34:25 EDT
From: "Norman H. Cohen"
To: ada9x-mrt@inmet.com
Subject: Are pool-specific access types access-to-variable types?
!topic Are pool-specific access types access-to-variable types?
!reference RM9X-3.10(10)
!reference RM9X-3.10(12)
!from Norman Cohen
<>
!discussion
Is the term "access-to-variable type" meant to include pool-specific
access types? 3.10(10) strongly suggests that access-to-variable
types are one of the two kinds of general access types. However,
3.10(12) refers to a "general access-to-variable type", suggesting that
there are access-to-variable types that are not general.
If paragraph 10 is correct in suggesting that an access-to-variable type
is a particular kind of general access type, then paragraph 12 is
redundant (and confusing), and the word "general" should be dropped from
the first sentence.