Version 1.6 of ais/ai-20217.txt

Unformatted version of ais/ai-20217.txt version 1.6
Other versions for file ais/ai-20217.txt

!standard 03.10.01 (02)          01-09-21 AI95-00217-03/01
!class amendment 01-09-21
!status work item 01-09-21
!status received 01-09-21
!priority Medium
!difficulty Hard
!subject Handling mutually recursive types via separate incomplete types
!summary
A new construct, called a separate incomplete type, is proposed as a potential solution to the "mutually recursive types across packages" problem. This is an alternative to the "with type" proposal of AI-217 and the "package abstract" proposal of AI-271.
A separate incomplete type is an incomplete type which is never completed. Another type can be "bound" to the separate incomplete type, which then allows the types to be used interchangeably.
!problem
Ada only allows mutually recursive types to be declared if they are all declared within the same library unit. This can force all of the major data structures of a program into a single library unit, which is clearly undesirable in some situations.
The goal of the proposed features is to allow mutual recursion among separately compiled types (types containing (pointers to) each other as components, and primitive operations with (pointers to) each other as parameters.) And to do this in a way that doesn't place any other restrictions on the programmer.
!proposal
incomplete_modifier ::= is tagged | is separate | is tagged separate; incomplete_type_declaration ::= type defining_identifier [discriminant_part] [incomplete_modifier];
An incomplete type containing the reserved word "tagged" (an "incomplete tagged type") may be used as the type of a formal parameter prior to its completion, in addition to the normal places where an incomplete type may be used. Also, the Class attribute is defined for an incomplete tagged type. The class-wide type denoted by the Class attribute of an incomplete type may also be used as the type of a formal parameter. The Class attribute of a non-tagged incomplete type is considered obsolescent. An incomplete tagged type must be completed by a tagged type.
Note that we are not proposing that an incomplete tagged type may be used as a return type, because of the many special rules and implementation issues associated with returning tagged types (e.g. functions becoming abstract, accessibility checks on returning limited by-reference types, dispatching on result coupled with tag indeterminacy, finalization associated with returning potentially controlled types, etc.)
An incomplete type containing the reserved word "separate" (a "separate incomplete type") is completed by a type declaration with a completion_clause.
completion_clause ::= for <separate_incomplete_type_name>
full_type_declaration ::= type <Defining_Identifier> [completion_clause] [known_discriminant_part] is type_definition; ...
The name in a completion_clause must name a visible separate incomplete type. There is a post compilation rule that a separate incomplete type may have only a single completion. The type defined by a type declaration with a completion_clause is known as a completing type for the separate incomplete type. The rules in 3.10.1(4) about a completion for an incomplete type apply to a completing type.
Where the completing type (or the full type declaration for the completing type, if the completing type is a private type) is visible, the separate incomplete type name (and the names of any subtypes of it) is treated as a subtype naming the completing type. (That is, it is a alternative name for the completing type.) In other places, the standard rules for the use of the name of an incomplete type apply. A separate incomplete type is exempted from the rule that completed declarations are hidden from all visibility (8.3(19)). (Since the name is declared in an different scope, there is no need to hide it; doing so probably would be additional, unnecessary work.)
A separate incomplete type can be frozen; it does not need a completion when it is frozen. Only non-separate incomplete types are excluded from freezing at the end of a scope (13.14(3)).
!discussion
The concept of a forward or separate incomplete type has been proposed in the past. At the time, other proposals looked more attraction. However, the complexity of those proposals (particularly the far reaching effects of adding a new kind of compilation unit) makes them less attractive to solve these problems.
There seem to be two general ways this kind of problem is solved in other languages. One approach is to permit unrestricted forward references. This is the approach adopted by Eiffel, Java, and Smalltalk. The other approach is to require "forward" or "incomplete" declarations, sometimes called "signatures" or "partial revelations." This is generally the approach adopted by Ada, as well as Pascal, Modula, ML, C/C++, etc.
We chose the simple approach of extending a single existing Ada feature, rather than adding a basket of heavy new features to provide the needed capability. This simplifies both the conceptual burden and the implementation cost.
The model of a separate incomplete type does not add any new implementation burden. That's because it is very similar to the incomplete-deferred-to-body which Ada 95 already has. The compiler does not need to know the real type involved as long as the incomplete usage rules are enforced.
Determining the representation of an access type that designates a separate incomplete type could be a problem for some implementations that select the representation of an access type based on the designated type. However, this would be no different than doing that for a type whose completion is deferred to the body. So such implementations must already be able to handle this somehow.
Since an access type that designates a separate incomplete type is a normal declaration, representation clauses (for storage pools, for instance) can be used as needed. This eliminates many of the problems found in the "with type" proposal. Note that such an access type can be used normally (including allocators, deallocators, and the like) when the completing type is visible.
This proposal allows the separate incomplete type to not be completed at all in the program. There is no problem with not having a completion (no object of the type could be created), and there is no implementation problem. Not having a completion can be useful in the prototype stages of a program, and also can be useful for a program with multiple implementations. (For instance, a compiler front-end could have an incomplete type for code generation information. A checkout version of the compiler, with no need for code generation, would not need to complete the incomplete type.)
An alternative model where the place and name of the completion is specified in the separate incomplete type's declaration was considered. This model was rejected for two reasons:
-- The name of the completion does not exist at the point of the separate
incomplete declaration. Indeed, we must not have a semantic dependence on the unit of the completion, or we will not be able to solve the mutual dependence problem. Thus, describing the semantics of such a requires a new language mechanism.
-- We want to insure that the separate incomplete type is visible to the
completion. This would require additional rules.
The model chosen has neither of these problems. Since we don't try to reference the completion in the separate declaration, we don't have to describe what it means. A comment may be appropriate, but that is out of control of the standard. Secondly, by requiring the name of the separate incomplete type in the completing declaration, we automatically require that the incomplete type is visible.
The legality checks for the use of the name of the separate incomplete type depend on the visibility of the completing type. This works because types can only be hidden from all visibility by a completion. We don't want to use "scope" here, as the scope of a library-level type includes the entire program. We only want to allow full use of the type where the package containing the completion is withed. (Keep in mind we are talking about "visibility", not "direct visibility", here.)
An alternative model where the separate incomplete type is never completed (a "forever incomplete type") was considered. In this model, a type is "bound" to a separate incomplete type. Then, anywhere in the scope of the binding, the two types are considered equivalent. In this model, the separate incomplete type can never be used anywhere declare an object, write an allocator, or instantiate a generic. This seems unnecessary, and the difficulty of writing the needed type matching rules caused it to run aground.
The model chosen allows separate incomplete types to be frozen, eliminating the anomaly of having unfrozen types imported from other units. It also eliminates having types that are never frozen anywhere in the program.
The author would prefer to change the freezing model for incomplete types to the following:
- Incomplete types freeze like other types. (The "except incomplete types"
in 13.14(3) is deleted.)
- When an incomplete type is frozen, if the incomplete type is not separate,
and not declared in the private part of a package specification, then it must have already been completed.
The author believes this is equivalent (with one minor exception) to the existing model, as the name usage rules 3.10.1(5-9) prevent any freezing before the completion anyway. (The exception is that the completion of an incomplete-deferred-to-body could appear after a body in the package body.) This change would eliminate the anomaly of unfrozen types ever existing outside of their declarative_part. This was not done mainly to avoid cluttering this proposal with a non-required change (thus making it seem bigger than it is).
The syntax for completion_clause does not apply to task or protected types in this proposal. These were omitted solely to keep the proposal simple; there is no known need to omit them. Adding them would require four additional syntax changes.
!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. (We assume the use of tagged types here to illustrate the use of tagged incomplete types.)
package Employees_Interface is type Employee is tagged separate; type Emp_Ptr is access all Employee'Class; end Employees_Interface;
package Departments_Interface is type Department is tagged separate; type Dept_Ptr is access all Department'Class; end Departments_Interface;
with Departments_Interface, Employees_Interface; package Employees is type Employee for Employees_Interface.Employee is tagged private; procedure Assign_Employee(E : in out Employee; D : in out Departments_Interface.Department); ... function Current_Department(D : in Employee) return Departments_Interface.Dept_Ptr; end Employees;
with Departments_Interface, Employees_Interface; package Departments is type Department for Departments_Interface.Departments is tagged private; procedure Choose_Manager(D : in out Department; Manager : in out Employees_Interface.Employee); ... end Departments;
!ACATS test
!appendix

From: Dan Eilers
Sent: Wednesday, June 20, 2001, 5:04 PM

I agree with Tucker et al that the "with type" proposal (AI 217) is
misguided, because it involves implicit type declarations and their
enclosing "ghost" library units.  These implicit declarations
provide no place to attach pragmas, representation clauses or
access type declarations.  They also don't work well in library-based
implementations, because such implementations can't easily go looking
for the source to an uncompiled unit to see what types are declared
inside it.  Instead, library-based implementations would probably need
some new compilation mode that enters the ghost library packages into
the library.

While the "package abstracts" proposal (AI 271) solves these problems,
I agree with Ed Schonberg et al that this solution is too heavy.  It
increases the number of package parts to 4 (abstract, visible spec,
private, body); it adds a new kind of library unit, causing impact to
tools that manipulate libraries, e.g. compilation-order tools; and
it adds a new kind of "with" clause, e.g. abstract with p, or
with p abstract, or with p'abstract.

I agree with Bob Duff et al that the alternative of bolting the package
abstract to the front of the package spec isn't attractive.  It doesn't
reduce the 4 kinds of package parts; it doesn't eliminate the need
for some new compilation mode that enters just the package abstract into
the library (although such a new compilation mode might be easier to
define and implement); and it doesn't eliminate the need for the
new kind of "with" clause.

The essence of the "package abstracts" proposal is that it provides
a place where an incomplete type can be declared whose full declaration
is expected to be found elsewhere.  (The name of the package abstract
provides the indication as to where the full type declaration is expected
to be found.)  But there seems to be no need for a new type of library
unit to do this job.  Instead, a pragma (or spiffy new syntax for
incomplete type declarations if you prefer) would suffice.

Instead of:

   package abstract Employees is
        type Employee;
        type Emp_Ptr is access all Employee;
    end Employees;

You would have:

   package unrelated is
       type Employee;
       pragma completed_elsewhere(Employee, where => Employees);
       type Emp_Ptr is access all Employee;
   end unrelated;

This involves no new package parts, no new library units, and no
new kinds of "with" clauses.  If packages Employees and Departments
were both children of the same parent, then the incomplete
declarations could simply go in the parent.

        -       -       -       -       -       -       -

As a further refinement, I would note that the "where" parameter
to pragma completed_elsewhere is anomalous, since it names a
package that isn't visible, and normally isn't even available in
the library.  I don't think the "where" parameter is actually
necessary.  Instead of the incomplete type declaration indicating
where the full type will be (so that the compiler can ultimately
connect the two), the full declaration could indicate what
incomplete type it is completing.  This could be done with a
pragma (or spiffy new syntax if you prefer).

So instead of:

    package abstract Employees is
        type Employee;
        type Emp_Ptr is access all Employee;
    end Employees;

    package abstract Departments is
        type Department;
        type Dept_Ptr is access all Department;
    end Departments;

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

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

we would have:

   package unrelated is
       type Employee;  pragma completed_elsewhere(Employee);
       type Emp_Ptr is access all Employee;

       type Department;  pragma completed_elsewhere(Employee);
       type Dept_Ptr is access all Department;
   end unrelated;


    with Unrelated;
    package Employees is
        type Employee is private;
           pragma completion(Employee, from => Unrelated);
        type Emp_Ptr is access all Employee;
        procedure Assign_Employee(E : access Employee;
          D : access Unrelated.Department);
        ...
        function Current_Department(D : access constant Employee) return
          Unrelated.Dept_Ptr;
    end Employees;

    with Unrelated;
    package Departments is
        type Department is private;
           pragma completion(Department, from => Unrelated);
        type Dept_Ptr is access all Department;
        procedure Choose_Manager(D : access Department;
          Manager : access Unrelated.Employee);
        ...
    end Departments;


This opens up the possibility of more than one completion of the same
incomplete type, which may be unnecessary, but doesn't seem harmful.

To summarize:
   no new kinds of library units
   no new kinds of package parts
   no new kinds of with clauses
   no impact to compilation-order tools
   no new compilation modes
   no forward references to uncompiled library units
   no problems with library-based implementations
   no new syntax unless we want it (we probably do).

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

From: Randy Brukardt
Sent: Tuesday, July 3, 2001, 6:14 PM

Re: Dan Eiler's proposal.

I'm quite surprised that no one seriously has looked at this proposal. It seems
to me that it eliminates some of the problems of the abstract package and
simplifies it a lot without necessarily losing the important features.

The basic proposal is to introduce a new kind of incomplete type that NEVER
needs to be completed. It would have syntax like (Dan used pragmas, but they're
really ugly for something that will be commonly used):

    type <Defining_Identifier> is separate;
    type <Defining_Identifier> is tagged separate;

The rules for packages would be unchanged. The tagged kind of separate
incomplete type would allow 'Class and use as the type name in parameter
declarations (but not function returns).

My immediate reaction was that this would be hard to implement. But, on further
reflection, I realized that as long as the incomplete rules were enforced
everywhere that the incomplete type is visible, the compiler NEVER needs to
know the real type involved. Moreover, this type could not be used in an
allocator or a generic, so we would get the restrictions we need for free.
(And it is rather like an incomplete type deferred to a body, for which the
compiler can't assume anything anyway.)

The completion would include a new clause in the type declaration:

    type <Defining_Identifier> [completion_clause] ...

    completion_clause ::= for <type_name>

[I'd rather use "completes" here, but that would introduce a new reserved word.
I also looked at "declare" for this use, but it reads funny. I'm sure someone
will have nifty syntax for this.]

There would be a post-compilation check that all "is separate" types are
completed exactly once. (I don't think that there is any benefit to allowing
multiple completions, because the types have to be considered the same in some
scopes, and that could lead to a situation where A = P and B = P, but A /= B.)

To recast Dan's example (fixing a couple of errors):

    package Partial_Definitions is
       type Employee is separate;
       type Emp_Ptr is access all Employee;

       type Department is separate;
       type Dept_Ptr is access all Department;
   end Partial_Definitions;

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

   with Partial_Definitions;
   package Departments is
       type Department for Partial_Definitions.Department is private;
       type Dept_Ptr is access all Department;
       procedure Choose_Manager(D : access Department;
          Manager : access Partial_Definitions.Employee);
        ...
   end Departments;


Now to look at the possible problems:

Determining the representation of Partial_Definitions.Emp_Ptr would be a
problem for some implementations that select the representation of an access
type based on the designated type. However, this would be no different than
doing that for a type whose completion is deferred to the body. So such
implementations must already be able to handle this.

Similarly, Partial_Definitions.Emp_Ptr would never be frozen, but again we
already have to handle that.

Partial_Definitions.Emp_Ptr would never have allocators defined for it.
Probably it would be best that it has no pool at all. That means that any
allocations would have to be converted to the type. Similarly,
Unchecked_Deallocation couldn't be instantiated for it. OTOH, other uses would
not need conversions. So that doesn't seem too bad. If it is a real problem,
AI-230 may help solve that. Or we could simply define such types to be
equivalent (that couldn't be a compatibility problem, as Ada 95 has no such
types!). But then we'd have to deal with defining the pool for the incomplete
access type.

The interesting question is exactly what does the "completion_clause" mean.
The idea appears to be that inside of package Employees,
Partial_Definitions.Employee and Employees.Employee are considered to be the
same type for all purposes including resolution. (That's how we get conversions
between Partial_Definitions.Emp_Ptr and Employees.Emp_Ptr.) Whether that should
hold outside of Employees at places that have visibility on both Employees and
on Partial_Definitions is an open question. Off-hand, I don't see how this
could cause a problem, but perhaps someone else can.


So, at first look, this seems like a useful idea to pursue. It doesn't have any
elaboration or "new unit kind" problems as package abstract does, and it
probably doesn't have the visibility issues either (depending on the exact
definition of when the types are considered the same). It's similar to the
current "with type" in that the access type equivalence isn't perfect, but it
looks much easier to fix than rather than with "with type".

So, what is wrong with this idea? Should it be written up completely?

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

From: Robert Dewar
Sent: Tuesday, July 3, 2001, 7:55 PM

I certainly prefer the separate idea to the package abstract proposal from
a language design point of view, but it is not clear to me that it satisfies
all the Java interface requirements. I definitely think it is worth writing
up the idea completely.

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

From: Randy Brukardt
Sent: Tuesday, July 3, 2001, 8:19 PM

Humm. The only Java interface requirement that I know of that we're trying to
address with this proposal is the mutually dependent types problem. And Java
types are essentially tagged types in Ada, so with the tagged incomplete type,
we get the needed functionality. I think. Is there something else that I've
missed?

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

From: Ted Baker
Sent: Thursday, July 5, 2001, 7:05 AM

I've been lurking out here watching the discussion on the package abstract
idea, dismayed by the conceptual complexity and ugliness.  The Dan/Randy
separate proposal seems much cleaner.  I hope you can work it out to everyone's
satisfaction.

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

From: Tucker Taft
Sent: Monday, July 9, 2001, 3:51 PM

Ted Baker wrote:
>
> I've been lurking out here watching the discussion on the package abstract
> idea, dismayed by the conceptual complexity and ugliness.  The Dan/Randy
> separate proposal seems much cleaner.  I hope you can work it out to everyone's
> satisfaction.  --Ted

I agree that Dan's proposal, with some of Randy's suggestions,
is a direction worth pursuing.  This is not too far from
a direction suggested in the past using pragma Import
and pragma Export applied to types, analogous to the
trick that can be used to implement a subprogram declared
in one package with a subprogram declared in another package.

I would like to know at least the name of the
package where the full type definition is provided,
and I wouldn't mind requiring it to be a child
of the package where the incomplete type is declared,
ensuring that the incomplete/separate type's declaration
is directly visible at the point of the full type definition.
I would require that the name of the full type in the child
match the name of the incomplete type in its parent.

Hence, something like:

   package P is
       type T is separate(C);  -- is completed
                               -- in child "C" of P.
       ...
   end P;

   package P.C is
       type T is ...
   end P.C;

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

From: Randy Brukardt
Sent: Friday, July 13, 2001, 6:15 PM

> I agree that Dan's proposal, with some of Randy's suggestions,
> is a direction worth pursuing.  This is not too far from
> a direction suggested in the past using pragma Import
> and pragma Export applied to types, analogous to the
> trick that can be used to implement a subprogram declared
> in one package with a subprogram declared in another package.

Except this is a model that makes sense, while using pragma Import and Export
says KLUDGE in large, flashing letters.

> I would like to know at least the name of the
> package where the full type definition is provided,
> and I wouldn't mind requiring it to be a child
> of the package where the incomplete type is declared,
> ensuring that the incomplete/separate type's declaration
> is directly visible at the point of the full type definition.
> I would require that the name of the full type in the child
> match the name of the incomplete type in its parent.
>
> Hence, something like:
>
>    package P is
>        type T is separate(C);  -- is completed
>                                -- in child "C" of P.
>        ...
>    end P;
>
>    package P.C is
>        type T is ...
>    end P.C;

Well, this is almost OK, but I think it has some problems.

First, describing the semantics of the name in the separate is going to be
interesting. It has to reference something not declared yet, and about all we
can say about it is that it is a library unit -- which doesn't even need to
exist yet. Indeed, there must be no requirement that it exist.

Second, restricting the completion to a child is a non-starter. I certainly
don't think people would be happy to have to rename all of their packages in
order to use this feature. It is particularly bad, because the parent
(presumably with the original name) would contain just the partial definitions,
while the child would contain the "meat" -- the stuff that exists now. So every
reference the package would have to be changed.

Moreover, I don't think such a restriction would work well with Robert Dewar's
"only reason for this feature" -- interfacing to Java. If you have two Java
classes Class_A and Class_B that are mutually recursive, this idea would force
you to introduce not only another package, but one whose name will show up
everywhere:

    package Java_Stuff is
       type Class_A is tagged separate (A);
       type Class_A_Access is access all Class_A;
       type Class_B is tagged separate (B);
       type Class_B_Access is access all Class_B;
    end Java_Stuff;

    package Java_Stuff.A is
       type Class_A is tagged ...
       ...
    end Java_Stuff.A;

    package Java_Stuff.B is
       type Class_B is tagged ...
       ...
    end Java_Stuff.B;

I think it would be much cleaner to say:

    package Abstract_A_and_B is
       type Class_A is tagged separate (A);
       type Class_A_Access is access all Class_A;
       type Class_B is tagged separate (B);
       type Class_B_Access is access all Class_B;
    end Abstract_A_and_B;

    with Abstract_A_and_B;
    package A is
       type Class_A is tagged ...
       ...
    end A;

    with Abstract_A_and_B;
    package B is
       type Class_B is tagged ...
       ...
    end B;

...because then the "abstract" package never appears in uses of Class_A or
Class_B. Only the declarations of Class_A and Class_B need worry about it.

It is especially annoying that there is no technical reason (at least that I
know) for this restriction: it would just be a language designer's feel for
"the best way to do it". There doesn't seem to be any technical need for direct
(or any!) visibility on the incomplete type in order to complete it - the type
has no operations (primitive or otherwise) anyway. And the visibility of uses
doesn't seem to matter either: you just get whatever view you have.

So I would make the name a library unit name, and drop any child requirement.

A third point (just a quibble) is that it really is necessary for readability
to mention that this type is a completion (even if the name of the type is
required to be the same). So, I'd expect to always put in a comment:
    -- Completes Abstract_A_and_B.Class_B.
It seems like it would be helpful to include this in the syntax somehow, just
as is done with subprogram names on "end". (Aside: I sure wish "end record;"
was "end <type_name>;", 'cause that's what I write every time anyway. :-)

Anyway, it seems that I've volunteered again :-) to write this up. I'm not sure
whether to replace AI-271 with this proposal, or to open a third AI for this
third approach. Suggestions?

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

From: Tucker Taft
Sent: Monday, July 16, 2001, 5:31 PM

Randy Brukardt wrote:
> >
> >    package P is
> >        type T is separate(C);  -- is completed
> >                                -- in child "C" of P.
> >        ...
> >    end P;
> >
> >    package P.C is
> >        type T is ...
> >    end P.C;
>
> Well, this is almost OK, but I think it has some problems.
>
> First, describing the semantics of the name in the separate is going to be
> interesting. It has to reference something not declared yet, and about all
> we can say about it is that it is a library unit -- which doesn't even need
> to exist yet. Indeed, there must be no requirement that it exist.

This is analagous to a subunit.  The subunit need not
exist at the point when the stub appears, but it logically
appears there.  So this seems pretty similar.

>
> Second, restricting the completion to a child is a non-starter. I certainly
> don't think people would be happy to have to rename all of their packages in
> order to use this feature. It is particularly bad, because the parent
> (presumably with the original name) would contain just the partial
> definitions, while the child would contain the "meat" -- the stuff that
> exists now. So every reference the package would have to be changed.

Hmmm... I suppose, though this seems like something
that would have to be mostly for new code.  Also,
library-unit renaming would allow the child to be
renamed as a top-level unit.

>
> Moreover, I don't think such a restriction would work well with Robert
> Dewar's "only reason for this feature" -- interfacing to Java. If you have
> two Java classes Class_A and Class_B that are mutually recursive, this idea
> would force you to introduce not only another package, but one whose name
> will show up everywhere:
>
>     package Java_Stuff is
>        type Class_A is tagged separate (A);
>        type Class_A_Access is access all Class_A;
>        type Class_B is tagged separate (B);
>        type Class_B_Access is access all Class_B;
>     end Java_Stuff;
>
>     package Java_Stuff.A is
>        type Class_A is tagged ...
>        ...
>     end Java_Stuff.A;
>
>     package Java_Stuff.B is
>        type Class_B is tagged ...
>        ...
>     end Java_Stuff.B;

Actually, Java classes are essentially always declared
in (Java) packages, so the Ada package corresponding
to the Java package is a natural place for these.
Also, the pointer type is the one that Java users will
use.  The package where the operations are declared
will not be as important if the obj.operation notation
is used.

>
> I think it would be much cleaner to say:
>
>     package Abstract_A_and_B is
>        type Class_A is tagged separate (A);
>        type Class_A_Access is access all Class_A;
>        type Class_B is tagged separate (B);
>        type Class_B_Access is access all Class_B;
>     end Abstract_A_and_B;
>
>     with Abstract_A_and_B;
>     package A is
>        type Class_A is tagged ...
>        ...
>     end A;
>
>     with Abstract_A_and_B;
>     package B is
>        type Class_B is tagged ...
>        ...
>     end B;
>
> ...because then the "abstract" package never appears in uses of Class_A or
> Class_B. Only the declarations of Class_A and Class_B need worry about it.

I guess I am still not convinced.  I think we need some
real examples, not just package A and B, to see which
works better.

> It is especially annoying that there is no technical reason (at least that I
> know) for this restriction: it would just be a language designer's feel for
> "the best way to do it". There doesn't seem to be any technical need for
> direct (or any!) visibility on the incomplete type in order to complete it -
> the type has no operations (primitive or otherwise) anyway. And the
> visibility of uses doesn't seem to matter either: you just get whatever view
> you have.
>
> So I would make the name a library unit name, and drop any child
> requirement.

But it seems important to know that a given type is
completing an incomplete type, because representation
may depend on this.  I would think we would certainly
require that that package with the full type "with" the
package with the incomplete type, so given that, it seemed
better to just make it into a child, which always implicitly
"with"s its parent.

>
> A third point (just a quibble) is that it really is necessary for
> readability to mention that this type is a completion (even if the name of
> the type is required to be the same). So, I'd expect to always put in a
> comment:
>     -- Completes Abstract_A_and_B.Class_B.
> It seems like it would be helpful to include this in the syntax somehow,
> just as is done with subprogram names on "end". (Aside: I sure wish "end
> record;" was "end <type_name>;", 'cause that's what I write every time
> anyway. :-)
>
> Anyway, it seems that I've volunteered again :-) to write this up. I'm not
> sure whether to replace AI-271 with this proposal, or to open a third AI for
> this third approach. Suggestions?

I hate to see a proliferation of AIs which are alternatives
of the same thing.  I guess I would rather see these all
part of the same AI, put forth as alternatives.  Once
we ultimately pick one for further refinement, we will want
the others in the appendix or rationale as rejected alternatives.
Hence, it seems to me they all belong in the same AI.
Perhaps we need to recognize the importance of "alternatives"
in amendment AIs.

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

From: Randy Brukardt
Sent: Tuesday, July 17, 2001, 4:41 PM

Tucker, responding to me responding to him, wrote:
> > >
> > >    package P is
> > >        type T is separate(C);  -- is completed
> > >                                -- in child "C" of P.
> > >        ...
> > >    end P;
> > >
> > >    package P.C is
> > >        type T is ...
> > >    end P.C;
> >
> > Well, this is almost OK, but I think it has some problems.
> >
> > First, describing the semantics of the name in the separate is going to be
> > interesting. It has to reference something not declared yet, and about all
> > we can say about it is that it is a library unit -- which doesn't even need
> > to exist yet. Indeed, there must be no requirement that it exist.
>
> This is analagous to a subunit.  The subunit need not
> exist at the point when the stub appears, but it logically
> appears there.  So this seems pretty similar.

I think you missed the point. In a subunit, there is no attempt to name some
unit that will exist sometime in the future. The stub is a declaration of a
some entity, and that entity actually exists there.

OTOH, the unit named in your proposed syntax is not declared anywhere. And the
appearance in the separate type declaration certainly is not a declaration, nor
does some random package spec logically exist at this point.

Traditionally, Ada 95 requires the completor to name the completee (think
"separate" clause in subunits). The partial declaration does not indicate much
(if anything) about the completor.

Thus, I think it makes the most sense for the completion to indicate that it is
completing a separate, rather than the separate trying to name the location of
the completion. (But I don't feel that strongly about this.)

> > Second, restricting the completion to a child is a non-starter. I certainly
> > don't think people would be happy to have to rename all of their packages in
> > order to use this feature. It is particularly bad, because the parent
> > (presumably with the original name) would contain just the partial
> > definitions, while the child would contain the "meat" -- the stuff that
> > exists now. So every reference the package would have to be changed.
>
> Hmmm... I suppose, though this seems like something
> that would have to be mostly for new code.  Also,
> library-unit renaming would allow the child to be
> renamed as a top-level unit.

Well, I suppose. *I* have existing code that desperately needs this feature,
and I would certainly want to use it when it is available.

But this means that virtually my entire program would have to be children of
some parent. The effect would be that every object would have a parent package
containing the abstract interface; and a child containing the real definition,
and a library unit renames to a reasonable name.

>...
>
> Actually, Java classes are essentially always declared
> in (Java) packages, so the Ada package corresponding
> to the Java package is a natural place for these.
> Also, the pointer type is the one that Java users will
> use.  The package where the operations are declared
> will not be as important if the obj.operation notation
> is used.

I know all of this (with the possible exception of the last), and I don't see
what it has to do with my point. Every Java object is converted into an Ada
package. But if incomplete types are restricted to children, each such package
will necessarily be two such packages. And the real implementation will be in
a child. So, the package names will not be a direct mapping from Java, and
you'll need a library renames to get close.

> > It is especially annoying that there is no technical reason (at least that
> > I know) for this restriction: it would just be a language designer's feel
> > for "the best way to do it". There doesn't seem to be any technical need
> > for direct (or any!) visibility on the incomplete type in order to
> > complete it - the type has no operations (primitive or otherwise) anyway.
> > And the visibility of uses doesn't seem to matter either: you just get
> > whatever view you have.
> >
> > So I would make the name a library unit name, and drop any child
> > requirement.
>
> But it seems important to know that a given type is
> completing an incomplete type, because representation
> may depend on this.  I would think we would certainly
> require that that package with the full type "with" the
> package with the incomplete type, so given that, it seemed
> better to just make it into a child, which always implicitly
> "with"s its parent.

If your initial statement is true (and it seems to be), then it is imperative
that the completing declaration include syntax to specify that it is a
completion. (So all of the reader, the writer, and the compiler knows certainly
that it is a completion, and of what.) That syntax would necessarily include
the name of the type being completed, so it follows that the package would
have to be withed (from the normal visibility rules). Once we have this, there
doesn't seem to be any need to restrict where this is used.

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

From: Pascal Leroy
Sent: Tuesday, July 17, 2001, 7:11 AM

Robert says:

> The main argument in favor of this feature at all (at least the market
> argument as opposed to "this would be nice to have") is to mimic C++ and
> Java existing code. So to me the only strong argument for nested package
> abstracts would be an argument that they are useful from this point of view.
> I do not see that this is the case, but am not really familiar enough to say.

I have an entirely different perspective here.  I don't think Rational will
implement interfacing with C++ or Java in the foreseeable future (I don't think
we have seen much demand).  On the other hand, mutually dependent types show up
all the time in OO designs (in particular when mapping a UML model to Ada).  So
I believe we need a general solution to this issue, regardless of interfacing
consideration (general doesn't mean overengineered, of course).

I guess different vendors see different user communities...

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

From: Erhard Ploederedere
Sent: Wednesday, July 18, 2001, 6:56 AM

What I would like to see at some point is one write-up that summarizes the
pros and cons of the three or more approaches in a single document. With
three or more separate write-ups, these comparisons are harder to make.

I guess I am asking for a summarizing Study Note rather than multiple AIs,
which describe a single approach each and collect random comments in the
appendix. This study note may well be the eventual "master AI-171".

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

From: Randy Brukardt
Sent: Wednesday, July 18, 2001, 2:26 PM

Sounds wonderful. Are you volunteering to write it? (So far, with this
proposal, the person who has floated an idea has been tagged to write it up.)

I know that the limited time I have available will prevent me from doing
anything like that until at least November. In the mean time, we're going to
have a meeting, and I need to rewrite several AIs (including the "type
separate" proposal). We're going to need something to discuss in the mean time.

If you want the simple version, here it is:

    "with type"
        Con: Can't handle access types (due to representation issues).
             Implementation needs "pseudo packages" internally.
             What happens when multiple paths to the same item "meet"?
        Pro: Easy to describe to users.
    "package abstract"
        Con: Heavy, complex mechanism. Complex visibility issues to work out.
             New kind of compilation unit affects all tools (alternatives
             avoiding compilation unit has similar effect on tools because the
             source must be compiled in two different ways).
        Pro: Handles access types cleanly.
    "type separate"
        Con: Must introduce extra packages (either "unnecessary" parents with
             Tucker's rules or "unrelated" ones with Dan's/Randy's rules).
             No allocators/deallocators on access types with designated types
             of this kind.
             (Probably others, once a proposal is made and picked apart.)
        Pro: No new kind of units. No new visibility issues. Access types can
             be handled (representation clauses can be given if needed). Model
             is easy to understand (it is an extension of existing rules). No
             run-time implementation problems; the implementation is identical
             to incomplete-completed-in-body.

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

From: Dan Eilers
Sent: September 6, 2001  6:17 PM

The writeup for this AI should probably reference:

http://home.bluemarble.net/~jvolan/WithingProblem/FAQ.html#forward_incompletes

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

From: Tucker Taft
Sent: Saturday, September 22, 2001, 7:56 AM

I like almost everything about this proposal except the syntax
(and perhaps the use of the phrase "separate incomplete type"
rather than something like "type stub" for the incomplete type).

How about defining a new kind of "representation" clause:

    for <full_type> use type <separate_incomplete_type>;

This reads better to me.  It also allows us to associate
task types and protected types with the separate incomplete type.
And it doesn't further muck up the syntax for declaring a type,
which is already one of the most nightmarish parts of the
Ada grammar.  Clearly this sort of "representation" clause
should have to be in the visible part of the package if the
connection between the two types is to be visible outside the package.

Another alternative, which would eliminate the need completely
for new syntax, would be something like:

     for <full_type>'Base use <separate_incomplete_type>;

Attribute 'Base is not quite right, of course, but something
else might be (e.g. for <full_type>'Interface use ... or
for <full_type>'Stub use ...).

One particularly nice feature of this proposal which I didn't
realize before is the multiple-implementation concept.  You
could have multiple completions of the same type stub, and use
a "with" clause on the main subprogram to determine which one
gets included in a particular program.

[While we are at it...  Many people wish we could have "object stubs"
as well, so that a table could be defined and redefined without having
to recompile the spec where the table is declared.]

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

From: Randy Brukardt
Sent: Monday, September 24, 2001, 7:56 PM

> I like almost everything about this proposal except the syntax
> (and perhaps the use of the phrase "separate incomplete type"
> rather than something like "type stub" for the incomplete type).

The reason for calling it an "incomplete type" is so that most of the rules
that apply to an incomplete type also apply to this type. There are enough
differences so that it doesn't matter a lot, but the incomplete syntax is
attractive.

> How about defining a new kind of "representation" clause:
>
>     for <full_type> use type <separate_incomplete_type>;
>
> This reads better to me.  It also allows us to associate
> task types and protected types with the separate incomplete type.
> And it doesn't further muck up the syntax for declaring a type,
> which is already one of the most nightmarish parts of the
> Ada grammar.  Clearly this sort of "representation" clause
> should have to be in the visible part of the package if the
> connection between the two types is to be visible outside the package.

That would be fine by me, although I'm not certain whether any anomalies are
introduced by allowing the type declaration and "completion" to be separated.
(I didn't think about that, 'cause it couldn't happen.)

> One particularly nice feature of this proposal which I didn't
> realize before is the multiple-implementation concept.  You
> could have multiple completions of the same type stub, and use
> a "with" clause on the main subprogram to determine which one
> gets included in a particular program.

Yes, that seems like another reason to prefer this syntax to the alternative of
specifying the completor. It probably ought to be mentioned in the AI.

> [While we are at it...  Many people wish we could have "object stubs"
> as well, so that a table could be defined and redefined without having
> to recompile the spec where the table is declared.]

Seems like a different kettle of fish; it doesn't have an obvious
implementation counterpart as incomplete types do. (Restricting such an item
so that you couldn't use it would work, of course, but seems pointless.)

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

From: Tucker Taft
Sent: Monday, September 24, 2001, 8:52 PM

> The reason for calling it an "incomplete type" is so that most of the rules
> that apply to an incomplete type also apply to this type. There are enough
> differences so that it doesn't matter a lot, but the incomplete syntax is
> attractive.

I wasn't objecting to the word "incomplete" but rather to the
word "separate".  The other places we use "separate" we
either use the word "stub" or "subunit," and this use
clearly corresponds more to the "stub" case.

> ...
>
> > [While we are at it...  Many people wish we could have "object stubs"
> > as well, so that a table could be defined and redefined without having
> > to recompile the spec where the table is declared.]
>
> Seems like a different kettle of fish; it doesn't have an obvious
> implementation counterpart as incomplete types do. (Restricting such an item
> so that you couldn't use it would work, of course, but seems pointless.)

I was more reacting to the fact that extending the notion of "stub"
from subprograms, packages, tasks, and protected bodies to
incomplete types made me think about how often I wish I could
declare an object in a package spec, but define its (static)
initializers in a separate compilation unit.  This is easy to
do in C, but a pain in Ada.

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

From: Christoph Grein
Sent: Tuesday, September 25, 2001, 1:10 AM

I'm not sure that I haven't missed something in the proposal.
What about multiple completions in one program?

   with XXX_Interface;
   package XXX_Completion is
     type T for XXX_Interface.T is tagged private;
     ...
   end XXX_Completion;

   with XXX_Interface;
   package Another_XXX_Completion is
     type T for XXX_Interface.T is tagged private;
     ...
   end Another_XXX_Completion;

Should there be a rule making a program illegal if there are two completions
at  the same time?

   with XXX_Completion, Another_XXX_Completion;  -- Compile time error here?
   package ZZZ is ...

This need not even be within the same unit, but could be in separated parts
of a program:

   with XXX_Completion;
   package P1 is ...       -- OK

   with Another_XXX_Completion;
   package P2 is ...       -- OK

   with P1, P2;            -- Compile time error here?
   procedure Proc is ...

   with Q1, Q2;            -- Compile time error here? Somewhere in the Q1
   procedure Qroc is ...   -- tree, P1 is "withed", the same for Q2 and P2.

Or would these be link-time errors?

And what about partitions having different completions?

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

From: Tucker Taft
Sent: Tuesday, September 25, 2001, 8:34 AM

Christoph Grein wrote:
>
> I'm not sure that I haven't missed something in the proposal.
> What about multiple completions in one program?

The rule was there, albeit a bit cryptic:

    ... There is a post compilation rule that a separate
    incomplete type may have only a single completion.

I presume the rule when written out in its entirety would make
it clear that at most one completion would be allowed in a
single partition.  I would think it would be OK if different
completions were used in different partitions of the same
program, so long as the type is not a "remote" type.  But
clearly some more thought is needed to understand all the
implications for a distributed program...

> ...
> Or would these be link-time errors?

That's what the phrase "post compilation" means (at least
to language lawyers ;-).


> And what about partitions having different completions?

Good question.  Should probably be legal except where
it causes problems (so what else is new?).

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

From: Simon Wright
Sent: Tuesday, September 25, 2001, 3:58 AM

The code below is my understanding of how what I'm presently doing
with GNAT's version of WITH TYPE might appear under the proposed
scheme. Have I got the right end of the stick?

The thing that causes GNAT most trouble appears to be the Storage_Size
rep clause (I understand Storage_Pool would be problematic too).

  package Department_Interface is
     type Handle is separate;
  end Department_Interface;

  package Employee_Interface is
     type Handle is separate;
  end Employee_Interface;

  with Department_Interface;
  with Employee_Interface;
  package Employee is
     type Instance (<>) is limited private;
     type Handle for Employee_Interface.Handle is access Instance;
     ...
  private
     type Instance is record
	...
	Dept : Department_Interface.Handle;
	...
     end record;
     for Handle'Storage_Size use ...;
     ...
  end Employee;

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

From: Randy Brukardt
Sent: Tuesday, September 25, 2001, 11:34 AM

It's not quite right, because you can't declare an object or component of an
incomplete type. So the component declaration "Dept" is illegal.

See the example in the AI for an example of how to do it. The basic idea is to
move the access type handle to the interface package. Because that is a normal
type, you can give the 'Storage_Size or 'Storage_Pool clause there.

That is, a "with type" becomes a incomplete stub (separate incomplete, I used
Tucker's suggested terminology here); a "with type access" becomes an access to
an incomplete stub.

> The thing that causes GNAT most trouble appears to be the Storage_Size
> rep clause (I understand Storage_Pool would be problematic too).

Right. That was the main reason that "with type access" was dropped from the
"with type" proposal, and eventually, we started looking for alternatives.

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

From: Simon Wright
Sent: Wednesday, September 26, 2001, 4:12 AM

OK, but I get the feeling that the part of the 'Storage_Size clause I
left out is going to be illegal now -- it does depend on a
GNAT-specific attribute.

  package Department_Interface is
    type Instance (<>) is separate;
    type Handle is access Instance;
    for Handle'Storage_Size use (Instance'Object_Size * 42) / 8;
    --  there are only ever going to be 42 Instances at once
    --  8-bit bytes here
  end Department_Interface;

The point of this manoeuvre is to avoid malloc/free in a VxWorks
context. Clearly I could allocate all the Instances I'm going to need
at initialization, or use a special storage pool which is initialized
after I know Instance'Size, or manage a static array, or ...; it
seemed better to use a language mechanism where possible.

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

From: Randy Brukardt
Sent: Wednesday, September 26, 2001, 11:13 AM

You're right, the use of the name "Instance" in the attribute is illegal by the
rules in 3.10.1.

One point that is important to note that your original example couldn't be
written in ANY of the proposals that the ARG is looking at; they all have
incomplete type restrictions on the type. (Private types aren't allowed in
package abstracts because of the freezing rules - I believe that is
intentional.)

> The point of this manoeuvre is to avoid malloc/free in a VxWorks
> context. Clearly I could allocate all the Instances I'm going to need
> at initialization, or use a special storage pool which is initialized
> after I know Instance'Size, or manage a static array, or ...; it
> seemed better to use a language mechanism where possible.

Another way to structure your code would be to use an incomplete deferred to
the body (to allow the component declaration), but that still wouldn't allow
the use of the 'Object_Size attribute.

After all, the entire point of this mechanism (or any of the others) is to
allow you to use a type without knowing much about it - representation, size,
etc. It seems wrong to then turn around and try to find out that information.
The only way to support this would be to totally abandon the requirement to
see the full type before using a partial view -- which would be a giant change
in the Ada model and clearly would have a large impact on implementations. That
seems to be much too big a change for Ada.

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

From: Tucker Taft
Sent: Wednesday, September 26, 2001,  1:43 PM

Simon wrote:

> > OK, but I get the feeling that the part of the 'Storage_Size clause I
> > left out is going to be illegal now -- it does depend on a
> > GNAT-specific attribute.

'Size would almost certainly work just as well as 'Object_Size presuming
you are allocating composite objects.  But the big problem is that
the compiler doesn't know anything about Instance, so 'Size or 'Object_Size
pretty much has to be illegal on a type stub.

> >   package Department_Interface is
> >     type Instance (<>) is separate;
> >     type Handle is access Instance;
> >     for Handle'Storage_Size use (Instance'Object_Size * 42) / 8;
> >     --  there are only ever going to be 42 Instances at once
> >     --  8-bit bytes here
> >   end Department_Interface;

One alternative would be to make this a general access type (... access all ...)
and have a separate access type (collection) which is used for all
allocators, and then convert the result of the allocator to the "visible"
access type declared in Department_Interface.  The access type
used for allocators could be declared at a point that had visibility
on the full type definition of "Instance."  In my experience, a much
smaller portion of the code needs to be able to do allocators than needs
to be able to reference the access type.

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

From: Simon Wright
Sent: Thursday, September 27, 2001, 3:22 AM

Randy said:

> Another way to structure your code would be to use an incomplete
> deferred to the body (to allow the component declaration), but that
> still wouldn't allow the use of the 'Object_Size attribute.

Thanks to you and Tuck for the suggestions, I'll need some time to
digest them!

> After all, the entire point of this mechanism (or any of the others)
> is to allow you to use a type without knowing much about it -
> representation, size, etc. It seems wrong to then turn around and
> try to find out that information. The only way to support this would
> be to totally abandon the requirement to see the full type before
> using a partial view -- which would be a giant change in the Ada
> model and clearly would have a large impact on implementations. That
> seems to be much too big a change for Ada.

Point taken.

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

From: Dan Eilers
Sent: Thursday, September 27, 2001, 12:23 PM

> OK, but I get the feeling that the part of the 'Storage_Size clause I
> left out is going to be illegal now -- it does depend on a
> GNAT-specific attribute.
>
>   package Department_Interface is
>     type Instance (<>) is separate;
>     type Handle is access Instance;
>     for Handle'Storage_Size use (Instance'Object_Size * 42) / 8;
>     --  there are only ever going to be 42 Instances at once
>     --  8-bit bytes here
>   end Department_Interface;

Perhaps a solution to this would be to extend the standard attribute
S'Max_Size_In_Storage_Elements (see RM 13.11.1), so that it can be applied
directly to an access type, instead of only to a subtype designated by an
access type.

This eliminates the problem Randy pointed out, where RM 3.10.1 (5-9)
precludes most usages of incomplete types.

Of course, the amount of storage needed will not be statically known at
the point of the storage_size attribute definition clause, but this clause
does not require a static expression.

Are there elaboration order concerns that would preclude this solution?
The amount of storage needed would probably have to be pre-elaborable.

Then this could be written as:

  package Department_Interface is
    type Instance (<>) is separate;
    type Handle is access Instance;
    for Handle'Storage_Size use (Handle'Max_Size_In_Storage_Elements * 42);
    --  there are only ever going to be 42 Instances at once
  end Department_Interface;

Note that this happens to also eliminate the "/ 8".

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

From: Dan Eilers
Sent: Thursday, September 27, 2001, 12:37 PM

Tuck wrote:
> Christoph Grein wrote:
> >
> > I'm not sure that I haven't missed something in the proposal.
> > What about multiple completions in one program?
>
> The rule was there, albeit a bit cryptic:
>
>     ... There is a post compilation rule that a separate
>     incomplete type may have only a single completion.
>
> I presume the rule when written out in its entirety would make
> it clear that at most one completion would be allowed in a
> single partition.  I would think it would be OK if different
> completions were used in different partitions of the same
> program, so long as the type is not a "remote" type.  But
> clearly some more thought is needed to understand all the
> implications for a distributed program...

Randy wrote:
>         I followed Pascal's lead, and only put in restrictions where they
> were necessary for consistency, semantic, or implementation reasons.

It would be useful to specify the reason for the above rule.
Is it necessary for type safety, as claimed in:
 http://home.bluemarble.net/~jvolan/WithingProblem/FAQ.html#forward_incompletes

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

From: Randy Brukardt
Sent: Monday, October 1, 2001,  7:28 PM

Dan said (talking about the postcompilation rule for a single completion):

> It would be useful to specify the reason for the above rule.
> Is it necessary for type safety, as claimed in:
> http://home.bluemarble.net/~jvolan/WithingProblem/FAQ.html#forward_incompletes

I mentioned the reason in e-mail (see the appendix of the AI) back in July, and
it seemed so obvious that I didn't mention it. Essentially it is necessary to
have only a single completion in a partition, because otherwise you could use
one completion as the other without any warning:

    package Interface is
       type Stub is tagged separate;
    end Interface;

    with Interface;
    package Complete1 is
       type Tag1 for Interface.Stub is tagged record
          F : Float;
       end record;
    end Complete1;

    with Interface;
    package Complete2 is
        type Tag2 for Interface.Stub is tagged record
           I : Integer;
        end record;
    end Complete2;

    with Interface;
    procedure Do_Something (A : in Interface.Stub);

    with Interface, Complete1;
    procedure Do_Something (A : in Interface.Stub) is
    begin
       if A.F = 1.0 then -- (1)
       ...
    end Do_Something;

    with Interface, Complete2, Do_Something;
    procedure Problem is
       T : Complete2.Tag2;
    begin
	 Do_Something (T); -- (2)
    end Problem;

Assume that multiple completions are allowed. The call at (2) is legal, as
Interface.Stub is a subtype of Complete2.Tag2 in Problem (as the completion is
visible). The reference to component F at (1) is also legal, because
Interface.Stub is a subtype of Complete1.Tag1 in Do_Something (again the
completion is visible). But we passed a Tag2 object to Do_Something, which then
proceeds to use it as a Tag1 object (reading an Integer as a Float, or worse).
Obviously, we can't allow that.

The easiest way to solve this problem is to disallow multiple completions in a
single partition, and it doesn't seem to be an important capability. I suspect
that the rule really has to be program (allowing different completions in
different partitions of the same program would allow the same problem to occur
across partitions -- which probably would be worse than the problem in a single
partition).

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

From: Dan Eilers
Sent: Thursday, September 27, 2001, 2:00 PM

Tuck wrote:

> How about defining a new kind of "representation" clause:
>
>     for <full_type> use type <separate_incomplete_type>;
>
> This reads better to me.  It also allows us to associate
> task types and protected types with the separate incomplete type.
> And it doesn't further muck up the syntax for declaring a type,
> which is already one of the most nightmarish parts of the
> Ada grammar.  Clearly this sort of "representation" clause
> should have to be in the visible part of the package if the
> connection between the two types is to be visible outside the package.

Would it make sense to allow an ordinary incomplete type to
be completed in this way as well?

Note: this is getting pretty close to type renaming.

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

From: Tucker Taft
Sent: Thursday, September 27, 2001,  3:27 PM

I'm not sure.  It's actually a bit misleading to call it
"completing" the incomplete type.  The incomplete type
is effectively becoming a subtype of the full type.
Perhaps that is the syntax we should use instead?  E.g.:

    separate subtype P.Type_Stub is Full_Type;

This would nicely generalize for "regular" incomplete types
to be "completed" by a subtype declaration.

    subtype Inc_Type is Whatever.Some_Full_Type;

I'm not sure what particular benefit this would provide,
because presumably the primitive operations of the
type are *not* made visible by this subtype declaration.

Actually, if we are willing to consider going back to the
new-kind-of-compilation-unit, then the correct syntax for
completing a type stub would be a separate compilation unit:

    with Z;
    separate (P)
    subtype Type_Stub is Z.Full_Type;

This would actually be the most consistent with existing stub
completion syntax.  The special rule is that this (sub)type "subunit"
would *not* be substituted into the point of the "stub" unless
the subunit is somehow made visible in a context clause.  And how
do we make the type subunit visible?  Well of course...


   [drum roll...]


    with type P.Type_Stub;

[I can hear the worldwide groans from here ;-]

> Note: this is getting pretty close to type renaming.

I guess, though it seems somewhat upside down.  Sort of
the "come-from" of the renaming world ;-).  I suppose
the real question is whether the primitive operations
of a type stub are implicitly declared (or implicitly
renamed?) at the point of the stub, when the completion
of the stub (whatever syntax that ends up using) is visible.

Randy, did you cover this issue of primitive operation
implicit declaration/renaming?

Any comments on using a subtype declaration-ish syntax
for "completing" a type stub, either as a declaration
within some package spec, or as a separate "type subunit"
compilation unit?

Actually, if we really give these things subtype-like
equivalence, perhaps we should allow the "is separate"
syntax on a subtype declaration only?  And similarly,
use an "incomplete subtype" if we want to complete
the declaration using a subtype declaration.  E.g.:

    subtype Subtype_Stub is separate;
      -- declare a subtype "stub"
  or
    subtype Incomplete_Subtype;
      -- declare an incomplete subtype

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

From: Dan Eilers
Sent: Friday, September 28, 2001, 2:31 PM

I think the "separate incomplete" proposal comes real close to
this, if one puts the full type definition in a child unit
(with the incomplete type in the package spec).
Then, instead of needing a new kind of compilation unit for
a separate subtype, a normal child unit works.  And instead
of needing a new type of "with" clause, a normal "with" of the
child unit works.

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

From: Randy Brukardt
Sent: Monday, October 1, 2001,  7:44 PM

Tuck said:

>    [drum roll...]
>
>
>     with type P.Type_Stub;
>
> [I can hear the worldwide groans from here ;-]

I certainly groaned at this whole line of thought. If we're going to bother
with a new kind of compilation unit, we might as well go with the package
abstracts. The whole point of incomplete stubs is that we don't need a bunch of
heavy new mechanisms to solve the problem.

> I guess, though it seems somewhat upside down.  Sort of
> the "come-from" of the renaming world ;-).  I suppose
> the real question is whether the primitive operations
> of a type stub are implicitly declared (or implicitly
> renamed?) at the point of the stub, when the completion
> of the stub (whatever syntax that ends up using) is visible.
>
> Randy, did you cover this issue of primitive operation
> implicit declaration/renaming?

I didn't "cover it", because I intended that no such thing would happen. I
selected "subtype" specifically because I didn't want to deal with primitive
operations. (Note that a regular incomplete type has no primitive operations of
its own; the problem occurs with tagged incomplete types, which allow
parameters of the type). Certainly, we want the primitive operations to be
declared with the full type. I guess I'd prefer that the incomplete stub had no
primitive operations by itself; we'd have to add a rule saying that there are
no such things. (You can't derive from the incomplete type anyway.) When it is
completed and is acting as a subtype, it is of course the same as the
completing type.

> Any comments on using a subtype declaration-ish syntax
> for "completing" a type stub, either as a declaration
> within some package spec, or as a separate "type subunit"
> compilation unit?

I'm not against the subtype syntax for a completion; it is, after all, exactly
what the semantics are. I like that better than the representation clause
syntax.

> Actually, if we really give these things subtype-like
> equivalence, perhaps we should allow the "is separate"
> syntax on a subtype declaration only?  And similarly,
> use an "incomplete subtype" if we want to complete
> the declaration using a subtype declaration.  E.g.:
>
>     subtype Subtype_Stub is separate;
>       -- declare a subtype "stub"
>   or
>     subtype Incomplete_Subtype;
>       -- declare an incomplete subtype

That is possible, but then of course we'd have to define these things
separately from existing incomplete types. That's not necessarily a bad idea
(it avoids the primitive operations problem noted above cleanly), but it does
increase the "weight" of the solution a bit.

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

From: Dan Eilers
Sent: Tuesday, October 2, 2001,  1:37 PM

Would the type safety problems associated with multiple bindings
of type stubs be avoided by prohibiting the use of the type stub
as anything other than a stub, even after being bound?
If so, this would eliminate the need for cumbersome post-compilation
rules attempting to prevent multiple bindings across independent
partitions within a program.

Slightly modifying Randy's example where noted:

    package Interface is
       type Stub is tagged separate;
    end Interface;

    with Interface;
    package Complete1 is
       type Tag1 for Interface.Stub is tagged record
          F : Float;
       end record;
    end Complete1;

    with Interface;
    package Complete2 is
        type Tag2 for Interface.Stub is tagged record
           I : Integer;
        end record;
    end Complete2;

    with Interface;
    procedure Do_Something (A : in Interface.Stub);

    with Interface, Complete1;
--  procedure Do_Something (A : in Interface.Stub) is
    procedure Do_Something (A : in Complete1.Tag1) is  -- conformant
    begin
       if A.F = 1.0 then -- (1)
       ...
    end Do_Something;

    with Interface, Complete2, Do_Something;
    procedure Problem is
       T : Complete2.Tag2;
    begin
         Do_Something (T); -- (2)  -- compile-time type clash: Tag2 vs. Tag1
    end Problem;

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

From: Gary Dismukes
Sent: Tuesday, October 2, 2001,  2:29 PM

I don't see how that would help.  You're generally going to want to
have access types that designate the stub type, and there's no way
of preventing accesses from denoting objects of differing full types
if you allow multiple full types.  The limitation to one full type
per type stub is a fundamental restriction as far as I can see.

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

From: Randy Brukardt
Sent: Tuesday, October 2, 2001,  2:24 PM

That was my original idea. But the rules for doing that are rather messy, and
it makes these very much harder to use, so I abandoned it. (Note that the
original name of the AI mentioned in the agenda is "forever incomplete types").

Among other things, we would have to invent a series of horrible type
conversion rules (in that model, the incomplete type has to be a completely
separate type); to make this work reasonably, they have to be implicit
conversions. Worse, it means that an access type which designates a separate
incomplete type could never have an allocator (as there would be no place where
such an allocator could be used). And that means that users would have to use
explicit conversions (or 'Access, they're equivalent) to use such a type. That
is one of the major reasons that "with type" was abandoned, and I was quite
unwilling to introduce it into this proposal.

No matter what the model, it's important to remember that "separate incomplete
types" or whatever they end up being called need to be usable. Restrictions on
the use of the type certainly harm the usability.

---

In your example:

    with Interface;
    procedure Do_Something (A : in Interface.Stub);

    with Interface, Complete1;
--  procedure Do_Something (A : in Interface.Stub) is
    procedure Do_Something (A : in Complete1.Tag1) is  -- conformant
    begin
       if A.F = 1.0 then -- (1)
       ...
    end Do_Something;

    with Interface, Complete2, Do_Something;
    procedure Problem is
       T : Complete2.Tag2;
    begin
         Do_Something (T); -- (2)  -- compile-time type clash: Tag2 vs. Tag1
    end Problem;

I don't see why you'd have a type clash here: the specification of Do_Something
clearly specifies Stub, which has to match either type. Without seeing the
body, you don't know which is meant. Surely you can't be suggesting some sort
of body dependence; it is too much dependence that we are trying to get away
from (keep in mind that Do_Something could have been defined in Interface
itself, and a body dependence then would essentially reintroduce the problem
we're trying to get around).

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

From: Tucker Taft
Sent: Monday, February 4, 2002,  5:49 PM

[ai-217-02 is the AI formerly known as "ai-277" ;-]

I thought I would mention that we went ahead and did a prototype
implementation of incomplete type stubs, following the suggestion
from Erhard where the package that defines the type is specified
at the point of the stub.  It turned out to be a relatively modest
enhancement to the existing support for incomplete types.

The only tricky part is defining under exactly what circumstances a
reference to the stub (say "P.T") is equivalent to a reference to
the completing type (say "A.B.C.T").  The basic rule was that,
presuming the package name given in the type stub declaration
was "A.B.C" then "A" must be the name of a (root) library
package that is visible in package Standard at the point
of the reference (because it was "with"ed or because it encloses
the point of reference); similarly B must be a (child) library package
visible within A, and C must be visible within B.  A, B, and C must
not be renamings, to avoid multiple names for the same completion.
And of course a (non-type-stub?) type with the same name as the type stub
(say "T") must be declared within the visible part of "C".  Presumably if "A"
is not *directly* visible (because it is hidden by an inner
homograph) that is OK, so long as <pkg_standard>.A is visible.

This check is done at pretty much any use of P.T *except* as a
designated subtype, as well as any dereference of an access-to-P.T.
One question is if A.B.C is visible at the point of a declaration
of an access-to-P.T type, should you "remember" that?  Our presumption
was no, since the user could have written "A.B.C.T" if they had wanted
those semantics.  Instead, if they write P.T as the designated subtype,
then this may be dereferenced only where A.B.C is visible.

Note that we require A.B.C to be visible, not merely in scope, so
that "ripple" effects due to adding or removing a "distant" with
clause don't alter the semantics.

I suppose another question is what happens when there are two
incomplete stubs with the same completing type.  Are they considered
statically matching subtypes?  Or only where their (shared) completion
is "available."  (I am using the term "available" to mean
the full type's full name, e.g. A.B.C.T, is visible.)

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

From: Randy Brukardt
Sent: Monday, February 4, 2002,  5:49 PM

> [ai-217-02 is the AI formerly known as "ai-277" ;-]

Well, actually, AI-00217-03 (the third alternative of AI-217) is the AI
formerly known as AI-277.

> I thought I would mention that we went ahead and did a prototype
> implementation of incomplete type stubs, following the suggestion
> from Erhard where the package that defines the type is specified
> at the point of the stub.  It turned out to be a relatively modest
> enhancement to the existing support for incomplete types.

Great! I need this yesterday in the Claw Builder. :-)

I've been waiting for a proposal (and the upcoming meeting) before doing
anything about an implementation.

How did you describe the package "name" in the incomplete stub (since it cannot
be a 'name' in the Ada sense, as it is not visible and cannot 'denote' anything
in the stub -- at least if it is to be a useful construct)?

> The only tricky part is defining under exactly what circumstances a
> reference to the stub (say "P.T") is equivalent to a reference to
> the completing type (say "A.B.C.T").  The basic rule was that,
> presuming the package name given in the type stub declaration
> was "A.B.C" then "A" must be the name of a (root) library
> package that is visible in package Standard at the point
> of the reference (because it was "with"ed or because it encloses
> the point of reference); similarly B must be a (child) library package
> visible within A, and C must be visible within B.  A, B, and C must
> not be renamings, to avoid multiple names for the same completion.
> And of course a (non-type-stub?) type with the same name as the type stub
> (say "T") must be declared within the visible part of "C".  Presumably if "A"
> is not *directly* visible (because it is hidden by an inner
> homograph) that is OK, so long as <pkg_standard>.A is visible.

Is the restriction against renames really necessary? It seems odd to introduce
a case where a renamed package is not semantically equivalent to the package it
renames. Such a restriction may make sense for the declaration (that would be
equivalent to the rule for child packages), but I don't think you would want to
enforce that on the checks (which is the 'use' of the rename, which is
certainly allowed for child units).

> ...
>
> I suppose another question is what happens when there are two
> incomplete stubs with the same completing type.  Are they considered
> statically matching subtypes?  Or only where their (shared) completion
> is "available."  (I am using the term "available" to mean
> the full type's full name, e.g. A.B.C.T, is visible.)

I would think only when the (shared) completion is available. But it doesn't
seem important either way.

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


Questions? Ask the ACAA Technical Agent