Version 1.5 of ais/ai-00306.txt
!standard 4.3.2 (05) 03-05-22 AI95-00306/03
!class binding interpretation 02-08-28
!status Amendment 200Y 03-02-18
!status WG9 approved 03-06-20
!status ARG Approved 7-0-1 03-02-09
!status work item 02-10-09
!status received 02-08-07
!qualifier Error
!priority Low
!difficulty Medium
!subject Class-wide extension aggregate expressions
!summary
If the ancestor_part of an aggregate is an expression, the expression must
not be dynamically tagged.
!question
Is the aggregate in the following example illegal? (Yes.)
package Foo is
type T is tagged null record;
function F (X : T) return T;
type TT is new T with null record;
function F (X : TT) return TT;
end Foo;
package body Foo is
function F (X : T) return T is
begin
return X;
end F;
function F (X : TT) return TT is
begin
return X;
end F;
procedure P (X : in T'Class) is
V : TT := (F (X) with null record); --
begin
null;
end P;
end Foo;
This appears to be legal, because the type of the
ancestor expression is T, and the aggregate's type (TT) is derived
from T (through one or more record extensions...). This is true in spite
of the fact that F (X) is a dispatching call.
It seems this should be illegal, as it would require the expression object of
T'Class (which might be an object of TT) to implicitly "truncate" to type T.
Usually, this is illegal by 3.9.2(9), but that rule doesn't apply in this
case because the expected type is not a specific tagged type. (4.3.2(4)
specifies "some non-limited tagged type".)
!recommendation
(See summary.)
!wording
Add the following rule as the second sentence of 4.3.2(5):
If the ancestor_part is an expression, it shall not be dynamically tagged.
!discussion
The current rules for resolution of an ancestor expression in an
extension aggregate permit the expression to be dynamically tagged.
This is because the name resolution rule in 4.3.2(4) specifies that
the expression can be of any type in the class of nonlimited tagged
types, and the restriction in 3.9.2(9/1) that disallows dynamically
tagged expressions only applies if "the expected type for an expression
or name is some specific tagged type". If an ancestor expression for
an aggregate of type TT is a dispatching call of the form F (X),
returning a specific type T that is an ancestor of type TT, then
it will satisfy the resolution rule in 4.3.2(4) as well as the
derivation rule of 4.3.2(5).
It's clearly undesirable to allow a dispatching call as an ancestor
expression, since logically the result of such a call can be of any
type in the class. Even if the semantics were specified to involve
a conversion to the specific root type, although this would be
well-defined, it would be error-prone and probably not reflect the
intent of the programmer (it's more likely to indicate a coding
mistake).
One approach to fixing the rules is to try and make a change that
allows the restriction of 3.9.2(9/1) to apply. The simplest change
seems to be to revise 4.3.2(4) to require an expression of any
specific nonlimited tagged type rather than any nonlimited tagged
type. The problem is that the rule in 3.9.2(9/1) is stated to only
apply if "the expected type is some specific tagged type". However
the wording in 4.3.2(4) talks about expecting a type in a class of
types, and that doesn't seem to accord with expecting some specific
type. It's not clear how 3.9.2(9/1) could be cleanly reworded to
cover this case. Thus, this approach was rejected.
Therefore, we add an additional legality rule to 4.3.2(5).
!corrigendum 4.3.2(5)
Replace the paragraph:
If the ancestor_part is a subtype_mark, it shall denote a specific
tagged subtype. The type of the extension_aggregate shall be derived from
the type of the ancestor_part, through one or more record extensions (and
no private extensions).
by:
If the ancestor_part is a subtype_mark, it shall denote a specific
tagged subtype. If the ancestor_part is an expression, it shall not
be dynamically tagged. The type of the extension_aggregate shall be
derived from the type of the ancestor_part, through one or more record
extensions (and no private extensions).
!ACATS test
A B-Test should be created to check that this rule is checked.
!appendix
From: Gary Dismukes
Sent: Wednesday, August 7, 2002 2:58 PM
One of our developers ran across a problem involving extension aggregates
that raised a semantic issue. The question is whether the aggregate in
the following example is illegal:
package Foo is
type T is tagged null record;
function F (X : T) return T;
type TT is new T with null record;
function F (X : TT) return TT;
end Foo;
package body Foo is
function F (X : T) return T is
begin
return X;
end F;
function F (X : TT) return TT is
begin
return X;
end F;
procedure P (X : in T'Class) is
V : TT := (F (X) with null record); -- Error?
begin
null;
end P;
end Foo;
By my reading of the rules this is legal, because the type of the
ancestor expression is T, and the aggregate's type (TT) is derived
from T (through one or more record extensions...). So this appears
to be legal in spite of the fact that F (X) is a dispatching call.
It seems that this should be illegal, but unless I'm missing something
the RM doesn't disallow it. If it's agreed that it's currently legal,
then I think that it's desirable to open an AI that would disallow it.
Even if the dynamic semantics of what such an aggregate means are well
defined (though actually I think they're not), this construct seems
harmful to allow since it's almost certainly not what the programmer
intended to write.
What do others think?
****************************************************************
From: Robert Dewar
Sent: Wednesday, August 7, 2002 4:04 PM
I definitely agree this example should be illegal. In fact I think it *is*
illegal on the grounds that the RM does not say silly things :-) :-)
****************************************************************
From: Bob Duff
Sent: Wednesday, August 7, 2002 3:36 PM
I think it's illegal by RM-3.9.2(9/1). F(X) is dynamically tagged,
but it's not a controlling operand.
****************************************************************
From: Gary Dismukes
Sent: Wednesday, August 7, 2002 4:16 PM
I had looked at that rule, but as far as I can see it doesn't apply in
this case, because the expected type is not "some specific tagged type".
The ancestor expression is "expected to be of any nonlimited tagged type".
****************************************************************
From: Robert Eachus
Sent: Saturday, August 10, 2002 10:36 AM
For me the interesting question is whether:
V: TT := TT'((F(X) with null record)); -- Legal?
...is legal. As far as I can see, it is. Certainly:
Y: T := (F(X) with null record);
V: TT := TT(Y);
...is illegal. So the question is whether an implicit type conversion
should be allowed in this situation. My feeling is that it should be
explicit, since it is very unclear otherwise whether the user is
expecting TT'(F(X) with null record) or TT(F(X) with null record).
Yeah, I know that most users have a hard time telling which one they
really did mean, but at least we can refuse to hide the choice. (Yes, I
know that the choices as stated may not be technically legal, take it as
a placeholder for making the case under discussion legal.) Incidently
GNAT seems to have real problems with this case and variants on it. I
won't submit a bug report until I know what the right result is, but one
problem seems to be an indefinite loop in type resolution..
****************************************************************
From: Gary Dismukes
Sent: Wednesday, October 9, 2002 8:53 PM
This is a draft of AI-306. Two possible wording changes are discussed,
so after we agree on the right fix it should be possible to condense
the !wording and !discussion sections.
(* Editor's note: This is version 01 of the AI. *)
****************************************************************
From: Tucker Taft
Sent: Wednesday, October 9, 2002 9:52 PM
I prefer one new simple rule to adding complexity
to an already complex rule. Hence, I prefer
solution 1.
****************************************************************
From: Robert A. Duff
Sent: Thursday, October 10, 2002 12:19 PM
> First solution:
>
> Add the following rule as the second sentence of 4.3.2(5):
>
> {If the ancestor_part is an expression, it shall not be dynamically tagged.}
I like that.
> The above sentence definitely seems to fix the problem, but the following
> change would avoid adding a new rule:
>
> Second solution:
>
> Revise the second sentence of 4.3.2(4) to read:
>
> If the ancestor_part is an expression, it is expected to be of any {specific}
> nonlimited tagged type.
>
> However, it's not clear that the second proposed solution will work,
> as the "expected to be of any ... type" wording still seems to prevent
> 3.9.2(9/1) from applying. See discussion.
Whether it works or not, it seems undesirable to me. I don't like
making the Name Resolution rules too precise. E.g, if there's a
function F that return T'Class, and a different F that returns T,
I don't want the compiler to pick one of them -- I want it to be
ambiguous. This rule is already too precise, IMHO. I would have left
out the part about "nonlimited" and "tagged", which only serve to aid in
the writing of obscure code. Adding "specific" just makes it worse.
So I prefer the First Solution.
****************************************************************
Questions? Ask the ACAA Technical Agent