!standard 03.10.01 (02) 01-09-21 AI95-00277/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 full_type_declaration ::= type [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 is separate; type 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 [completion_clause] ... completion_clause ::= for [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 ;", '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 ;", '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 use 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 'Base use ; Attribute 'Base is not quite right, of course, but something else might be (e.g. for 'Interface use ... or for '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 use 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 use 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. *************************************************************