Version 1.3 of ais/ai-00306.txt

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

!standard 4.3.2 (05)          03-02-18 AI95-00306/02
!class binding interpretation 02-08-28
!status Amendment 200Y 03-02-18
!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); -- Error? (Yes.) 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...). So this appears to be legal 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 jibe 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