CVS difference for ai05s/ai05-0051-1.txt
--- ai05s/ai05-0051-1.txt 2007/11/07 06:32:42 1.3
+++ ai05s/ai05-0051-1.txt 2007/12/13 04:39:36 1.4
@@ -1209,3 +1209,607 @@
access discriminant of a "wrapper" result type.
****************************************************************
+
+From: Stephen W. Baird
+Sent: Tuesday, November 27, 2007 5:23 PM
+
+I don't like the general approach of the AI05-51 proposal we
+discussed at the Fairfax meeting (see Tuck's message
+of 11/06/07).
+
+In general, I feel that this proposal adds
+unnecessary complexity to the dynamic semantics of
+the language. By making the runtime accessibility
+checking rules of the language more complex, we
+would be making it harder to write reliable software.
+
+Consider the following example:
+
+ procedure Foo is
+ package Pkg is
+ type T is tagged
+ record
+ Int : Integer;
+ end record;
+ function F (X : T) return access String;
+ end Pkg;
+
+ package body Pkg is
+ type String_List_Node is
+ record
+ Item : access String;
+ Link : access String_List_Node;
+ end record;
+
+ Allocated_Strings : access String_List_Node;
+
+ function F (X : T) return access String is
+ begin
+ return Result : access String :=
+ new String'(Integer'Image (X.Int)) do
+ Allocated_Strings :=
+ new String_List_Node'
+ (Item => Result,
+ Link => Allocated_Strings);
+ end return;
+ end F;
+ end Pkg;
+ begin
+ declare
+ type D is new Pkg.T with null record;
+ X : D := (Int => 123);
+
+ procedure Check (F_Result : access String) is
+ Check_Failed : exception;
+ begin
+ if F_Result.all /= Integer'Image (X.Int) then
+ raise Check_Failed;
+ end if;
+ end Check;
+ begin
+ Check (X.F);
+ Check (Pkg.T (X).F);
+ end;
+ end Foo;
+
+As I understand the proposed changes, this example
+would raise Program_Error during the evaluation of
+the aggregate (specifically, during the type
+conversion associated with the "Item => Result"
+portion of the aggregate).
+
+I think that users would find this surprising.
+I believe that this example should execute without
+raising an exception.
+
+It's clear that we have make changes of some kind
+to deal with the problems identified in the AI.
+Let's consider another alternative.
+
+The root cause of these problems is a disagreement
+between the accessibility level of the function
+named in a call and the accessibility level of the
+function whose body is executed. I would like to
+eliminate this case without imposing draconian
+restrictions. Very roughly speaking, the idea is to
+get a function to behave as though it were declared
+at the least deeply nested level from which it
+could be called. If a function is callable from a
+less nested level than where it is declared, then
+that is part of the contract which the function's
+body is responsible for honoring.
+
+We define a new accessibility level, the "inherited
+accessibility level" (for lack of a better name),
+of a subprogram as the most deeply nested
+accessibility level that satisfies the following
+conditions:
+
+ The inherited accessibility level of a subprogram
+ is not deeper than the accessibility level of the
+ subprogram.
+
+ The inherited accessibility level of an inherited
+ subprogram is not deeper than the inherited
+ accessibility level of the subprogram's parent
+ subprogram.
+
+ The inherited accessibility level of a
+ dispatching subprogram is not deeper than the
+ inherited accessibility level of any subprogram
+ which the given subprogram overrides.
+
+The function result accessibility checks discussed
+in the AI are then performed relative to the
+inherited accessibility level of the function, as
+opposed to the "normal" accessibility level of
+the function. Similarly, the properties of an
+(anonymous) access result type (including its
+accessibility level) are determined by the
+inherited accessibility level of the function.
+In determining this new accessibility level, there
+are no dependencies on dynamic values such as
+the tag of the controlling operand or the
+subprogram view referenced by the caller.
+
+A few legality rules would be needed in order to
+deal with some corner cases.
+
+In the case of a dispatching operation which is
+declared as a rename of some other subprogram, the
+inherited accessibility level of the dispatching
+operation and of the renamed subprogram must match.
+We don't want to allow
+
+ declare
+ package Outer is
+ type T1 is tagged null record;
+ function F1 (X : T1) return access String;
+ end Outer;
+
+ ... ;
+ begin
+ declare
+ package Inner is
+ type T2 is new Outer.T1 with null record;
+
+ not overriding function F2 (X : T2)
+ return access String;
+
+ overriding function F1 (X : T2)
+ return access String renames F2;
+ end Inner;
+
+ ... ;
+ begin
+ ...;
+ end;
+ end;
+
+, because Inner.F2's inherited accessibility level
+is deeper than Inner.F1's.
+
+In the case of a dispatching inherited
+subprogram which is not overridden, the inherited
+accessibility level of the subprogram and of its
+parent subprogram must match. We don't want to allow
+
+ declare
+ package Outer is
+ type Ifc is interface;
+ function F (X : Ifc) return access String
+ is abstract;
+ end Outer;
+ begin
+ declare
+ package Inner1 is
+ type T1 is tagged null record;
+ function F (X : T1) return access String;
+ end Inner1;
+
+ package Inner2 is
+ type T2 is new Inner1.T1 and Outer.Ifc
+ with null record;
+ end Inner;
+
+ ...;
+ begin
+ ...;
+ end;
+ end;
+
+If this were allowed, a call to Outer.F might end up
+executing the body of Inner1.F, thereby violating the
+rule that a function's inherited accessibility level
+corresponds to the least deeply nested level from
+which the function might be called.
+
+These restrictions are only needed if
+the subprogram is a function with an "interesting"
+result type (anonymous access, class-wide, or has
+access discriminants). I believe they are not
+needed for an abstract subprogram.
+
+Does this approach look like it would work?
+Does it look preferable to the Fairfax proposal?
+
+I don't like the name "inherited accessibility
+level" very much. Any suggestions? Would
+"caller's accessibility level" or "exported
+accessibility level" be better?
+
+****************************************************************
+
+From: Tucker Taft
+Sent: Wednesday, November 28, 2007 11:15 AM
+
+> I don't like the general approach of the AI05-51 proposal we
+> discussed at the Fairfax meeting (see Tuck's message
+> of 11/06/07).
+>
+> In general, I feel that this proposal adds
+> unnecessary complexity to the dynamic semantics of
+> the language. By making the runtime accessibility
+> checking rules of the language more complex, we
+> would be making it harder to write reliable software.
+
+On the other hand, it seems valuable to me to have
+the same rules for anonymous allocators as access
+discriminants and as "unwrapped" values in a return
+statement. That is, the rules proposed on 11/6 have the
+advantage of uniformity, where:
+
+ return new T;
+ and
+ return (Discrim => new T, ...);
+
+both use a storage pool determined by the caller (presuming
+the access types are anonymous in both cases).
+
+> Consider the following example:
+>
+> procedure Foo is
+> package Pkg is
+> type T is tagged
+> record
+> Int : Integer;
+> end record;
+> function F (X : T) return access String;
+> end Pkg;
+>
+> package body Pkg is
+> type String_List_Node is
+> record
+> Item : access String;
+> Link : access String_List_Node;
+> end record;
+>
+> Allocated_Strings : access String_List_Node;
+>
+> function F (X : T) return access String is
+> begin
+> return Result : access String :=
+> new String'(Integer'Image (X.Int)) do
+> Allocated_Strings :=
+> new String_List_Node'
+> (Item => Result,
+> Link => Allocated_Strings);
+ > end return;
+
+It seems easy enough to avoid using the storage pool
+provided by the caller if that is desirable, by allocating
+the result as part of the creation of the String_List_Node,
+and then returning that:
+
+ Allocated_Strings :=
+ new String_List_Node'
+ (Item => new String'(Integer'Image (X.Int)),
+ Link => Allocated_Strings);
+ return Allocated_Strings.Item;
+
+It seems that programmers will need to get used to the idea that
+the storage pool for anonymous allocators in a return
+statement are determined by the caller. To have different
+rules for whether the anonymous allocators are inside or
+outside an aggregate seems potentially more error prone,
+and has the added advantage of avoiding a certain storage
+leak.
+
+> end F;
+> end Pkg;
+> begin
+> declare
+> type D is new Pkg.T with null record;
+> X : D := (Int => 123);
+>
+> procedure Check (F_Result : access String) is
+> Check_Failed : exception;
+> begin
+> if F_Result.all /= Integer'Image (X.Int) then
+> raise Check_Failed;
+> end if;
+> end Check;
+> begin
+> Check (X.F);
+> Check (Pkg.T (X).F);
+> end;
+> end Foo;
+>
+> As I understand the proposed changes, this example
+> would raise Program_Error during the evaluation of
+> the aggregate (specifically, during the type
+> conversion associated with the "Item => Result"
+> portion of the aggregate).
+
+A similar result would occur if Result were an object
+with an access discriminant, and the programmer attempted
+to string the value of the access discriminant on
+a local list before returning it.
+
+> I think that users would find this surprising.
+> I believe that this example should execute without
+> raising an exception.
+
+Clearly this is a matter of expectation. Programmers
+will not be surprised if they understand the general
+rule that anonymous allocators in return statements
+use the caller's storage pool. That makes sense since
+the primary user of the result is the caller. If
+the programmer wants to squirrel away a copy of something
+before it is returned, they clearly have a special
+case and will have to use special care to avoid
+dangling references.
+
+> It's clear that we have make changes of some kind
+> to deal with the problems identified in the AI.
+> Let's consider another alternative.
+>
+> The root cause of these problems is a disagreement
+> between the accessibility level of the function
+> named in a call and the accessibility level of the
+> function whose body is executed.
+
+I would claim that isn't really the heart of the
+problem for anonymous allocators. There it is that the
+caller uses the result of the anonymous allocator
+in a particular context, and it is not helpful
+to allocate in a storage pool over which the
+caller has no control.
+
+Having anonymous allocators defining all or part
+of a return value is new with Ada 2005. It seems
+quite helpful if all such anonymous allocators are
+treated similarly, whether they are used to define
+the whole return value, or an access discriminant
+of one.
+
+> ... I would like to
+> eliminate this case without imposing draconian
+> restrictions. Very roughly speaking, the idea is to
+> get a function to behave as though it were declared
+> at the least deeply nested level from which it
+> could be called. If a function is callable from a
+> less nested level than where it is declared, then
+> that is part of the contract which the function's
+> body is responsible for honoring.
+>
+> We define a new accessibility level, the "inherited
+> accessibility level" (for lack of a better name),
+> of a subprogram as the most deeply nested
+> accessibility level that satisfies the following
+> conditions:
+>
+> The inherited accessibility level of a subprogram
+> is not deeper than the accessibility level of the
+> subprogram.
+>
+> The inherited accessibility level of an inherited
+> subprogram is not deeper than the inherited
+> accessibility level of the subprogram's parent
+> subprogram.
+>
+> The inherited accessibility level of a
+> dispatching subprogram is not deeper than the
+> inherited accessibility level of any subprogram
+> which the given subprogram overrides.
+>
+> The function result accessibility checks discussed
+> in the AI are then performed relative to the
+> inherited accessibility level of the function, as
+> opposed to the "normal" accessibility level of
+> the function. Similarly, the properties of an
+> (anonymous) access result type (including its
+> accessibility level) are determined by the
+> inherited accessibility level of the function.
+> In determining this new accessibility level, there
+> are no dependencies on dynamic values such as
+> the tag of the controlling operand or the
+> subprogram view referenced by the caller.
+> ...
+> Does this approach look like it would work?
+> Does it look preferable to the Fairfax proposal?
+
+I'm not very fond of effectively pushing the accessibility
+level up to the global level in most cases,
+and making a storage leak even more likely.
+
+Once we accept that anonymous allocators in return
+statements generally depend on the context provided
+by the caller, it seems natural for this to apply
+to access results as well as access discriminants
+of results.
+
+****************************************************************
+
+From: Tucker Taft
+Sent: Wednesday, November 28, 2007 9:40 PM
+
+> > In general, I feel that this proposal adds
+> > unnecessary complexity to the dynamic semantics of
+> > the language. By making the runtime accessibility
+> > checking rules of the language more complex, we
+> > would be making it harder to write reliable software.
+>
+> On the other hand, it seems valuable to me to have
+> the same rules for anonymous allocators as access
+> discriminants and as "unwrapped" values in a return
+> statement. That is, the rules proposed on 11/6 have the
+> advantage of uniformity, where:
+>
+> return new T;
+> and
+> return (Discrim => new T, ...);
+>
+> both use a storage pool determined by the caller (presuming
+> the access types are anonymous in both cases).
+
+Surely having them illegal would have that effect. ;-)
+
+But I'm not sure I agree with your basic point.
+
+In the case of an access discriminant, the discriminant cannot be changed on
+the target object; either that object is already constrained (and this one
+had better match) or this is an initial value, in which case your point
+holds.
+
+But in the case of an access return, the returned access can be used in many
+ways, and there is no reason to assume that the level of the call is any
+more correct than the level of the function. For instance, consider:
+
+ function New_Node return access T is
+ begin
+ return new T;
+ end New_Node;
+
+ type List is access all T;
+ My_List : List;
+ Another_List : access T;
+
+ procedure Yuck is
+ begin
+ if ...
+ My_List := List(New_Node);
+ else
+ Another_List := New_Node;
+ end if;
+ end Yuck;
+
+With the rules as they are, this will work as expected. With the "level of
+the call" rule, this will have fail an accessibility check, because List is
+library-level, and surely the level of the call is nested in Yuck. (And the
+same is true for the accessibility level of the anonymous type of
+Another_List.)
+
+The net effect is that the correct pool for an access return depends on how
+it is going to be used: it's not the call that matters, it is how the result
+of the call is "consumed". I'm not sure that there is much point to a rule
+with complex dynamic semantics unless it can properly handle simple cases
+like these.
+
+I could imagine trying to factor the accessibility of the target type into
+this, but that just seems too complex to me.
+
+The net is that I don't think we should even be allowing allocators for
+access results, because we have no idea what the appropriate pool is (it
+should probably be that of List for the first call). In the access
+discriminant case, the (ultimate) caller's level seems to be the right
+answer (in that the object has to be declared there), but that doesn't make
+sense for access results.
+
+It makes sense to be consistent, but only as long as the result makes sense.
+Allocators for access results don't seem to make sense. (One could argue
+that the entire access result thing is an attempt to be overly consistent,
+but it seems useful to replace the misguided return-by-reference -- but we
+don't need allocators to do that).
+
+...
+> > I think that users would find this surprising.
+> > I believe that this example should execute without
+> > raising an exception.
+>
+> Clearly this is a matter of expectation. Programmers
+> will not be surprised if they understand the general
+> rule that anonymous allocators in return statements
+> use the caller's storage pool. That makes sense since
+> the primary user of the result is the caller. If
+> the programmer wants to squirrel away a copy of something
+> before it is returned, they clearly have a special
+> case and will have to use special care to avoid
+> dangling references.
+
+As I showed above, the caller can't squirrel it away, either (with your
+proposed model), so when/how can they do it????
+
+The fact is that your proposed rules are next to useless, because they
+almost never do what the user really wants. Indeed, *any* rules that try to
+guess the user intent for access results are going to fail, because there
+are so many things that could be usefully done. It makes more sense to
+*make* the user say what pool they want (by using a named access type) for
+an allocator in a return statement, and that is easily accomplished by
+defining the storage pool to be zero for access result types.
+
+Why cause our heads to explode for this one???
+
+...
+> I would claim that isn't really the heart of the
+> problem for anonymous allocators. There it is that the
+> caller uses the result of the anonymous allocator
+> in a particular context, and it is not helpful
+> to allocate in a storage pool over which the
+> caller has no control.
+
+Absolutely right. But even more than that, it doesn't make sense to allocate
+in the *wrong* storage pool, either: only the programmer knows what they
+want, and the language should not be in the business of guessing what it is.
+Make them write it out.
+
+> Having anonymous allocators defining all or part
+> of a return value is new with Ada 2005. It seems
+> quite helpful if all such anonymous allocators are
+> treated similarly, whether they are used to define
+> the whole return value, or an access discriminant
+> of one.
+
+And a horrible mistake, at that. Instead of getting rid of the mistake,
+we've made it 10 times worse. Grrrr.
+
+P.S. I'd suggest making all allocators for anonymous access types illegal,
+too, but I think people are getting tired of hearing that. ;-) [They
+logically leak, and fooling with the rules to "define" that away does
+nothing to make the language more consistent or usable. Our implementation
+leaks for all such allocators, and no one has ever complained about that...]
+
+****************************************************************
+
+From: Tucker Taft
+Sent: Wednesday, November 28, 2007 10:36 PM
+
+I don't have time to give a full response at
+this moment, but when I say it uses
+caller "context" to determine the storage pool,
+I mean it does something analogous to what
+we have defined to happen for functions that
+return limited types. That is, the level is
+not that of the function call, but rather
+a level determined by how the value is used.
+Effectively for an anonymous access type,
+the level/storage pool used for the anonymous
+allocator in the return statement is determined by the
+level/storage pool of the type to which the
+result is converted at the call site.
+
+I gave more details in my original proposal of 11/6,
+I believe.
+
+More later...
+
+****************************************************************
+
+From: Randy Brukardt
+Sent: Wednesday, November 28, 2007 11:33 PM
+
+...
+> I don't have time to give a full response at
+> this moment, but when I say it uses
+> caller "context" to determine the storage pool,
+> I mean it does something analogous to what
+> we have defined to happen for functions that
+> return limited types.
+
+Riiiggghhhttt...
+
+But that sort of definition doesn't take assignment into account (it's not
+necessary for limited return types), and adding that complicates things a
+lot. (And they're already too complicated.)
+
+Your note of 11/6 says that access return types would be treated like they
+were the discriminants of an anonymous and invisible wrapper object. But
+that is precisely the model that I was objecting to, because while there is
+no obvious problem with access discriminants (since they can't be assigned
+into an existing object), that model doesn't work with bare access types
+(since they can be assigned, and usually would be).
+
+I'll wait for your full rebuttal before writing more. (Well, at least I'll
+try... ;-)
+
+*****************************************************************
Questions? Ask the ACAA Technical Agent