Version 1.2 of 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; --
...
Local_Var_Rec : Var_Rec(True);
...
Global_Var_Rec := Local_Var_Rec; --
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
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 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). 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