Version 1.1 of ais/ai-00402.txt

Unformatted version of ais/ai-00402.txt version 1.1
Other versions for file ais/ai-00402.txt

!standard 03.07(10)          05-01-22 AI95-00402/01
!standard 06.05(20)
!class amendment 05-01-27
!status work item 05-01-27
!status received 05-01-22
!priority High
!difficulty Medium
!subject Access discriminants of non-limited types
!summary
Access discriminants on non-limited types have accessibility based on the object rather than the type, as for those of limited types. However, defaults are only permitted for access discriminants of limited types, to avoid accessibility checks whih would be needed on assignment statements to objects with unconstrained discriminants.
!problem
As part of generalizing the use of anonymous access types, we would like to allow them in most contexts where named access types are permitted currently. Discriminants provide an interesting challenge, because limited types already allow anonymous access types, with special accessibility rules. We could allow them on non-limited types with more "conventional" accessibility rules, but that would mean shifting between limited and non-limited might have a significant effect on their semantics. Normally making a type non-limited during maintenance enables more capabilities, but in this case, it would make the type less flexible, and potentially cause compilation errors where the type is used, with no indication of a problem at the point of type declaration.
In this proposal, we try to allow access discriminants of nonlimited types to be as similar as possible to access discriminants of limited types. Any restrictions (relative to access discriminants of limited types) are apparent when the nonlimited type is compiled, rather than showing up when the type is used.
!proposal
Access discriminants are permitted on non-limited types, but defaults are not permitted. Defaults are only permitted for access discriminants if the type is a descendant of a task type, protected type, or limited record type, which correspond to the cases where access discriminants are permitted at all in Ada 95. Note that tagged types are not permitted to have defaults for any sort of discriminant, so we don't need to mention limited type extensions, for example, in this list.
The accessibility rules for access discriminants for nonlimited types are the same as the current rules for limited types, namely that the accessibility level associated with the anonymous type of an access discriminant is that of the enclosing object or constrained subtype.
Additional checks are performed when allocating an object or returning an object with access discriminants, to ensure that the object does not outlive the entity designated by the access discriminant. These checks already exist in the case of an allocator (the normal accessibility checks accomplish this). The check on return is similar to a check instituted to prevent returning an object of a class-wide type outside of the scope of the type identified by its run-time tag.
!wording
Move paragraph 3.7(11) (which talks about default_expressions) in front of paragraph 3.7(10).
Then change 3.7(10) as follows:
A discriminant_specification for an access discriminant [shall appear] {may have a default_expression} only in the declaration for a task...
AARM Language Design Principles note: When an access discriminant is initialized at the time of object creation with an allocator of an anonymous type, the allocated object and the object with the discriminant are tied together for their lifetime. They should be allocated out of the same storage pool, and then at the end of the lifetime of the enclosing object, finalized and reclaimed together.
AARM Discussion: The above principles when applied to a nonlimited type implies that such an object may be copied only to a shorter-lived object, because attempting to assign it to a longer-lived object would fail because the access discriminants would not match. In a copy, the lifetime connection between the enclosing object and the allocated object does not exist. The allocated object is tied in the above sense only to the original object. Other copies have only secondary references to it.
AARM Note: Note that when an allocator appears as a constraint on an access discriminant in a subtype_indication that is elaborated independently from object creation, no such connection exists. For example, if a named constrained subtype is declared via "subtype Constr is Rec(Acc_Discrim => new T);" or if such an allocator appears in the subtype_indication for a component, the allocator is evaluated when the subtype_indication is elaborated, and hence its lifetime is typically longer then the objects or components that will later be subject to the constraint. In these cases, the allocated object should not be reclaimed until the subtype_indication goes out of scope.
Modify the paragraph added after 6.5(20) by AI-354:
If the result type is class-wide, a check is made that the accessibility level of the type identified by the tag of the result is not deeper than that of the master that elaborated the function body. {If the result expression is of a type with an access discriminant, a check is made that the accessibility level of the object associated with the value of the expression is not deeper than that of the master that elaborated the function body.} If [this] {either of these} check{s} fail[s], Program_Error is raised.
[Note: I am assuming that if we get the accessibility level of the result object right for the extended_return_statement, then we will get the appropriate accessibility check when the access discriminant's value is specified via the subtype_indication of the result object, rather than coming from the object associated with the value of the return expression.]
!discussion
We considered various alternative approaches to dealing with access discriminants for non-limited types. Here are some relevant paragraphs in a recent draft of the Ada 2005 AARM -- paragraphs 3.7(10.d-10.l/2):
* If a type has an access discriminant, this automatically makes it
limited, just like having a limited component automatically makes a type limited. This was rejected because it decreases program readability, and because it seemed error prone (two bugs in a previous version of the RM9X were attributable to this rule).
* A type with an access discriminant shall be limited. This is equivalent to the rule we actually chose for Ada 95 , except that it allows a type to have an access discriminant if it is limited just because of a limited component. For example, any record containing a task would be allowed to have an access discriminant, whereas the actual rule requires “limited record”. This rule was also rejected due to readability concerns, and because would interact badly with the rules for limited types that “become nonlimited”.
* A type may have an access discriminant if it is a limited partial
view, or a task, protected, or limited record type. This was the rule chosen for Ada 95.
* Any type may have an access discriminant. For nonlimited type,
there is no special accessibility for access discriminants; they're the same as any other anonymous access component. For a limited type, they have the special accessibility of Ada 95. However, this doesn't work because a limited partial view can have a nonlimited full view -- giving the two view different accessibility.
* Any type may have an access discriminant, as above. However,
special accessibility rules only apply to types that are “really” limited (task, protected, and limited records). However, this breaks privacy; worse, Legality Rules depend on the definition of accessibility.
* Any type may have an access discriminant, as above. Limited types
have special accessibility, while nonlimited types have normal accessibility. However, a limited partial view with an access discriminant can only be completed by a task, protected, or limited record type. That prevents accessibility from changing. A runtime accessibility check is required on generic formal types with access discriminants. However, changing between limited and nonlimited types would have far-reaching consequences for access discriminants - which is uncomfortable.
* Any type may have an access discriminant. All types have special
accessibility. This was considered early during the Ada 9X process, but was dropped for “unpleasant complexities”, which unfortunately aren't recorded. It does seem that an accessibility check would be needed on assignment of such a type, to avoid copying an object with a discriminant pointing to a local object into a more global object (and thus creating a dangling pointer).
* Any type may have an access discriminant, but access discriminants
may not have defaults. All types have special accessibility. This gets rid of the problems on assignment (you couldn't change such a discriminant), but it would be horribly incompatible.
This proposal refines the last choice by allowing defaults where access discriminants are currently permitted, on "really" limited types, but disallowing them elsewhere. This preserves upward compatibility, while eliminating the need to do accessibility checks on assignment statements, because assignment statements won't be able to change the value of an access discriminant.
One issue of concern might be a variant record, where one variant has a component with an access discriminant, while the other doesn't. What happens if in an assignment the LHS does not have the component, and the RHS has the component. Could this create a dangling reference? For example:
type Rec(Acc_Disc : access Integer) is null record;
type Var_Rec(B : Boolean := False) is record case B is when False => null; when True => R : Rec(Acc_Disc => ???); end case; end record;
Global_Var_Rec : Var_Rec; -- unconstrained, defaults to False
...
Local_Var_Rec : Var_Rec(True);
...
Global_Var_Rec := Local_Var_Rec; -- Is there a danger here?
We could have a problem if Local_Var_Rec.R.Acc_Disc designated a local object. But since there is no default for Acc_Disc, its value must be specified in the component definition for R, and the only objects it could designate are objects that live as long as the Var_Rec type, or an object designated by an outer access discriminant (there isn't one in this case), or an object created by an allocator. (If the type were limited, R.Acc_Disc could be initialized to Var_Rec'Access, but since it is non-limited, we don't have that problem.)
An allocator is not a problem, because it is evaluated at the time the enclosing type is elaborated, and hence it's accessibility level is that of the enclosing type. This may be a bit of a surprise to the user, but this is already true for limited types with access discriminants. By contrast, if the allocator were specified as a default value for the discriminant (of a limited type, given our other rules), then it would not be evaluated until the enclosing object is created, and its accessibility level would be that of the object rather than the type.
We considered disallowing allocators in this context, because of the somewhat surprising accessibility level, but since they are already permitted for limited types, we wouldn't gain much by disallowing them only for nonlimited types. A compiler might want to warn about this case, because the "lifetime connection" we talk about in our language design principles doesn't apply for these. They are tied to the lifetime of the component's subtype indication, rather than to the component. And in this case, the subtype indication is elaborated when the enclosing type is elaborated.
The other ways to initialize a nested access discriminant are not a problem, because the only way the value could refer to a local object is via an access discriminant of the enclosing object, and since that outer access discriminant couldn't have a default, again, it couldn't be changed by an assignment.
!example
--!corrigendum
!ACATS test
ACATS B and C-Tests should be created to check these rules.
!appendix

****************************************************************

Questions? Ask the ACAA Technical Agent