Version 1.1 of acs/ac-00034.txt

Unformatted version of acs/ac-00034.txt version 1.1
Other versions for file acs/ac-00034.txt

!standard 13.14( 3)          02-05-03 AC95-00034/01
!class amendment 02-05-03
!status received no action 02-05-03
!subject Does a body freeze an incomplete type?
!appendix

From: Randy Brukardt
Sent: Monday, April 29, 2002  9:13 PM

I recently received a note from a real Ada user who was suggesting the
addition of an ACATS (sub)test to detect freezing of incomplete types by a
nested body. Apparently, a new release of a compiler that they use broke
some of their code.

A small sample of the problem is:

  procedure Case_16 is
      type Foo;                -- is an incomplete type

      procedure Nested is      -- a body that causes freezing [RM 13.14(3)]
      begin
	  null;
      end Nested;

      type Foo is new Integer; -- so the frozen type cannot be completed
  begin
      null;
  end Case_16;

The latest version of one of the compilers they use has started to detect
this problem. None of the other compilers that they use (including a
previous version of this compiler) detects this error. I've tried this
program on two additional compilers, both of which also happily accept this
program.

The user reports that the vendor provided the following explanation:

"The <..> compiler is doing the right thing in rejecting this.

The second sentence of RM95 13.14(3) reads: "a noninstance body causes
freezing of each entity declared before it within the same
declarative_part." The body of procedure Nested is a noninstance body, so it
freezes every entity declared before it, including the incomplete type Foo.
Note that the second sentence of 13.14(3) (unlike the first sentence)
doesn't have a special clause excluding incomplete types, so incomplete
types get frozen too.

The fact that Nested does not reference Foo is irrelevant. That's in order
to preserve the equivalence between proper bodies and stubs, as explained in
AARM 13.14(3.d)."

It appears to me that the vendor is correct. However, since virtually every
existing compiler allows this, this appears to be a case where changing the
language would make more sense than enforcing the language as it is written.
Adding an ACATS test (as the user wants me to do), would force vendors to
break existing programs, and there seems to be no problem (or value) to
doing so.

Freezing of incomplete types can't do anything interesting (there is no
representation to set, and virtually all uses are illegal anyway), so it
seems odd to mandate an error here.

Should this paragraph's wording be changed??

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

From: Robert Dewar
Sent: Monday, April 29, 2002  9:19 PM

<<Adding an ACATS test (as the user wants me to do), would force vendors to
break existing programs, and there seems to be no problem (or value) to
doing so.>>

Don't be too sure of your power :-) It might also cause vendors to decide
that ACATS validations are not worth the effort!

So indeed I think it makes more sense to see if this can be allowed in
some way. Disclaimer: GNAT is one of the compilers that accepts this
without complaint. Of course it would be easy to fix, but we would
hesitate to make the fix ...

It sure is one of the most annoying things when you discover that you have
not been testing some legality condition, and you fix and improve your
compiler, causing headaches out there in userland!

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

From: Pascal Leroy
Sent: Tuesday, April 30, 2002  3:20 AM

> Freezing of incomplete types can't do anything interesting (there is no
> representation to set, and virtually all uses are illegal anyway), so it
> seems odd to mandate an error here.

I am going to vehemently oppose any loosening of the freezing rules.  The
implementation in question didn't change for the sole purpose of annoying its
users.  It used to fail with an internal error in some cases (more complicated
than the above) and we always prefer to statically reject some construct (even
if that means that users will have to fix their code) than to die with internal
errors or to generate incorrect code.

We do an awful lot of stuff at the freezing point of a type (among other things,
we choose the type layout) and the rest of the compiler depends on the freezing
rules having the right effect.

Especially important for us is the equivalence between proper bodies and
subunits (an invariant which has been around since ca. A.D. 1980) as in some
cases we "suck up" the body of subunits during compilation.  Breaking that
equivalence could cause the freezing points to change during the "sucking up"
process, which would be disastrous.

I am in favor of fixing the language when there is something that is actually
broken and is a nuisance to users.  The name resolution rules for 'Access were
an example of that.  But here the fix is simply to make Nested a two-part
subprogram, and move its body after the completion of Foo.  Nothing that
deserves a language change.

I am (evidently) not opposed to checking this in the ACVC, but I won't insist on
that.  There are much more important things that are not tested.  This is after
all a rather odd cornercase.

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

From: Robert Dewar
Sent: Tuesday, April 30, 2002  5:39 AM

I find Pascal's argument convincing here, and I am afraid that any tinkering
with the rules is very risky. It's always unfortunate when you find you have
failed to diagnose an error, and users get understandably irritated, but
the fact that compilers have been wrong is not sufficient to change the
language unless the change makes very clear semantic sense. In this case
it does not.

This is a very odd construct, and it is hard to find an example where the
work around is not trivial.

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

From: Robert A. Duff
Sent: Tuesday, April 30, 2002  7:43 AM

Me, too, although after pondering for 20 or 30 minutes,
I can't come up with any actual semantic anomalies that
would arise from relaxing the rule.

I am mildly in favor of testing it, either way.
There is some value in uniformity across implementations.

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

From: Randy Brukardt
Sent: Tuesday, April 30, 2002  3:15 PM

Pascal said:

I agree that we don't want to change something for the sake of changing it.
But I went through the same exercise as Bob before posting the suggestion
(which, IMHO, does belong on Ada-Comment, as it is a request to change the
language), and I can't think of any actual problems either.

Pascal, would it be possible to see one of the examples which caused a
problem for your compiler? That could be convincing...

> I am mildly in favor of testing it, either way.
> There is some value in uniformity across implementations.

Well, this is a pretty unlikely case (an incomplete type in a body). I'd
rather concentrate new tests on areas where differences could cause programs
to silently malfunction (such as changing the discriminant of a constrained
type), rather than ones where the program either works properly or gets a
compiler error.

Putting on a different hat, I don't want to have to go back into that code
in Janus/Ada: it was a bear to get working properly (there are a whole bunch
of special cases for incomplete types). I suspect that other implementors
feel similarly, especially given the low likelihood of occurrence.

I'll put it on the list of things to test, marked low priority. Probably it
would only get tested in the unlikely event that there actually was a
significant budget to create new tests.

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

From: Pascal Leroy
Sent: Thursday, May 2, 2002  3:35 AM

> Pascal, would it be possible to see one of the examples which caused a
> problem for your compiler? That could be convincing...

Well, I was hoping that I wouldn't have to type a long message, but apparently
Randy won't let me get off the hook that easily.

I was not necessarily arguing that it wouldn't be possible to come up with a
consistent set of language rules (I have not explored this issue fully), but I
was arguing that breaking invariants might cause trouble for implementations,
and possibly confuse users.

It is useful to discuss separately the case of proper bodies and the case of
stubs.

1 - For proper bodies, I agree that the rules of RM95 3.10.1 about premature
usages of incomplete types are way more restrictive than the freezing rules, so
relaxing the freezing rules would not change the legality. It could still break
implementations, though.  Consider the following example:

    procedure P;
    pragma Inline (P);

    type T;

    procedure Q is
    begin
        P;
    end Q;

    type T is ...;

    procedure P is
        X : T;
    begin
        ...
    end P;

The old Alsys implementation used to do inlining on the syntax tree, before
choosing the layout of types.  Such an implementation would end up, after
inlining, with an object declaration (X in the inlined call to P) occurring
before the completion of T, at a place where the layout of T has not been
decided yet.  This doesn't look too good.

2 - For stubs, new rules would be added to the language.  Consider the example:

    type T;
    procedure Q is separate;
    type T is ...;

As far as I can tell, you have three choices here:

a - Stubs freeze incomplete types, even though proper bodies don't.  This breaks
the equivalence between stubs and proper bodies, and it means that, during
development, changing a proper body into a stub could cause compilation errors
to appear.  This would surprise users.  It might also spell trouble for those
implementations that generate a stub for generic instantiations occurring before
the generic body has been entered in the library, although I have not thought
out the consequences.

b - Stubs don't freeze, but the rules of 3.10.1 about premature usages of
incomplete types apply in the subunit.  This is an entirely new mechanism for
compilers: when processing the subunit, a new check would be needed on each
usage of a type to see if it has an incomplete type declaration, and the stub
occurs between the incomplete type and its completion.

c - Stubs don't freeze, and the rules of 3.10.1 don't apply in the subunit
(after all, the completion exists when we process the subunit).  The subunit
could for instance declare objects of type T.  But then an implementation which
"sucks up" subunits in their parent during the compilation process would have a
hard time, because it would again end up with an object declaration occurring
before the type has been laid out.  (We do this sort of things.)

Was that convincing?

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

From: Robert Dewar
Sent: Thursday, May 2, 2002  6:32 AM

This one is indeed out of the question for GNAT, which *always* "sucks up"
subunits as Pascal describes.

I think that Pascal's explanation is clear here.

His solution 1 is ugly for users

His solution 2 is ugly for compilers

His solution 3 is unacceptable

The situation is very unusual, and in almost all cases the fix is trivial.
I think there is insufficient reason to go tinkering around with compilers
here. Yes, all compilers make mistakes and sometimes allow things they
shouldn't. Yes, it's annoying to users, when this is corrected. The mere
fact that all compilers have made the same mistake is not enough reason to
go tinkering with the language unless the tinkering is a clear improvement.
That's not the case here, so let's all of us get to work and fix our compilers.

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

From: Ed Schoenberg
Sent: Thursday, May 2, 2002  8:21 AM

Note that GNAT implements b) already: we inline the subunit, and therefore
detect premature uses of the incomplete type as expected. So this approach
might be ugly for OTHER compilers :-)!

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

From: Robert Dewar
Sent: Thursday, May 2, 2002  9:14 AM

:-)

(your OTHERS compiler note)

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

From: Tucker Taft
Sent: Thursday, May 2, 2002  8:39 AM

And just to add to the mix, AdaMagic inlines subunits if and only if they
are not library level, except of course if they are generics, in which case...

I agree we should leave this sleeping dog alone completely.

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

From: Robert Dewar
Sent: Thursday, May 2, 2002  9:15 AM

> I agree we should leave this sleeping dog alone completely.

except that we should fix the bug, and I suppose that a test should be
put in :-(

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

From: Robert A. Duff
Sent: Thursday, May 2, 2002  8:34 AM

> a - Stubs freeze incomplete types, even though proper bodies don't.  This breaks
> the equivalence between stubs and proper bodies, and it means that, during
> development, changing a proper body into a stub could cause compilation errors
> to appear.  This would surprise users.

There are already cases (inherited from Ada 83) where you can't change a
proper body into a subunit, and vice-versa.  But we should not make it
worse.

>...It might also spell trouble for those
> implementations that generate a stub for generic instantiations occurring before
> the generic body has been entered in the library, although I have not thought
> out the consequences.
>
> b - Stubs don't freeze, but the rules of 3.10.1 about premature usages of
> incomplete types apply in the subunit.  This is an entirely new mechanism for
> compilers: when processing the subunit, a new check would be needed on each
> usage of a type to see if it has an incomplete type declaration, and the stub
> occurs between the incomplete type and its completion.

Choice b doesn't sound like such a big deal to me.  The compiler already
does a check on every reference to a type, to see if it is incomplete.
Yeah, this is a new situation in which to do the check, but it doesn't
seem Earth-shattering.

If the rule is changed, Choice b seems like the right answer.  However,
I still agree with Pascal and Robert that the proposed change is not
worth it.

> c - Stubs don't freeze, and the rules of 3.10.1 don't apply in the subunit
> (after all, the completion exists when we process the subunit).  The subunit
> could for instance declare objects of type T.  But then an implementation which
> "sucks up" subunits in their parent during the compilation process would have a
> hard time, because it would again end up with an object declaration occurring
> before the type has been laid out.  (We do this sort of things.)

Choice c is madness.  ;-)

procedure P is
    type T;
    package Sub is
        function F return Integer;
    end Sub;
    package body Sub is separate;
    type T is array (Integer range 1..F) of Boolean;
begin
    ... -- Print out the value of T'Last
end P;

separate(P)
package body Sub is
    function F return Integer is
    begin
        return T'Last;
    end F;
end Sub;

What should the above program print?

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


Questions? Ask the ACAA Technical Agent