Version 1.3 of ais/ai-00402.txt

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

!standard 03.07(10)          05-03-11 AI95-00402/02
!standard 03.07(11)
!standard 06.05(20)
!class amendment 05-01-27
!status Amendment 200Y 05-03-11
!status ARG Approved 7-0-3 05-02-13
!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 which 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-344:
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
(See discussion.)
!corrigendum 3.7(10)
Replace the paragraph:
A discriminant_specification for an access discriminant shall appear only in the declaration for a task or protected type, or for a type with the reserved word limited in its (full) definition or in that of one of its ancestors. In addition to the places where Legality Rules normally apply (see 12.3), this rule applies also in the private part of an instance of a generic unit.
by:
Default_expression
s shall be provided either for all or for none of the discriminants of a known_discriminant_part. No default_expressions are permitted in a known_discriminant_part in a declaration of a tagged type [or a generic formal type].
A discriminant_specification for an access discriminant may have a default_expression only in the declaration for a task or protected type, or for a type with the reserved word limited in its (full) definition or in that of one of its ancestors. In addition to the places where Legality Rules normally apply (see 12.3), this rule applies also in the private part of an instance of a generic unit.
!corrigendum 3.7(11)
Delete the paragraph:
Default_expression
s shall be provided either for all or for none of the discriminants of a known_discriminant_part. No default_expressions are permitted in a known_discriminant_part in a declaration of a tagged type [or a generic formal type].
!corrigendum 6.5(20)
Replace the paragraph:
The exception Program_Error is raised if this check fails.
by:
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 either of these checks fail, Program_Error is raised.
!ACATS test
ACATS B and C-Tests should be created to check these rules.
!appendix

From: Tucker Taft
Sent: Sunday, January 23, 2005  1:33 PM

Pascal and others expressed concern about the proposed
rule that access discriminants for nonlimited types
would have the accessibility of the enclosing *type*,
while access discriminants for limited types would
have the accessiblity of the enclosing *object*.

After some discussion, we concluded that we wanted
the nonlimited ones to adopt the current rule
for limited ones if at all possible.  Together
we determined that about the only way to make this
work was to disallow defaults for access discriminants
when used on a nonlimited type.  I volunteered to
write up an AI on this topic.  And here it is. [Editor's note: this
is version /01.]

[For what it is worth, I went off on a tangent worrying
about components whose access discriminants are
initialized by allocators of an anonymous type
(Steve loves those!).  After inventing various complicated
rules to deal with them, I discovered that they
aren't a problem after all, because the subtype
indication of a component is elaborated when the
enclosing *type* is elaborated, rather than when
the enclosing object is created.  So they end
up with a "safe" accessibility level after all,
and all instances of the type designate the
same allocated object via this component's
access discriminant, and all is copacetic.]

I presume we'll discuss this in the shadow
of the Eiffel tower...

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

From: Randy Brukardt
Sent: Monday, January 24, 2005  9:27 PM

> AARM Language Design Philosophy note:

I got a good laugh out of this one. Usually, we try to use existing
categories. I suppose you meant "Language Design Principles" (or
"MetaRules", as it's known in the AARM source).

> Modify the paragraph added after 6.5(20) by AI-354:

I think you mean AI-344. I don't think "Group execution-time budgets" have
anything to do with accessibility checks!

What I don't understand about this is why there isn't any problem on
assignment of these guys. You went into a lengthy explanation of why variant
record components aren't a problem, but (a) I don't understand why variants
would have anything to do with it; (b) why this isn't a problem on top-level
discriminants. There isn't any accessibility check defined for components of
any kind, after all; and there certainly isn't any defined for record types.
So what prevents:

    type Rec(Acc_Disc : access Integer) is null record;

    Global_Rec : Rec (...);

    procedure P is
       I : aliased Integer := ...;
       Local_Rec : Rec (I'Access);
    begin
       Global_Rec := Local_Rec;
    end P;

Ah, got it; the *discriminant check* prevents problems. The AI really,
really ought to show an example like this and make this point. Talking about
discriminant defaults hardly makes the appropriate connection (probably
because they are such a lousy way to define mutuable records - sigh). We
shouldn't have to write examples each time to figure out what is going on...

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

From: Tucker Taft
Sent: Tuesday, January 25, 2005  10:57 AM

>>AARM Language Design Philosophy note:
>
>
> I got a good laugh out of this one. Usually, we try to use existing
> categories. I suppose you meant "Language Design Principles" (or
> "MetaRules", as it's known in the AARM source).

Yes, that is what I meant.

>
>
>>Modify the paragraph added after 6.5(20) by AI-354:
>
>
> I think you mean AI-344. I don't think "Group execution-time budgets" have
> anything to do with accessibility checks!

Details, details...

>
> What I don't understand about this is why there isn't any problem on
> assignment of these guys. You went into a length explanation of why variant
> record components aren't a problem, but (a) I don't understand why variants
> would have anything to do with it; (b) why this isn't a problem on top-level
> discriminants. There isn't any accessibility check defined for components of
> any kind, after all; and there certainly isn't any defined for record types.
> So what prevents:
>
>     type Rec(Acc_Disc : access Integer) is null record;
>
>     Global_Rec : Rec (...);
>
>     procedure P is
>        I : aliased Integer := ...;
>        Local_Rec : Rec (I'Access);
>     begin
>        Global_Rec := Local_Rec;
>     end P;
>
> Ah, got it; the *discriminant check* prevents problems. The AI really,
> really ought to show an example like this and make this point. Talking about
> discriminant defaults hardly makes the appropriate connection (probably
> because they are such a lousy way to define mutual records - sigh).

It made the connection for me, but you are probably right,
not everyone has that particular synapse wired very tightly.
In any case, the whole point of disallowing defaults is
to disallow changing the discriminant as a result of
an assignment statement.

> ... We
> shouldn't have to write examples each time to figure out what is going on...

Agreed.

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


Questions? Ask the ACAA Technical Agent