Version 1.5 of ais/ai-00217.txt

Unformatted version of ais/ai-00217.txt version 1.5
Other versions for file ais/ai-00217.txt

!standard 10.1.2 (01)          00-04-13 AI95-00217/03
!standard 3.10.1 (02)
!class amendment 99-03-23
!status work item 99-03-23
!status received 99-03-23
!priority High
!difficulty Hard
!subject Handling Mutually Dependent Type Definitions that Cross Packages
!summary
A new kind of "with" clause, the "with type" clause, is proposed to support mutually dependent type definitions that cross packages. In addition, a new variant of the incomplete type definition for incomplete tagged types is proposed.
!problem
Ada 95 does not allow two package specifications to depend on each other. This can cause problems when creating Ada packages to use in interfacing with code written in languages like Java. In Java, there are no restrictions against mutually dependent class definitions. When expressed in Ada, this means that a package may define a type which is used in a second package's specification, while also making use of a type defined in that package specification. This is clearly not allowed due to restrictions against cyclic semantic dependences. Cyclic semantic dependences cannot generally be allowed in Ada because of the order of elaboration requirements.
This problem also can occur when organizing large object-oriented systems solely in Ada 95. The "solution" is to place all of the types in a single package. However, the rules for defining primitive operations of a type require that most of the operations of the type also must be declared in the package. This leads to gigantic packages. An alternative solution is to add additional indirection into the data structures. This adds complexity as well as space and time costs at runtime.
!proposal
RM95 3.10.1(2) is amended as follows:
incomplete_type_definition ::= type identifier [discriminant_part] [ is tagged ];
Insert after RM95 3.10.1(10):
An incomplete type definition with the words "is tagged" introduces a @i<tagged incomplete> type with the given identifier. A tagged incomplete type may appear in all the same contexts as an incomplete type, but in addition, it may be used as the type of a parameter or of a function result, and as the prefix in a reference to the Class attribute. The full type definition of a tagged incomplete type must be for a tagged type.
RM95 10.1.2 is amended as follows:
with_type_clause ::= with type library_unit_package_name . type_identifier [ is tagged ];
A with_type_clause may appear only within a context clause of a compilation unit. [OPEN ISSUE: Should only a library unit package be allowed to have a "with type" clause? It might be useful to use "with type" on a generic.] A with_type_clause introduces the specified identifier within the specified package as denoting a (tagged) incomplete type. The full type definition for the type is presumed to exist in the specified package, but no semantic dependence is created on this package, and the package need not be present in the environment at the time the with_type_clause is processed.
Any compilation unit that has a semantic dependence on another compilation unit which has a "with_type_clause," as well as a dependence on the package identified by the with_type_clause, must verify that a type declaration (not just a subtype declaration) for the named type does in fact appear within the visible part of the package. In addition, if the package is a descendant of a private child of some ancestor package, then the unit with the with_type_clause must also be a descendant of a private child of that same ancestor package.
If the with_type_clause included "is tagged," then the type must be a (visibly) tagged type.
OPEN ISSUE: Completeness checks for "with type" types. Proposal: Follow Erhard's suggestion, and not perform any completeness checks at link-time. At compile-time, we perform a completeness check every time the unit with the "with type" and the package with the type are both depended-on semantically by a single compilation unit. There seems no convincing rationale for why it helps the user to require that the package declaring the "withed" type be included in an executable, if there is no "real" use of the type in the closure of the main subprogram.
!wording
(See proposal.)
!example
Here is the classic case of mutual dependence, where an employee belongs to a particular department, and a department has a manager who is an employee.
with type Departments.Department; package Employees is
type Employee is private; procedure Assign_Employee(E : access Employee; D : access Departments.Department); ... type Dept_Ptr is access all Departments.Department; function Current_Department(D : access constant Employee) return Dept_Ptr;
end Employees;
with type Employees.Employee; package Departments is type Department is private; procedure Choose_Manager(D : access Department; Manager : access Employees.Employee); ... end Departments;
Note that if the Employee type needs to have a field in which it can store an access value designating its department, then we can define an access-to-Department type (Dept_Ptr above). If we make this a general access type, the proposed implicit conversion rules will allow use of a value of any access-to-Department type to initialize the field.
!discussion
This proposal attempts to resolve several problems encountered when creating Ada packages to use in interfacing with code written in languages like Java, and in using various common object-oriented programming paradigms. (A related problem is solved in the proposal contained in AI-00230.)
In Java (and Eiffel), there are no restrictions against mutually dependent class definitions. When expressed in Ada, this means that a package may define a type which is used in a second package, while also making use of a type defined in that package. This is clearly not allowed due to restrictions against cyclic semantic dependences. Cyclic semantic dependences cannot generally be allowed in Ada because of the order of elaboration requirements.
The "with_type" clause eliminates the cyclic dependence problem by allowing a name for an incomplete type to be introduced without needing those type definitions to have been elaborated. The existing limitations on incomplete types are sufficient to protect against inappropriate usage of such types prior to their full elaboration.
One alternative approach to "with type" would be to use pragma Import to import the full definition of a type from some other package, without creating a semantic dependence on that package. For example:
type T; pragma Import(Ada, T, External_Name => "Some_Package.T");
would introduce an incomplete type named T and then specify that its full definition appears in Some_Package. Similarly, an access type could be declared, and then imported from some other package.
Again, any compilation unit that depends both on the package where such a pragma Import appears, as well as on the package identified in the External_Name, would have to make sure that the type definitions were compatible. Furthermore, it would have to treat the types as aliases for one another, allowing free interconversion between the two.
An advantage of this pragma Import approach is that it requires no new syntax, nor any new mechanisms for introducing names. However, having the Import have the effect of creating a type equivalence is certainly stretching the original intent for Import, and would clearly require extra work to support in the compiler (though probably less than that required for the "with type" approach).
A disadvantage of this pragma Import approach is that it creates implicit connections between compilation units which are not visible in the context clause. Also, if the imported type is given the same identifier as it has in the package where its full definition appears, then compilation units that "use" both packages will find the type names hiding one another.
We considered a complementary "with access type" mechanism. This proved to be problematic. The problems encountered relate to issues such as "thin" versus "fat" access-to-array types, conventions associated with access types, access subtypes, operational items of access types, etc. An implicit conversion proposal (see AI-00230) has been defined to replace it.
!appendix

Randy Brukardt  99-03-29

I've reformatted Tucker's submission into the format discussed at
the recent ARG meeting. Note that I haven't made any of the changes
considered at that meeting.

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

From: Dan Eilers
Sent: Saturday, May 15, 1999 6:28 PM

> If the second form of with_type_clause was used, it must also
> verify that the type is an access type with a statically matching
> designated subtype.

I suspect this sentence is an obsolete reference to an earlier proposal.

> The "with_type" clause eliminates the cyclic dependence problem by
> allowing a name for an incomplete type to be introduced
> without needing those type definitions to have been elaborated.
> The existing limitations on incomplete types are sufficient to protect
> against inappropriate usage of such types prior to their full elaboration.

This proposal doesn't seem to follow the "Ada way" of breaking cyclic
dependencies between packages, which of course is to split the package
into two separately compilable parts (spec and body), where both specs
can be compiled before both bodies, providing a non-cyclic elaboration
order.

In the case of mutually dependent types, it seems that if the language
was changed to allow a package spec to be split into two separately
compilable parts (visible and private), and also to allow type renaming,
then each recursive type could be introduced in the other package as a
normal incomplete type in the visible part, and a type renaming in the
private part.

Support for separately compiled private parts is a desirable enhancement
anyway, in order to allow WITH clauses on the private part; to allow
the private part to be in a physically separate file; and to allow a
CM system to support multiple private parts for the same visible part.

Type renaming is also a desirable enhancement anyway, because the kludgy
way of type renaming via a degenerate subtype declaration doesn't introduce
the operations of a type.

> Any compilation unit that has a semantic dependence on another compilation
> unit which has a "with_type_clause", as well as a dependence on the
> package identified by the with_type_clause, must verify that the
> named type does in fact appear within the visible part of the package.

This funky rule would no longer be necessary.

I'm sure I'm not the first to propose this, but it would be nice to see
such a proposal addressed as an alternate (along with the pragma import
proposal).

        -- Dan

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

From: Tucker Taft
Sent: Saturday, May 15, 1999 9:01 PM

> > If the second form of with_type_clause was used, it must also
> > verify that the type is an access type with a statically matching
> > designated subtype.
>
> I suspect this sentence is an obsolete reference to an earlier proposal.

Oops.  Right you are.

> ...
> I'm sure I'm not the first to propose this, but it would be nice to see
> such a proposal addressed as an alternate (along with the pragma import
> proposal).

Good point.  It would be helpful to address this alternative.  It is
attractive in some ways, and we have talked about it several times
in the ARG meetings.  Ultimately we ended up rejecting it, though
without going back through the minutes I can't remember why.  So
I *should* go back through the minutes and capture the argument for
all.

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

From: Dan Eilers
Sent: Sunday, May 16, 1999 12:36 AM

> I don't understand how Dan's proposal would be used to map typical
> Java and C++ classes (which is from my point of view the major problem
> to be solved here).
>
> Perhaps Dan if you could give an example of dealing with a typical
> Java class circularity using your approach it would be clearer.

Maybe if someone could supply an example Java class implemented
in Ada using WITH TYPE, I could modify it using separate private parts.
I make no claim to have thought through all the details!!

Using the existing example:

    with type Departments.Department;
    package Employees is
        type Employee is private;
        procedure Assign_Employee(E : access Employee;
          D : access Departments.Department);
        ...
        type Dept_Ptr is access all Departments.Department;
        function Current_Department(D : access constant Employee) return
          Dept_Ptr;
    end Employees;

    with type Employees.Employee;
    package Departments is
        type Department is private;
        procedure Choose_Manager(D : access Department;
          Manager : access Employees.Employee);
        ...
    end Departments;

I would delete:
    with type Departments.Department;

and add a normal incomplete type declaration early in the visible part:
    type Department;

In the private part of package Employees, which would be compiled
separately, I would add a WITH clause on package Department,
and supply the completion of type Department as:
    type Department renames Departments.Department;

The private part would have to be compiled before any units that
WITH this package, as currently happens with contiguous private parts.

This proposal would seem to improve on the pragma import proposal
by making the "type equivalence" explicit using type renaming, as
well as making the package dependencies explicit using a WITH clause
on a separately compiled private part.

It has the nice feature that support for mutually dependent types
simply falls out, rather than requiring special language mechanisms.
It also would prevents users from asking for WITH SUPROGRAM so they
can call subprograms in packages they haven't WITH'D. ;) ;)

        -- Dan

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

From: Dan Eilers
Sent: Sunday, May 16, 1999 12:57 AM

A few minutes ago I wrote:
> The private part would have to be compiled before any units that
> WITH this package, as currently happens with contiguous private parts.

Maybe this is the subtle part.  The mutually dependent packages
would of course have to WITH each other's visible part before the
private parts were all compiled.  So it seems that WITHing only
a visible part would have to be allowed, but with restrictions
on usage of incomplete types.

        -- Dan

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

From: Pascal Leroy
Sent: Monday, May 17, 1999 5:48 AM

> Support for separately compiled private parts is a desirable enhancement
> anyway, in order to allow WITH clauses on the private part; to allow
> the private part to be in a physically separate file; and to allow a
> CM system to support multiple private parts for the same visible part.
>
> Type renaming is also a desirable enhancement anyway, because the kludgy
> way of type renaming via a degenerate subtype declaration doesn't introduce
> the operations of a type.

This sounds like a nice language feature, but the implementation difficulties
are daunting.

You have obviously two options for implementing such a model: either you delay
code generation of the clients until the private parts have been completed, or
you use a model similar to shared generics (ie type descriptors everywhere).

We tried the latter in the past, at a time when we had hardware support for
shared generics (this was done with pragmas instead of new syntax, but the
effect was the same).  We never managed to make it work in the case of a
private type declared in a generic, where the completion depended on a formal
type of the generic (although the details of what exactly went wrong escape me
now).

Even if we set aside the huge implementation effort, I am not sure that we
want to plague users with the inefficiencies of separating private parts if
all they want is to declare a few mutually-dependent types.

Also, the model you propose doesn't work as soon as there is the slightest
violation of the privacy of private types.  There are a few such violations in
the language as it stands.  It is unclear if they could be fixed in a
compatible fashion.

We are trying to solve a reasonably simple problem, which is a pain for real
users today, and is harming Ada.  I am in favor of a simple-but-clean solution
that can be defined and implemented relatively quickly, rather than aiming for
a grandiose scheme that is going to keep the ARG and the implementers busy for
another 5 years.

Pascal

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

From: Dan Eilers
Sent: Monday, May 17, 1999 4:31 AM

Actually, I am proposing that clients would typically WITH a split package
after compilation of both the visible and private parts.  In situations
where that is not feasible (e.g. mutually dependent specs), the client
would be restricted in its usage of incomplete or private types,
similar to existing restrictions on such usage.

This keeps the implementation simple.  Presumably it still solves
the original problem, since in the original proposal the types introduced
by "WITH TYPE" are also simply incomplete types, as I understand it,
with corresponding restrictions on their usage.

        -- Dan

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

From: Randy Brukardt
Sent: Monday, May 17, 1999 1:21 PM

> 2) Implicit conversions to general access types:
>
> RM95 8.6(22,25) is amended as follows:
>
>    When the expected type for a construct is a general access type T
>    (access-to-variable or access-to-constant, anonymous or named) with
>    designated type D, the type of the construct shall resolve
>    to T or to an access type that is convertible to T, and whose
>    designated type is D'Class or is covered by D.

Something seems wrong with this. Perhaps it just is redundant. I originally
thought that it meant resolve to (T or an access type that is convertible to T),
and (whose designated type is D'Class or is covered by D). But the last part
seems to be essentially the same as the rules for "convertible to T", so I don't
see the point. (Or is it just a clarification??) My other interpretation is
resolve to T or (an access type that is convertible to T, and whose designated
type is D'Class or is covered by D). That makes a bit more sense, but still
seems redundant.

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

From: Randy Brukardt
Sent: Monday, May 17, 1999 1:46 PM

My original reaction to Dan's split package proposal was much as Pascal's: it is
unimplementable. However, I have a hard time Dan would propose something like
that, so I suspect that he has something more benign in mind.

What would help greatly here is for someone to flesh out Dan's idea enough so
that we can see how it would be implemented, how it would work, and to have
something to poke holes into. Tucker has suffered enough on this line, and since
it is Dan's proposal, it would be best if he created a more complete proposal.
Some features of this idea are attractive, and it is possible that Dan has a new
angle on this that we didn't consider at the meeting.

In particular, I would like to have answers to the following:

1) What would the syntax for this look like? It would seem that the "separate"
private part would have to be declared in the original package specification
somehow. One of the obvious choices for the private part already has been used
in Ada 95.

2) What restrictions on the use of the "private-free" package specification
would be needed? Would these be obvious or counter-intuitive to the average
user?

3) How can Dan's rule (the private part must be compiled before regular withs
can be done) be reconciled with the complete elimination of compilation ordering
in Ada 95?

4) Pascal is worried about the various "private leakage" problems causing
troubles for this proposal. The main one that needs consideration is the
convention rules of AI-00117. Since those rules were adopted mainly to make C++
and Java interfacing work sensibly, the interaction with this proposal would
have to be considered.

Hopefully, someone can figure out how this would work, or find a show-stopper
which would help justify Tucker's proposal.

			Randy.

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

From: Randy Brukardt
Sent: Monday, May 17, 1999 2:27 PM

OK, I think it is time for the cold water on Tucker's current proposal. I think
adding implicit conversions to the language now is just too incompatible with
existing code to do between revisions. It certainly would make sense in Ada 0y,
but not as a "minor" enhancement to Ada 95.

Consider:

with Ada.Streams;
package Ai217A is
    type Buffer_Ptr is access all Ada.Streams.Stream_Element_Array;
    function Get_Data return Buffer_Ptr;
    -- Lots of other stuff here.
end Ai217A;

with Ada.Streams;
package Ai217B is
    type Better_Buffer_Ptr is access all Ada.Streams.Stream_Element_Array;
    function Get_Data return Better_Buffer_Ptr;
    -- Lots of other stuff here.
end Ai217B;

with Ai217A, Ai217B;
use  Ai217A, Ai217B;
procedure AI217 is
    B : Better_Buffer_Ptr := Get_Data; -- ** Oh-oh.
begin
    null;
end AI217;

If we have a "use" style user, they may have a Use clause on Ai217A to use other
features in it. Now, this program is correct Ada 95. However, if we add Tucker's
implicit conversion rule, "Oh-Oh" is ambiguous.

This example can be written in other ways, and is much more likely with tagged
types (where the designated type does not need to match). I would not be
surprised if we could also cause the problem with direct visibility and possibly
other cases. I doubt that this sort of problem would be common, but I question
if we can allow it at all.

This all would have been acceptable during the design phase of Ada 95 (these are
all new features, so they could have worked how they worked), but at this point,
I think we have a red flag. I don't see how we can adopt an amendment, with the
intent that it be used immediately, that makes some existing legal code illegal.
Perhaps we could do that with the next major revision, but not in between.

I would expect this sort of thing to occur with any (new) implicit conversion
rule, so I reluctantly conclude that we cannot use that in a solution to this or
any other problem. Honestly, given the constraints on a solution, I am beginning
to think that the problem is insoluble. Please prove me wrong.

				Randy.

*************************************************************
From: Tucker Taft
Sent: Monday, May 17, 1999 1:40 PM

Randy Brukardt wrote:
>
> > 2) Implicit conversions to general access types:
> >
> > RM95 8.6(22,25) is amended as follows:
> >
> >    When the expected type for a construct is a general access type T
> >    (access-to-variable or access-to-constant, anonymous or named) with
> >    designated type D, the type of the construct shall resolve
> >    to T or to an access type that is convertible to T, and whose
> >    designated type is D'Class or is covered by D.
>
> Something seems wrong with this. Perhaps it just is redundant. I originally
> thought that it meant resolve to (T or an access type that is convertible to
> T), and (whose designated type is D'Class or is covered by D). But the last
> part seems to be essentially the same as the rules for "convertible to T",
> so I don't see the point. (Or is it just a clarification??)
> My other interpretation is resolve to T or (an access type that is
> convertible to T, and whose designated type is D'Class or is covered by D).
> That makes a bit more sense, but still seems redundant.

This is the correct interpretation.  It isn't fully redundant
because "convertibility" is in part more restrictive
in that 4.6(13-17) gives a bunch of extra requirements,
and in part is less restrictive because, if tagged, the
only requirement is that the designated types be convertible.

Perhaps it might be clearer to state the rule as follows:

    ... the type of the construct shall resolve
    either to T, or to an access type that is convertible to T and
    has a designated type that, if tagged, is D'Class or is covered by D.

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

From: Tucker Taft
Sent: Monday, May 17, 1999 5:37 PM

> OK, I think it is time for the cold water on Tucker's current proposal. I
> think adding implicit conversions to the language now is just too
> incompatible with existing code to do between revisions. It certainly would
> make sense in Ada 0y, but not as a "minor" enhancement to Ada 95.

I don't understand that reasoning at all.  Why wait until there is
a whole lot of Ada 95 code to create an incompatibility?  There
really is no good time for an incompatibility.

In any case, I was certainly aware of the incompatibility, and
I should have highlighted it in the XI, but I don't believe
we need to consider it a "show stopper."  There is
still relatively little Ada 95 out there, and this is such an
important problem that I believe we can endure a bit of pain to
get there.  Of course compilers will need to have a "mode" switch
for a while, perhaps providing warnings about the incompatibilities
when in "compatibility" mode.  Note that this is not a "silent"
incompatibility, since it would always be caught at compile time.
Also, the existing more-stringent Name Resolution rule for qualified
expressions can be used to resolve ambiguities.

One alternative I considered (and mentioned in the XI) was to
restrict this to conversions to general access-to-classwide
types.  That would reduce the incompatibilities, but would
complicate the implicit conversion rules, since we would then
have a more complex split in access types relative to implicit conversion
of anonymous access types plus named general access-to-classwide,
versus everything else.  This split would be different than the
one for explicit conversion, which is based on general vs pool-specific.

Restricting implicit conversion to general access-to-classwide would
also not help with the problem of cross-package mutual dependence for
non-tagged types.  It seems funny to push someone to use a tagged
type just to be allowed to separate their types into separate packages.
I don't like to create "funny" incentives like that.

> ... Perhaps we could do that with the next major
> revision, but not in between.

Again, I don't buy this argument.  I also don't see any "major revision"
on the horizon.  Just a series of "minor revisions."

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

From: Randy Brukardt
Sent: Monday, May 17, 1999 9:11 PM

> > OK, I think it is time for the cold water on Tucker's current proposal.
> > I think adding implicit conversions to the language now is just too
> > incompatible with existing code to do between revisions. It certainly
> > would make sense in Ada 0y, but not as a "minor" enhancement to Ada 95.
>
> I don't understand that reasoning at all.  Why wait until there is
> a whole lot of Ada 95 code to create an incompatibility?  There
> really is no good time for an incompatibility.

Right. Therefore we should avoid them unless there is absolutely no alternative.
I don't think we are there yet...

> In any case, I was certainly aware of the incompatibility, and
> I should have highlighted it in the XI, but I don't believe
> we need to consider it a "show stopper."  There is
> still relatively little Ada 95 out there, and this is such an
> important problem that I believe we can endure a bit of pain to
> get there.

I agree this an important problem, but I don't think we can introduce a series
of small incompatibilities.

> Of course compilers will need to have a "mode" switch
> for a while, perhaps providing warnings about the incompatibilities
> when in "compatibility" mode.

You have to look at the big picture here. We are planning to work on a series of
amendments to Ada 95. If we approve these piece-meal (as is likely), we could
easily end up with a series of incompatibilities. Then compilers would need a
"mode" switch essentially to turn on or off each incompatibility individually. I
don't think that any users OR implementers will stand for that - no one will
know quite which language they are compiling.

This IS an important problem. We need a solution sooner rather than later. If we
have to introduce significant incompatibilities in order to get to a solution, I
think the adoption of the solution will be slowed substantially.

> Note that this is not a "silent"
> incompatibility, since it would always be caught at compile time.

I'm not convinced of this. The interaction between use clauses and direct
visibility seems to allow the existence of programs that change meaning
silently. That was my original concern, but I decided to concentrate on the
simpler (and easier to write) compile-time incompatibility.

> > ... Perhaps we could do that with the next major
> > revision, but not in between.
>
> Again, I don't buy this argument.  I also don't see any
> "major revision"
> on the horizon.  Just a series of "minor revisions."

A series of "small" incompatibilities is simply an unacceptable way to revise
the language. I think we can add a series of upward compatible revisions without
much resistance, but I have no illusions that incompatibilities can be
introduced without giant amount of justification. Based on my experience with
the smallest incompatibilities on the ARG, I don't think any proposal with them
will have an easy time. (Especially one that sweeps them under the rug.) I also
worry about the architectural integrity of the language if we simply add a
series of incompatible Band-Aids to the language.

If this doesn't bother anyone else, I certainly won't stand in the way of this
proposal, but I would be quite surprised if it doesn't bother someone else.

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

From: Dan Eilers
Sent: Tuesday, May 18, 1999 5:17 AM

> > In particular, I would like to have answers to the following:
>
> 1) What would the syntax for this look like? It would seem that the
> "separate" private part would have to be declared in the original package
> specification somehow. One of the obvious choices for the private part
> already has been used in Ada 95.

The syntax is probably not the most crucial part of the proposal, but
here's a stab at it anyway.

At the end of the visible part:
  private is separate;
would indicate that a private part is to be compiled separately.

At the start of the private part:
  package private p is
    ...
  end p;


> 2) What restrictions on the use of the "private-free" package specification
> would be needed? Would these be obvious or counter-intuitive to the average
> user?

I am assuming that the freezing rules of RM 13.14 would pretty much just work.

> 3) How can Dan's rule (the private part must be compiled before regular
> withs can be done) be reconciled with the complete elimination of
> compilation ordering in Ada 95?

By using the freezing rules.

> 4) Pascal is worried about the various "private leakage" problems causing
> troubles for this proposal. The main one that needs consideration is the
> convention rules of AI-00117. Since those rules were adopted mainly to make
> C++ and Java interfacing work sensibly, the interaction with this proposal
> would have to be considered.

I'm not up to speed on the "private leakage" problems.

        -- Dan

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

From: Tucker Taft
Sent: Tuesday, May 18, 1999 5:57 AM

Unfortunately, the "separate private" approach doesn't completely
solve the problem.  By inserting a "type Department;" in
the visible part of package Employees, and then a
"type Department renames Departments.Department;" in the
private part, you can successfully define operations
in package Employees that take parameters of both [access] Department
and [access] Employee.  Unfortunately, only those privy to
the private part of package Employees *knows* that type "Employees.Department"
and type "Departments.Department" are the same type.
That is not what is needed.

What is needed is a solution where the whole "world" knows that
the operations take both [access] Department and [access] Employee
parameters, including code inside package Departments, inside package
Employees, and outside both.

Hence, this means that in the *visible* package Employees there must be
a way of referring to a type that is declared in the *visible*
part of package Departments, and vice-versa.

One possible alternative solution is to create a new kind of package part
that *precedes* the conventional visible part, where incomplete or
private declarations could appear.  This has been called a package
"abstract" or "forward" part.  However, this seems like a much
"bigger" change than the "with type" approach, and requires a new kind
of "with" clause in any case, to "with" just the "abstract" part
of a package.

Another alternative that was discussed was a special "with" clause
for a package which gave visibility to all the types in a package,
but only the access types would be "complete," all other types made
visible by such a "with" clause would be incomplete.  One tentative
syntax for this was "with access P;".

So I don't think the separate private part proposal, though perhaps
interesting for other reasons, addresses the problem we are currently
most focused on solving.

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

From: Robert Dewar
Sent: Tuesday, May 18, 1999 8:04 AM

incidentally, just for information purposes. The forthcoming release of
GNAT will support a version of the "with type" proposal. Gary Dismukes
can provide details of what we will support. We need this for our Java
work, so have decided to go ahead with it now. This does not mean of
course that the ARG should adopt anything near to what we are doing,
but it does mean that "with type" is now alive in GNAT, and will be
forever.

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

From: Robert I. Eachus
Sent: Tuesday, May 18, 1999 11:27 AM

At 09:11 PM 5/17/1999 -0500, Randy Brukardt wrote:
>> > OK, I think it is time for the cold water on Tucker's current proposal.
>> > I think adding implicit conversions to the language now is just too
>> > incompatible with existing code to do between revisions. It certainly
>> > would make sense in Ada 0y, but not as a "minor" enhancement to Ada 95.
>>
>> I don't understand that reasoning at all.  Why wait until there is
>> a whole lot of Ada 95 code to create an incompatibility?  There
>> really is no good time for an incompatibility.
>
>Right. Therefore we should avoid them unless there is absolutely no
>alternative. I don't think we are there yet...

    We are approaching five years from the Ada 95 standard, and in one
sense we are already there.  If the biggest change in what becomes Ada0Y is
to fix the mutual dependence problem, great.  But I think that there are a
couple of other necessary fixes which should be incorporated.  (Possibly a
CORBA binding and a standard component library.)  In any case it is time to
start developing these "enhancements" to Ada 95, and I don't find a
simple-to-fix incompatibility to be an absolute bar.

    I do agree that we should put together ONE set of enhancements that get
officially promulgated together.  That is not to say we shouldn't encourage
compiler implementors and users to experiment with these enhancements, just
that they should be added to the language--and the validation suite--as a
group.

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

From: Tucker Taft
Sent: Tuesday, May 18, 1999 11:30 AM

Randy Brukardt wrote:
> ...
> This IS an important problem. We need a solution sooner rather than later.
> If we have to introduce significant incompatibilities in order to get to a
> solution, I think the adoption of the solution will be slowed substantially.

Perhaps we can use the internet to help here.  If we can implement
a version of this, at least in the front end, then we could make it
available for folks to run existing code against it and see what
kind of incompatibilities show up.

We know this might affect folks who are simulataneously
using general access types and overloading and/or use clauses.
A relatively simple script could identify candidate trouble
spots.

>
> > Note that this is not a "silent"
> > incompatibility, since it would always be caught at compile time.
>
> I'm not convinced of this. The interaction between use clauses and direct
> visibility seems to allow the existence of programs that change meaning
> silently. That was my original concern, but I decided to concentrate on the
> simpler (and easier to write) compile-time incompatibility.

I don't see how.  Presuming there is no "preference" rule added, and
we don't change any hiding rules, there is no way allowing more
implicit conversion could result in a different
unambiguous interpretation, since clearly the old interpretation is
not hidden and is still valid.

> ...
> Based on my experience with the smallest incompatibilities on the ARG, I
> don't think any proposal with them will have an easy time. (Especially one
> that sweeps them under the rug.) I also worry about the architectural
> integrity of the language if we simply add a series of incompatible
> Band-Aids to the language.

As far as "integrity" my goal was to make this a "seamless"
revision, in that once it had been made, you couldn't see the
seam between Ada 95 and Ada 99/00/...
I would hope that every revision has the effect of preserving the
integrity of the language, and if anything, removing non-orthogonalities
(such as the lack of anonymous access-to-constant).  I believe we
did a fairly good job of hiding "seams" between Ada 83 and Ada 95 features.

> If this doesn't bother anyone else, I certainly won't stand in the way of
> this proposal, but I would be quite surprised if it doesn't bother someone
> else.

I think with the Ada 83 => Ada 95 transition, we had a language that
had been carved in stone as far as some people were concerned, and
so any change at all was viewed suspiciously.  I hope that we now
see Ada as a more vibrant language than that, where occasional
incompatible changes will be accepted to keep the language evolving
at a reasonable rate.  As you pointed out, this incompatibility only
involves code that is using Ada 95 features, so it doesn't hit against
the Ada 83 legacy code base.

It certainly will be interesting to see how the ARG and others react.
I don't know which of us is a better predictor in this case...

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

From: Robert Dewar
Sent: Tuesday, May 18, 1999 11:59 AM

<<I think with the Ada 83 => Ada 95 transition, we had a language that
had been carved in stone as far as some people were concerned, and
so any change at all was viewed suspiciously.  I hope that we now
see Ada as a more vibrant language than that, where occasional
incompatible changes will be accepted to keep the language evolving
at a reasonable rate.  As you pointed out, this incompatibility only
involves code that is using Ada 95 features, so it doesn't hit against
the Ada 83 legacy code base.
>>

Our policy here is that we aboslutely will NOT implement any non-compatible
extensions whatever the ARG says. I do not think this kind of incompatibility
begins to be worth the minor advantages of small incremental improvements
in functionality.

I think introducing incompatibilities is WAY out of scope for the work
of the ARG in this area.

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

From: Jean-Pierre Rosen
Sent: Tuesday, May 18, 1999 12:35 PM

>The syntax is probably not the most crucial part of the proposal, but
>here's a stab at it anyway.
>
>At the end of the visible part:
>  private is separate;
>would indicate that a private part is to be compiled separately.

Agreed (I even suggested that earlier)

>At the start of the private part:
>  package private p is
>    ...
>  end p;
>
or:
   separate (P) private
      ...
   end P;

(but of course, it's only syntactic sugar)

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

From: Dan Eilers
Sent: Tuesday, May 18, 1999 9:38 PM

> Unfortunately, the "separate private" approach doesn't completely
> solve the problem.  By inserting a "type Department;" in
> the visible part of package Employees, and then a
> "type Department renames Departments.Department;" in the
> private part, you can successfully define operations
> in package Employees that take parameters of both [access] Department
> and [access] Employee.  Unfortunately, only those privy to
> the private part of package Employees *knows* that type "Employees.Department"
> and type "Departments.Department" are the same type.
> That is not what is needed.
>
> What is needed is a solution where the whole "world" knows that
> the operations take both [access] Department and [access] Employee
> parameters, including code inside package Departments, inside package
> Employees, and outside both.
>
> Hence, this means that in the *visible* package Employees there must be
> a way of referring to a type that is declared in the *visible*
> part of package Departments, and vice-versa.

Yes, I see the problem now.  RM 3.10.1/3 doesn't allow an incomplete
type declared in the visible part to be completed in the private part.
In order to make this proposal work, we seem to need some way to relax
this rule, possibly via a new pragma applied to the incomplete type.
Such a pragma would indicate two things:
  1) that the incomplete type is to be completed in the private part;
  2) that the incomplete type would not be considered a private type
     even though completed in the private part.  In other words, its
     full definition would be available to any WITHing packages
     (provided that the separate private part had been compiled).

> One possible alternative solution is to create a new kind of package part
> that *precedes* the conventional visible part, where incomplete or
> private declarations could appear.  This has been called a package
> "abstract" or "forward" part.  However, this seems like a much
> "bigger" change than the "with type" approach, and requires a new kind
> of "with" clause in any case, to "with" just the "abstract" part
> of a package.

This proposal appears to involve roughly the same amount of work as
mine, but doesn't appear to offer the user any additional benefits
comparable to the benefit of separately compiled private parts.

Adding special-purpose language machinery to solve this problem,
particularly new special-purpose syntax, seems to be an admission
of defeat.  One would like to claim that Ada's general-purpose
mechanisms are as powerful as those of other competing languages.

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

From: Franco Gasperoni
Sent: Wednesday, May 19, 1999 5:24 AM

I have carefully read Tuck's WITH TYPE proposal.  Overall I think it
is a very nice proposal.  A number of things probably need to be
hammered out, but all in all the proposal is a step in the much desired
direction.

I have spent quite a bit of time thinking about the "WITH TYPE"
problem and possible solutions in the context of interfacing Java (or
C++) and Ada.

I have a few small questions on the first part of the proposal ("with
type") that I am sending in a separate e-mail.

The point over which there is much debate, I understand, is the
second part of the proposal (implicit conversions). I am happy to see
that I am not the only one to think that Ada 95 is cumbersome when it
comes to OOP and pointers. One thing that I have never been
confortable with, in particular, is the fact that you can write in Ada 95

  type Base is tagged ....;
  type Deriv is new Base with ...;

  function Foo (X : Base'Class) return Deriv'Class;

  B : Base;
  D : Deriv;

  B_C1 : Base'Class := Foo (B);
  B_C2 : Base'Class := Foo (D);

  D_C1 : Base'Class := Foo (B);
  D_C2 : Base'Class := Foo (D);

but have no way to write the above with pointer semantics without
putting explicit conversions all over the places with no justifiable
reason.

When mapping Java or C++ to Ada all these conversions come out and
make the code not only ugly but very hard to read and undertsand (and
in addition it is very hard to explain to student why it is such a
mess :). I believe both Tuck and I have come to the same conclusion with
respect to this since we both had to face the same problem.

Tuck's proposal of basically making general access types really
"subtypes" of an anomymous general access type is a way to handle
this. Let me throw at everybody an alternative which accomplishes the
same effect as Tuck's implicit conversion proposal without any
backward incompatibility **but at the cost of added syntax** (there is
no free lunch I guess :)

The idea would be to extend the use of anonymous access types by
taking away the restriction that null pointers cannot be passed to
anonymous access types and extending the syntax as the following
example shouws (I can be more formal, but I just want to convey the feeling)

  type Base is tagged ....;
  type Deriv is new Base with ...;

  function Foo (X : access Base'Class) return access Deriv'Class;

  B : access Base;
  D : access Deriv;

  B_C1 : access Base'Class := Foo (B);
  B_C2 : access Base'Class := Foo (D);

  D_C1 : access Base'Class := Foo (B);
  D_C2 : access Base'Class := Foo (D);

The above mimicks the situations in C++ where (* Base) and (* Deriv)
are essentially access Base'Class and access Deriv'Class.

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

From: Franco Gasperoni
Sent: Wednesday, May 19, 1999 5:24 AM

QUESTION 1: Can a "with type" clause appear only in package specs, or
            can it appear in all types of compilation units ?

QUESTION 2: The proposal says

   RM95 10.1.2 is amended as follows:

       with_type_clause ::= WITH TYPE [ancestor_library_unit_name.]
                               package_identifer . type_identifier ;

   In the with_type_clause, the name:

      [ancestor_library_unit_name].package_identifier.type_identifier

   is introduced as the name of an incomplete type that is presumed to be
   declared in the package [ancestor_library_unit_name].package_identifier.
   However, no semantic dependence is created on this package, and in fact the
   package need not be present in the environment at the time the
   with_type_clause is processed.  If present, the ancestor_library_unit_name
   is treated as though it appeared in a "normal" with clause, meaning that the
   corresponding library unit must exist, and a semantic dependence is created
   upon it.

 the question is: what happens if the unit in which the "with type" occurs is
 equal to or an ancestor of ancestor_library_unit_name ?

 Example:

 with type A.B.Typ;

 package A is .... end A;

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

From: Pascal Leroy
Sent: Wednesday, May 19, 1999 7:45 AM

>  the question is: what happens if the unit in which the "with type" occurs
>  is equal to or an ancestor of ancestor_library_unit_name ?
>
>  Example:
>
>  with type A.B.Typ;
>
>  package A is .... end A;

It seems that the proposal is pretty clear, and that the effect is the same
as:

with A.B;
package A is ... end A;

Ie, presumably, the with clause is rejected.

This being said, I am not sure what is the rationale for requiring that the
ancestor_library_unit_name be in the library when the 'with type' is compiled.
The net effect is to forbid what you are describing, ie a cycle involving a
parent and some of its children.  I don't recall that this topic was ever
discussed by the ARG.  Tucker, is this a methodological restriction?  or is
there some implementation difficulty that I am missing?

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

From: Tucker Taft
Sent: Thursday, May 20, 1999 10:37 AM

Someone asked why this requirement that the ancestor library units
already exist in the environment, and why must there be a dependence
created on them.  This was an attempt to simplify implementation a bit,
but it doesn't seem that significant.  So probably the simpler proposal
is to not create any semantic dependence on anything as a result
of a "with type" clause.

One open question is whether an "incomplete" package introduced by a with-type
clause can appear in a "use" clause.  I suppose it ought to be allowed.
Of course the only name(s) that would be made visible is(are) the one(s)
introduced by the with_type clause(s) that mention the package, presuming
the package is not also mentioned in a "normal" with clause.

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

From: Pascal Leroy
Sent: Thursday, May 20, 1999 1:05 PM

I am the one who asked, and I think it would in fact complicate (slightly) the
implementation.  If "with type" clauses create a semantic dependence on the
ancestor unit, then they have to be taken into account when selecting a
compilation order.  If they don't create such a dependence, then they don't
need to be taken into account at this stage.

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

From: Franco Gasperoni
Sent: Sunday, May 23, 1999 11:53 AM

   From: Tucker Taft

   Someone asked why this requirement that the ancestor library units
   already exist in the environment, and why must there be a dependence
   created on them.  This was an attempt to simplify implementation a bit,
   but it doesn't seem that significant.  So probably the simpler proposal
   is to not create any semantic dependence on anything as a result
   of a "with type" clause.

That was me :). I really think it would simplify and make things
easier bindings-wise to

   not create any semantic dependence on anything as a result
   of a "with type" clause.

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

From: Erhard Ploedereder
Sent: Wednesday, May 19, 1999 8:31 PM

> > If the proposal were restricted to class-wide target types, then I know the
> > way to eliminate any upward incompatibility.

> Please do tell!  Don't keep it a secret...

It hooks into 8.6.(20ff) to allow for an overload resolution that implies
these implicit conversions for controlling operands (and results?) of
primitive operations only. (Admittedly, the results-part I have not really
looked at in detail, but it seems to work for that, too; Franco's message
pointed my nose at it.)

Of course that gives you the dreaded ambiguity, if multiple redefinitions
are directly visible. However, the solution there is to fix 8.6(31) to
actually allow! such ambiguity of overloading, iff
    -- the interpretation yields only primitive operations and
   (nasty Ada, allowing different defaults on redefined operations !)
    -- no parameters are defaulted

Since the call is guaranteed to be dispatching, this is completely
type-safe.

It's a patch by special-casing a rule, but it's upward compatible :-)

Also, I believe that even a more general solution will have to allow
for ambiguity that ultimately doesn't matter.
Much as we complain today about all the explicit type conversions,
users would equally complain if we effectively forbade direct
visibility to multiple (re-)definitions of such an operation.

I also thought about preference rules instead of allowed ambiguity:
    -- perfect match; that doesn't help if none of the visible ops
       matches perfectly
    -- most general match; that would work (but must retain the
       "no defaults" rule or else there would be the most nasty kind
       of incompatibility -- silent change of semantics)
       This would actually be my implementation technique to implement
       the allowed ambiguity.
    -- closest ancestor match; I saw no advantages over most general match

but then, preference rules in the language seem to have a way of biting
back, even if I don't see how the most general match preference could in
any way be harmful. In terms of RM verbage, it would be easier to digest
than allowed ambiguity.

But that's really just technical detail on how to phrase the RM to achieve
this effect, which I do believe to be fully upward-compatible.
Does anybody have a counter-example ?

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

From: Tucker Taft
Sent: Thursday, May 20, 1999 8:45 AM

Erhard Ploedereder wrote:
> ...
> but then, preference rules in the language seem to have a way of biting
> back, even if I don't see how the most general match preference could in
> any way be harmful. In terms of RM verbage, it would be easier to digest
> than allowed ambiguity.

Pretty much any preference rule causes trouble if the "preferred" item
could come from a use'd package.  If you claim this is just resolving
ambiguity between things with identical run-time semantics, then of
course the preference rule can't do any harm.  I would actually prefer
permitting ambiguity so long as all have a suitably defined equivalent
run-time semantics (including any defaulted parameters).  This would
solve an annoying problem with renames hiding one another when both
are use-visible.

> But that's really just technical detail on how to phrase the RM to achieve
> this effect, which I do believe to be fully upward-compatible.
> Does anybody have a counter-example ?

My problem is that I can't even come up with one example of what
you are trying to accomplish any more.  You seem to be limiting it
to primitive operations, but I didn't think we had a problem with
those to begin with, so we must be imagining different problems.

Can you provide a few examples where it solves a problem, and then
I will take a hack at finding a few examples where it creates a problem.

*************************************************************
From: Erhard Ploedereder
Sent: Saturday, May 22, 1999 1:19 PM

I am talking about the following example (slightly expanded from the minutes
of the last meeting):

package A is
   type t is tagged
   procedure p (x: access t);
   procedure q (x: t);
   type class_t is access tclass;
end A;

package B is
   type nt is new t;
   type class_nt is access ntclass;
   -- procedure p (x: access nt);  -- inherited or redefined
   -- procedure q(x: nt);          -- dito
end B;

o : B.class_nt;  -- someplace  (usually some component of access-designated
objects)
of: nt'Class := ...;

B.p(o); -- legal
A.p(o); -- currently illegal and a mystery/big nuisance to people with OOP
        -- experience in other languages.

-- ... especially when compared to...:
B.q(of); -- legal
A.q(of); -- legal

The usual programming style that runs into this problem is not as shown
above, but in the shape of
with A; use A; -- the package with the root class and its general ops
-- for the subsequent routines that are written using direct visibility of
-- these general ops in dispatching calls.

I am looking for a solution to this problem, which is a really pervasive
problem in describing graph structures using tagged types (such as an AST).
Your proposal of general implicit convertability solves the problem, of
course, but ....

such implicit conversion can cause an ambiguity problem
for overload resolution, if general and more specific ops are directly
visible. (I am not talking about the upward-compatibility problem yet.)
The only case where the ambiguity does not effectively matter is when
the ambiguity is among inherited/redefined primitive subprograms (with
equal defaults, if defaults apply). In all other cases (i.e., overloading
declarations of non-primitive ops), it does matter.  Consider:

with A;
package C is
    procedure foo(X: A.Class_T);
end C;

with B;
package D is
    procedure foo(X: A.Class_NT);
end D;

use C,D;
Ptr: A.Class_NT;
   foo(Ptr);  -- today legal and clearly D.foo;
              -- tomorrow ambiguous due to implicit conversions ?

For these other cases, the choices are tough:
   - make the ambiguity illegal; the result is upward-incompatible and,
     besides, arguably a nuisance to the user, who actually wrote a
     call with a perfect type match and sees no obvious reason why this
     call should be ambiguous at all.
   - introduce a preference for the perfect match; you get Beaujolais and
     you still need another rule for the nominal ambiguity in the case of
     primitive subprograms, where the perfectly matching subprogram
     might not be among the choices.
   - introduce a preference for the closest match; you get Beaujolais,
     but at least the same solution is applicable to the case of primitive
     subprograms.

I would hate to see Beaujolais reintroduced in the language, and
particularly one that is not just academic as the one in Ada83. That leaves
the first choice as the only viable one and I think the jury is still out on
whether this rule is desirable even in the absence of upward-compatibility
considerations, e.g., in the example given above.

So, if one allowed the implicit conversion ONLY in the case of controlling
arguments of primitive subprograms, where the same implementation of the
subprogram will be selected by dispatching anyway, these other cases do not
arise (explicit conversions are needed there if there is not the perfect
match).

These restricted implicit conversions still solve the OOP problem.
Incidently, as an amendment to my previous msg., controlling results don't
play a role, since access types are never controlling results.

Now, this doesn't fully solve your problem of wanting implicitly convertable
access types needed for a convenient solution to the mutually dependent
types. But, it solves at least part of it.

Further, there doesn't seem to be a technical reason why not to amend
assignment and initialization semantics in a similar fashion, i.e., to allow
these implicit conversions upon assignment and initialization for class-wide
access types in general. That's upward-compatible, too. (And I believe it
solves Franco's problem, which I mistook as a dispatching issue; it's not,
it's an assignment issue.)

But you can't allow the implicit conversion in all contexts, particularly in
the parameter association of non-controlling arguments, without facing the
above ambiguity issue.

-----
On the question of allowing conversions of access types that are not
class-wide, that ambiguity issue turns really nasty. Consider:

with A;
package C is
    Type Tree_Ptr is access A.T;
    procedure foo(X: Tree_Ptr);
end C;

with A;
package D is
    type Graph_Ptr is access A.T;
    procedure foo(X: Graph_Ptr);
end D;

use C,D;
Ptr: Graph_Ptr;
   foo(Ptr);  -- ambiguous ???? you can't be serious !!

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

From: Robert I. Eachus
Sent: Thursday, May 20, 1999 3:54 PM

At 03:01 PM 5/18/1999 EDT, Robert Dewar wrote:

>Well as I said in a previous message, I am probably being too absolute here,
>but it would take a LOT of work to convince me that any language extension
>was worth an incompatibility.

   It depends on your definition of incompatibility.  In particular, I
think that the ARG has to be willing to consider changes which are
compatible with the RM but are incompatible with some implementation
defined extensions.  We should not create such incompatibilities
gratuitously, but we should be willing to choose the best of what is out
there in some areas.

   I think that the with type issue is one where we have to choose the best
long term solution.  I hope we can do it without incompatibilities with the
RM, but I do not consider making additional names visible in some programs
as "right out."
(Of course, I am assuming that any such "additional" names could only
create ambiguities, and never accidently change the meaning of a program.)

   (I hate it when 90% of what I write ends up on the cutting room floor,
but no reason I should bother you people with things that are not
completely thought through.  I keep writing the next section, reviewing it
and rewriting, and I think that the final version does, in fact, fit in the
margin. ;-)

   The notational problem doesn't seem to me, at this point, to be about
conversions, explicit or implicit at all.  That may in fact be what
happens, but the correct notation, IMHO, for the painful cases is:

   Foo := Bar.all'Access;

   You want a an access value of the correct type for Foo, which designates
the same object as Bar does.

   If this does indeed cover all (or even most) of the nuisance
conversions, the simple fix--if one is needed--is to define an attribute
which combines the two notational steps.  (Call it ref for now in honor of
Algol 68.):

   Foo := Bar'Ref;

   I'm open to suggestions for a better name, but it sure makes programming
using access parameters easier.  You don't need to remember the type of Foo
so that you can write the conversion, and as far as I can see there are no
new ambiguities.

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

From: Franco Gasperoni
Sent: Sunday, May 23, 1999 11:54 AM

   From: Tucker Taft

   One open question is whether an "incomplete" package introduced by
   a with-type clause can appear in a "use" clause.  I suppose it
   ought to be allowed.  Of course the only name(s) that would be made
   visible is(are) the one(s) introduced by the with_type clause(s)
   that mention the package, presuming the package is not also
   mentioned in a "normal" with clause.

Yes that makes sense.

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

From: Franco Gasperoni
Sent: Sunday, May 23, 1999 11:55 AM

By the way can with_type appear in package bodies ?

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

From: Pascal Leroy
Sent: Wednesday, April 05, 2000 4:49 AM

> Any compilation unit that has a semantic dependence on another compilation
> unit which has a "with_type_clause," as well as a dependence on the package
> identified by the with_type_clause, must verify that a type declaration (not
> just a subtype declaration) for the named type does in fact appear within the
> visible part of the package.

This rule would be extremely hard to implement for compilers doing incremental
compilation (I know one such compiler).

Consider the case where a package P declares a type T, and T is commented out.
Currently, you only need to check the _direct_ semantic dependents of P for
usages of T (and do selective recompilation, if needed).  But with the above
rule, removing T could cause illegalities to occur in any indirect dependent of
P, no matter how many levels deep.  This means that the impact analysis now has
to look at the transitive closure of P, and is therefore proportional to the
size of the entire program.  This essentially kills incremental compilation.

At some point the above rule was a link time check, which didn't have this
problem.  Is there any deep technical reason for the change, other than someone
(was it Erhard?) finding the link time check aesthetically unpleasant?

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

From: Robert A Duff
Sent: Wednesday, April 05, 2000 11:23 AM

Are there not other cases where an incremental compiler needs to do
things transitively?  Eg:

    package P1 is
        type T is private;
    ...
    end P1;

    with P1;
    package P2 is
        X, Y: P1.T;
    end P2;

    with P2;
    package body P3 is
        ...
        P2.X := P2.Y; -- Legal?
    end P3;

Suppose I change T to be limited.  Then the assignment statement in P3
becomes illegal.

I'm not particularly arguing against a link-time rule; just trying to
understand the issue.  I don't remember why the link-time rule was
rejected.

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

From: Pascal Leroy
Sent: Wednesday, April 05, 2000 11:33 AM

Yes, obviously changes can have a ripple effect.  But the point is, once you
have determined that a unit (say P4) is not impacted by the change, you do not
need to look at the clients of P4.  In your example, if P2 did not declare
objects of type T, then P3 could not be impacted.  (Well, the details are a bit
sticky, but by and large that's how it works.)

With the proposed rule, you need to traverse the entire closure just in case
there is someone down there who sees (indirectly) both the 'with type' and the
actual type.

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

From: Gary Dismukes
Sent: Wednesday, April 05, 2000 12:33 PM

> At some point the above rule was a link time check, which didn't have
> this problem.  Is there any deep technical reason for the change, other
> than someone (was it Erhard?) finding the link time check aesthetically
> unpleasant?

There was some feeling that it was undesirable to complicate
the binder/linker (which I'm inclined to agree with).  Erhard
specifically described his concern in terms of a scenario
of a compiler Front End and Back End with mutually dependent
data structures.  If one wanted to build just a Front End
(or tool dependent on just the Front End), then it would
be necessary to drag in the Back End as well in order to
satisfy the with-type completeness check.  (See the notes
of the Sept. meeting.)

However, there was another alternative discussed at the September
meeting to avoid the link-time check that seems to have fallen
by the wayside.  The suggestion was made that we could required
the body of a unit having a "with type" to have a normal "with"
dependence on the "with-type" package, and Ed Schonberg proposed
that "with type" could act as an implied Elaborate_Body.

A straw vote was taken between the alternatives of

  A) completion optional unless really needed (Erhard's proposal)
  B) body required, and normal "with clause" in body of dependent package

FWIW, the result of the straw vote (in favor of alternative B)
was 6-2-3.  (My notes show that the two opposed votes were
from Erhard and Tuck.)

So it seems we should at least have some further consideration of
Alt. B, since the majority favored it.  Note also that B would avoid
the incremental compilation concern raised by Pascal.  I'd be interested
in seeing some discussion of the pros and cons of the "body dependence"
alternative rather than just dropping it.

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

From: Erhard Ploedereder
Sent: Wednesday, April 05, 2000 4:26 PM

Pascal wrote:

> At some point the above rule was a link time check, which didn't have this
> problem.  Is there any deep technical reason for the change, other than
> someone (was it Erhard?) finding the link time check aesthetically
> unpleasant?

No, this rule was never a link time check. The link time check referred to
the situation where the type was left incomplete and no package in the link
closure ever referenced/depended on the full declaration of the type. (and I
argued that, since no unit depended on completion of the type, there was no
point in rejecting the program and in fact good reasons to allow the link to
succeed).

The rule is there to ensure that, if a package can depend on both the full
and the incomplete type, there is indeed a matchup of the two views.
And this rule cannot possibly be a link-time check, since it has a major
impact on static semantics.

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

From: Pascal Leroy
Sent: Thursday, April 06, 2000 4:07 AM
To: ARG@ACM.ORG
Subject: Re: AI 217 revision

> The rule is there to ensure that, if a package can depend on both the full
> and the incomplete type, there is indeed a matchup of the two views.
> And this rule cannot possibly be a link-time check, since it has a major
> impact on static semantics.

I see your point.  If one side thinks that T is tagged and the other thinks that
it isn't, you have a problem which has an impact on static semantics, and
deferring the check to link-time would seem hard.

It seems that even the rule "the body must have a with" doesn't help, because
there may not be a body (yet) when you try to compile a client that sees both
views of T.

On the other hand, I have been looking at the impact on incremental compilation
for the last two days and my brain aches.  The core problem is that to answer
the question "on which units is this rule checked?" you have to consider the
entire graph of semantic dependencies.  Change a with somewhere in your program,
and the places where the rule has to be checked change.  This means that
incremental compilation has to somehow record the topology of the dependency
graph and detect when it changes.  That's both hard and inefficient.

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

From: Tucker Taft
Sent: Wednesday, April 05, 2000 5:59 PM

I would suggest we make this into a "post compilation" check which allows
it to be checked as soon as it is detected, but also allows it to be postponed
to link-time if that is the more natural place to check (e.g., in the presence
of incremental changes).

> However, there was another alternative discussed at the September
> meeting to avoid the link-time check that seems to have fallen
> by the wayside.  The suggestion was made that we could required
> the body of a unit having a "with type" to have a normal "with"
> dependence on the "with-type" package, and Ed Schonberg proposed
> that "with type" could act as an implied Elaborate_Body.

This clearly defeats Erhard's idea of allowing a program to be built
without pulling in the full type definition, if noone needs it.

I like not having to pull in a full type if it is not needed.  This is a useful
technique in C.

I believe if we make the check proposed in the AI into a post-compilation check,
then Pascal's concern is alleviated, without forcing the linker to get into the
act for a more "conventional" compiler structure.

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

From: Pascal Leroy
Sent: Thursday, April 06, 2000 4:18 AM

When I wrote "link time" I really meant "post compilation".  Making the rule a
post compilation check would be fine with me.  But it seems to me that Erhard
has a valid point: if the partial view and the incomplete view don't match, how
do you check static semantics?

> This clearly defeats Erhard's idea of allowing a program to be built
> without pulling in the full type definition, if noone needs it.
>
> I like not having to pull in a full type if it is not needed.  This is a
> useful technique in C.

I cannot get too excited about this "feature".  If it comes for free, fine.  If
it forces me to write more than 10 lines of code, no.  To use Gary's example, if
you want to build a tool that depends on the front-end _and_ doesn't pull the
back-end, then relying on "with type" is not going to work: there is a 99%
probability that during maintenance someone will add a with clause to some
front-end unit that will reference a back-end unit, and your "separation" will
vanish.  If you want to separate two subsystems like that, you need to use
stronger techniques (generics, access-to-subprograms, OOP, etc.).

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

From: Tucker Taft
Sent: Thursday, April 06, 2000 3:26 PM

Why are these techniques stronger?  They both rely on using discipline
to avoid creating dependencies.   As an example, we have a compiler
and a prelinker that share a lot of declarations, but the prelinker is much
smaller.  This would not be the case if incomplete types were forced
to be completed.  There are a *lot* of incomplete types in this system.
Forcing all of these to be replaced with heavier techniques like
generics, access-to-subprograms, or OOP, would require a major
restructuring.

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

From: Gary Dismukes
Sent: Wednesday, April 05, 2000 2:00 PM
To: ARG@ACM.ORG
Subject: Re: AI 217 revision

In AI-217, the proposal is to allow tagged incomplete types to be
used as parameter and
function result types.  At the Sept. ARG meeting we discussed
the function case and rejected it.  One problem is that you
can't tell whether the function is going to be return-by-reference
or return-by-value, so that would create implementation problems
(and I don't think we want to add the ability to indicate whether
an incomplete type is limited).  So I propose that we do not
allow function result types to be incomplete, and I don't believe
we lose much by such a restriction.

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

From: Tucker Taft
Sent: Wednesday, April 05, 2000 6:16 PM

Thanks for pointing out this problem.  I don't believe the
minutes identified the problem, so I left in the more general
approach lacking a specific reason to restrict it.

Yet I do wonder whether this is a real problem.  Clearly when the compiler gets
around to implementing the body or a call of such a function, it must have
access to the full type. Is there a need to know whether the function is return
by reference when compiling the spec?

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

From: Gary Dismukes
Sent: Wednesday, April 05, 2000 7:09 PM

The spec could contain something like the following:

  function F (X : Incomplete_Tagged_Type) return Integer;

  function G return Incomplete_Tagged_Type;

  type Nasty_Rec is
     record
        I : Integer := F (G);
     end record;

Admittedly perverse, but it creates a problem for generating the default
init routine.

Calls could also occur in other contexts in outside client packages
that don't have knowledge of the full type (though such uses would also
tend to be pathological cases).

> > ... So I propose that we do not
> > allow function result types to be incomplete, and I don't believe
> > we lose much by such a restriction.
>
> I agree, presuming it is a "real" problem.

Seems like a real problem.

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

From: David Emery
Sent: Wednesday, April 05, 2000 2:08 PM

What other cases do we have of a type that is allowed as a parameter
but not as a function return?  I understand the reason for the restriction,
but the apparent asymmetry with the rest of the language makes me
uncomfortable...  And it seems to me that this would be a reasonable
thing to want to do...

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

From: Pascal Leroy
Sent: Thursday, April 06, 2000 3:17 AM

> What other cases do we have of a type that is allowed as a parameter
> but not as a function return?

Anonymous access types.

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

From: Tucker Taft
Sent: Wednesday, April 05, 2000 6:21 PM

> What other cases do we have of a type that is allowed as a parameter
> but not as a function return?

Well, if the type is an abstract type, then we require that all functions
that return it be abstract.

Perhaps that suggests a rule -- functions returning an incomplete tagged type
are permitted so long as the function is abstract.  Gary -- would that reduce
the implementation burden?

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

From: Gary Dismukes
Sent: Wednesday, April 05, 2000 8:06 PM

I'm not sure that helps much.  You could still have cases
where the function could be invoked via a dispatching call:

  type Rec is
     record
        I : Integer := F (G (<Dynamically_Tagged_Operand>));
     end record;

By the way, don't we want operations such as

  X.all := Y.all;  -- X and Y are access to an incomplete tagged type

to be illegal in a context where the designated type is incomplete,
as well as things like X.all'Size?  It's not clear to me that these
are illegal in the current proposal, but it seems that they should
be.  (Although, come to think of it, I guess those aren't necessarily
difficult to support, since they can probably be done by means of
dispatching calls in typical implementations, but maybe it's a bad
idea to assume that.)

I guess what would be needed is a freezing restriction.  We need to
say that you can't do anything that would freeze the type in a context
where you can't see the full type.  I guess that would solve some
of these problems.  Perhaps you had assumed such a restriction
as being implicit in your proposal.  But it still doesn't directly
solve the default initialization case, since that doesn't cause
freezing by the normal rules.

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

From: Robert A Duff
Sent: Thursday, April 06, 2000 9:36 AM

> Perhaps that suggests a rule -- functions returning an incomplete tagged type
> are permitted so long as the function is abstract.  Gary -- would that reduce
> the implementation burden?

I don't see how that could help.  The issue is whether compilers
generate different code *at the call site* depending on whether the
function is return-by-ref.  If so, Gary's concern is "real".

I think compilers *do* generate different code, in the case where the
result type is indefinite -- make a copy at the call site if the result
is indefinite and by-copy.  These things can end up being indefinite,
can't they?

Gary, do I understand the issue correctly?

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

From: Tucker Taft
Sent: Thursday, April 06, 2000 3:31 PM

I don't see why that is the problem, since at the call site, you
know the nature of the full type, or else you couldn't possibly
call the function.  The only issue is whether there are problems with
compiling the function *declaration*.  Calls and the function body
would have the full information.

I don't think the call site is a problem.  The problem must be
at the point of function declaration.

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

From: Robert A Duff
Sent: Thursday, April 06, 2000 3:47 PM

But Gary gave an example where the call was a default value, so the
compiler is seeing it without seeing the full type.  I realize that if
you always inline defaults, you don't have that problem, but Gary
implied that they are generating code for those calls then and there, in
an init procedure for that record type, which sounds like a perfectly
reasonable approach.

I don't see how compiling the function spec itself would cause trouble
-- function specs don't do anything interesting at run time.

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

From: Gary Dismukes
Sent: Thursday, April 06, 2000 3:57 PM

Right, the problem is the need to generate calls to the function
(for default init routines) when you don't know the nature of the
full type.

The possible problem for declarations is that you might want to set some
attributes that might depend on knowing the full type.  In any case, it's
the call site that's my main concern.

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

From: Tucker Taft
Sent: Thursday, April 06, 2000 4:09 PM

Whether it is in a default expression or elsewhere, we wouldn't
allow *calls* that return an incomplete tagged type.  This is
a static semantic check that has nothing to do with how default
expressions are implemented.

> I don't see how compiling the function spec itself would cause trouble
> -- function specs don't do anything interesting at run time.

I agree.

I know we had some difficulty supporting access-to-deferred-incomplete
types in our Ada->Java-byte-code compiler, but if anything, supporting
incomplete tagged types as parameters would be easier than supporting
access-to-deferred-incomplete types, since we at least know that the
designated type is tagged (and we know it's full name).

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

From: Gary Dismukes
Sent: Thursday, April 06, 2000 4:33 PM

Tuck wrote:
> Whether it is in a default expression or elsewhere, we wouldn't
> allow *calls* that return an incomplete tagged type.  This is
> a static semantic check that has nothing to do with how default
> expressions are implemented.

Fine, if there will be a restriction to prevent it then I think that should
address the concern about function results.  But I haven't seen the
list of restrictions on use of these types, as it wasn't in the proposal :-)

The restrictions don't just fall out of existing rules as far as
I can tell.  It would be nice to put together this list, which would
include this one, plus an extension to the freezing rules.  Do we
know what other restrictions we need?

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

From: Tucker Taft
Sent: Thursday, April 06, 2000 9:02 PM

My intent was that incomplete tagged types have the same restrictions
as incomplete types, except for the ability to use them as parameter and result
types in a subprogram declaration.

> The restrictions don't just fall out of existing rules as far as
> I can tell.  It would be nice to put together this list, which would
> include this one, plus an extension to the freezing rules.  Do we
> know what other restrictions we need?

I would phrase it more in terms of what relaxations on the "normal" incomplete
type rules we think are necessary for the feature to be useful.  Thus far, it
is only the less restricted use of 'Class (and 'Class_Access ;-), and the use
as parameter and result types.  Are there any other uses that seem important?

Remind me what we need to do about the freezing rules...

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

From: Randy Brukardt
Sent: Thursday, April 13, 2000 10:37 PM

(This discussion seems to have died without a resolution...)

OK, but it is allowing an incomplete tagged type as a result type that causes
the problem: once that happens, you can make a call on a function for which you
do not know the full type. You said that you wanted to make such calls illegal.
OK, but that requires a new restriction somewhere (because a normal incomplete
type doesn't have this problem: it can't get far enough to make a call).

So I think it is more than just a simple "relaxing" (unless you guys spent a lot
of time discussing nothing last week!).

> Remind me what we need to do about the freezing rules...

Gary was concerned about operations like:
    Size : Integer := F.all'size;
occurring where F's type is an incomplete tagged type whose full type is not
known. He thought that an appropriate extension to the freezing rules would
cover the problem. Gary said: "We need to say that you can't do anything that
would freeze the type in a context where you can't see the full type."

(He used "X" implying an object, but I don't think you can get an object of
these guys before the full type is defined).

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

From: Tucker Taft
Sent: Friday, April 14, 2000 6:58 AM

There seems to be some reasonable agreement on
these 4 AIs, which is good.  However, we definitely
are still debating the issue of checking the incomplete
type against the full type.  Here is one idea:

  *Require* the check only when the incomplete type
  "meets" the full type in a particular usage.

There are few places where the incomplete type can be used:
   1) As the designated type in an access type definition
   2) As a parameter type in an access-to-subp definition
   3) As a parameter type (and result?) in a subprogram declaration
      (if incomplete tagged).

These "meet" the full type when
  1) The access type is dereferenced.
  2) '[Unchecked_]Access or "new" is used to create a value of the access type.
  3) The subprogram is called.
  4) The subprogram (proper) body is provided.

In these four cases, at a minimum, the implementation must verify
that the full type exists in the specified package, is a first
subtype, and is tagged if the incomplete type is tagged.

Implementations are permitted to verify these requirements in any unit
that has a semantic dependence on both the comp unit defining the incomplete
type and the one defining the full type.

The primary wording in the RM could be kept simple, simply requiring
that they be the same.  An implementation permission (or requirement) could
indicate the places where the rule needs to be verified.

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


Questions? Ask the ACAA Technical Agent