!standard 3.10.1(2) 05-03-21 AI95-00412/04 !standard 3.10.1(7) !standard 8.3(20) !standard 8.5.3(3) !standard 8.5.3(4) !standard 10.1.2(8) !class amendment 05-02-03 !status Amendment 200Y 05-02-28 !status ARG Approved 5-0-5 05-02-12 !status work item 05-02-03 !status received 05-02-03 !priority Medium !difficulty Medium !subject Subtypes of incomplete types; renamings of limited views !summary Subtype declarations are permitted when the subtype mark is that of an incomplete type provided they do not have any constraints or a null exclusion. Renaming of the limited view of a package is permitted. The view visible via a rename of a library package (or one of its visible subpackages) is determined by what kind of with clause applies at the point of reference. !problem The introduction of limited with clauses and the fact that use package clauses and renamings are not permitted with a limited view can create long names which cannot be abbreviated since subtype declarations are not permitted for an incomplete type. In writing examples for the rationale, we have found some of these long names tedious, and wonder whether we couldn't allow the declaration of subtypes, so long as no constraint is specified. Similarly, we wonder whether we could also permit renamings of limited views of packages, as this is a common alternative approach to "use" clauses to provide "shorthand" notations for entities whose full name is very long. Even if we didn't permit renaming of limited views, there is a question of what view of a package is visible via a renaming declared in a place that has a full view, but referenced in a place that has only a limited view of a package. !proposal To answer the last issue first -- if referenced from within the scope of a limited with for a package, any renaming of that package that might occur in some other compilation unit provides the same limited view. Similarly, if the package has nested packages, then renames of those also provide limited views when referenced from inside the scope of the limited with. We propose to allow declarations of constraint-less subtypes of incomplete types, and renamings of limited views. These have slightly different rules, so we will describe them separately. For an incomplete type other than one implicitly declared in a limited view of a package, a named subtype denotes the full view of the type where the full view is visible. For an incomplete type from a limited view of a package, a named subtype denotes the full view when within the scope of a non-limited with clause for the enclosing library unit. Otherwise it denotes the incomplete view. This is intended to mirror the rules for access-to-incomplete types. For a renaming of a limited view, the renaming may only be used within the scope of a limited or non-limited with clause for the enclosing library unit. If neither kind of with clause for the library unit containing the viewed package applies to a given place, no reference to the renaming is permitted. If within the scope of a with clause, then if it is limited, a limited view is provided, if the with clause is non-limited, then a full view is provided. This ensures that only one view of a given package is visible at a given point. These rules are intended to ensure that there is never the need to implicitly produce a full view of a package as a side effect of referencing a renaming of a limited view or a subtype of an incomplete type, except within the scope of a non-limited with clause for the package. Equivalently, after processing the context clause, for any given library package, no further views will be needed. !wording Add after the paragraphs added by AI-326 to 3.10.1(2): Similarly, if a subtype_mark denotes a subtype_declaration defining a subtype of an incomplete view T, the subtype_mark denotes an incomplete view except under the same two circumstances given above, in which case it denotes the full view of T. Add after 3.10.1(6/2) (might as well reuse 7/2 which is now empty): * as the subtype_mark in the subtype_indication of a subtype_declaration; the subtype_indication shall not have a null_exclusion or a constraint; Revise 8.3(20) as follows: * The declaration of a library unit (including a library_unit_renaming_declaration) is hidden from all visibility at places outside its declarative region that are not within the scope of a nonlimited_with_clause that mentions it. The {declaration} of the limited view of a library package is hidden from all visibility at places that are not within the scope of a limited_with_clause that mentions it; in addition, the {declaration of the} limited view is hidden from all visibility within the declarative region of the package, as well as within the scope of any nonlimited_with_clause that mentions it. {Where the declaration of the limited view of a package is visible, any name that denotes the package denotes the limited viewRedundant:[, including those provided by a package renaming.]} * Redundant:[For each declaration or renaming of a generic unit as a child of some parent generic package, there is a corresponding declaration nested immediately within each instance of the parent.] Such a nested declaration is hidden from all visibility except at places that are within the scope of a with_clause that mentions the child. Drop the restriction of 8.5.3(3/2) that requires the view be nonlimited. Instead, add the following: If the package_name of a package_renaming_declaration denotes a limited view of a package P, then a name that denotes the package_renaming_declaration shall occur only within the immediate scope of the renaming or the scope of a with_clause that mentions the package P or (if P is a nested package) the nearest library package enclosing P. Add after 8.5.3(4): Redundant:[At places where the declaration of the limited view of the renamed package is visible, a name that denotes the package_renaming_declaration denotes a limited view of the package (see 10.1.1).] AARM Note: Proof: This rule is found in 8.3, Visibility. Replace the second paragraph added after 10.1.2(8) by AI-217-6 with: A limited_with_clause shall not appear on a library_unit_body, subunit, or library_unit_renaming_declaration. AARM Reason: We don't allow a limited_with_clause on a library_unit_renaming_declaration because it would be useless. A renaming could not appear in a limited_with_clause (by the rule above), and a renaming could not appear in a nonlimited_with_clause (because the name would not be within the scope of a with_clause denoting the package, see 8.5.3). Nor could it be a parent of another unit. That doesn't leave anywhere that that the name of such a renaming *could* appear. !discussion The canonical example of limited with clauses is limited with Departments; package Employees is type Employee is tagged private; procedure Assign_Employee(E: in out Employee; D: in out Departments.Department'Class); type Dept_Ptr is access all Departments.Department'Class; function Current_Department(E: Employee) return Dept_Ptr; ... end Employees; limited with Employees; package Departments is type Department is tagged private; procedure Choose_Manager(D: in out Department; M: in out Employees.Employee'Class); ... end Departments; The names Departments.Department and Employees.Employee are long and cumbersome. There are two ways of avoiding such long names in general. One is by adding use clauses and the other is by renaming (or using subtypes). In the case of limited with, use package clauses are forbidden. Moreover use package clauses are often forbidden by projects anyway. In such circumstances the use of renaming or subtypes is very useful. Thus we might have V: Float renames Aeroplane_Data.Current_Velocity; The use of meaningful package names and variable names is good practice but when put together they can be overwhelming. The renaming enables an abbreviation to be used locally but ensures that the full name is still visible to the human reader. In the case of types, renaming is not appropriate but a subtype serves an equivalent purpose thus subtype T is Long_Package_Name.Long_Type_Name; In the Employees and Departments example we would like to say subtype Dept is Departments.Department; and subtype Empl is Employees.Employee; This is currently forbidden because the name of an incomplete type is only permitted in a short list of places by 3.10.1(5/2-10/2). We therefore add that it can also be used in a subtype declaration. Clearly however, we know little about the type (except that it might be tagged) and so constraints and not null are not permitted. The only other things made visible by a limited with clause are the names of the package itself and any nested packages. We are also proposing to allow renaming of such packages. Because subtype declarations and package renamings can be used to "re-export" items made visible in a unit via limited with clauses, we need to be concerned about what are the properties of these re-exported views. One concern is about "ripple" effects, by which we mean that the addition, removal, or change of a with clause on some "distant" member of the semantic closure of a unit might affect the legality of the given unit. In particular, we are worried what happens when we change a "with" clause to a "limited with" clause, or vice-versa. In the past, we have been concerned primarily about ripple effects relating to a rule that causes the presence or absence of a particular unit in the semantic closure to affect the legality of otherwise unrelated constructs. For example, in Ada 83, the 'Address attribute was only legal if package System was somewhere within the semantic closure. This notion was dropped in Ada 95, as it is surprising during maintenance, and it makes incremental compilation that much harder. In the case of the "limited with," we tried to avoid having ripple effects that determined whether the dereference of an access-to-incomplete type was itself incomplete based on whether there was a non-limited with clause on the package defining the full type somewhere in the closure. We ultimately decided the with clause had to be on the unit itself. But we also allowed incomplete to match complete in more circumstances. Nevertheless, we are still left with a semi-ripple effect, in that if the place where the access type is declared changed its with clause from a non-limited to a limited with clause, the dereference of an access value of the type shifts from being non-incomplete to incomplete. This is a "semi" ripple effect in that there must be an unbroken chain of references crossing only direct "with" clause dependences back to the unit whose with clause is changed. This does not seem as bad. This is more like there being two separate packages, P1 and P2, both defining a type T, one private and one non-private, and a third package defining an access-to-T type. If the third package changes its with clause to "with and use" P2 rather than P1, then the properties of the designated type of the access-to-T changes, and if we make use of values of that type, they change from being access-to-non-private-T to access-to-private-T. That of course is not at all surprising. Effectively we should realize that changing a "with" clause to a "limited with" is somewhat like change "with P1" to "with P2." We are seeing a significantly different set of types and subpackages. If we reexport entities whose properties derive from declarations within P1 and P2, or equivalently, within the full or limited view of a package, then the change in the with clause will certainly be visible to those who import those entities. We have tried to isolate this somewhat, by saying that if the "importing" unit itself has a "with" or "limited with" clause on the package, then that determines its view of a renaming, a subtype-of-T, or an access-to-T. If it has neither kind of with clause, then for subtype-of-T and access-to-T, the incomplete/access-to-incomplete view is provided. For renaming, we make neither view available. For renaming and subtype-of-T, we considered providing an "implicit" non-limited "with" if needed, but it was pointed out that there are implementations that rely on the context clause to determine order of compilation, and having such implicit dependences on the full view of a package would seriously undermine that approach. This alternative is considered in more depth in the following. A REJECTED ALTERNATIVE A possible alternative is to say that you always get the full view, *unless* you are in the scope of a limited with. This would probably have friendlier behavior during maintenance, and during transition to Ada 2005. Let's take a specific scenario. We have two packages in Ada 95, P_Pkg and Q_Pkg, which declare T and U, respectively. We would really like to have P_Pkg have operations on U, and Q_Pkg have operations on T, but we get into a cycle, so we play the "class-wide" type trick, as follows: package Q_Root_Pkg is type Q_U_Root is tagged ... end Q_Root_Pkg; with Q_Root_Pkg; use Q_Root_Pkg; package P_Pkg is type T is tagged ... procedure Cyclic(X : Q_U_Root'Class); -- I really wanted to take Q_Pkg.U but -- I am using the old Ada 95 class-wide -- trick to avoid the circularity. -- In the body of Cyclic I'll convert X to Q_Pkg.U. end P_Pkg; with Q_Root_Pkg; use Q_Root_Pkg; with P_Pkg; package Q_Pkg is package P_Ren renames P_Pkg; -- declare a shorthand subtype P_T is P_Ren.T; -- declare a shorthand type U is new Q_U_Root with ... -- for the class-wide trick procedure Fun(Y : P_T); end Q_Pkg; Now let's suppose that we have lots of existing code that depends on Q_Pkg, and makes use of some of the shorthands it declares: with Q_Pkg; use Q_Pkg; procedure Some_Random_Procedure is A : P_Ren.T; -- use shorthand B : P_T; -- use shorthand begin Fun(A); Fun(B); end; Now along comes Ada 2005, and we realize we can change the "with" of P_Pkg to a "limited with" of P_Pkg, and then add a "with" of Q_Pkg on P_Pkg, and change the Cyclic procedure to take a Q_Pkg.U directly, rather than playing the class-wide type game. We also have these shorthands declared in Q_Pkg. We can erase them, but then who knows how much code is using them. But with this proposal, we allow shorthands to be used even in the presence of limited with, so I'm saved. Hence, we can leave the shorthands there, change the "with" of P_Pkg to a "limited with" of P_Pkg, and fix Cyclic to produce: with Q_Pkg; use Q_Pkg; package P_Pkg is type T is tagged ... procedure Cyclic(X : U); -- In Ada 2005 I can say what I mean! end P_Pkg; limited with P_Pkg; package Q_Pkg is package P_Ren renames P_Pkg; -- declare a shorthand subtype P_T is P_Ren.T; -- declare a shorthand type U is tagged ... procedure Fun(Y : P_T); end Q_Pkg; Unfortunately, all the compilation units that were using the shorthands are now in trouble, depending on what are the rules relating to the re-export of limited/incomplete views. There are various choices, and conceivably one could make different choices for package renaming and subtype declarations (and deref of access-to-incomplete): 1) In the absence of a "with" or "limited with" clause: a) you see the same view as was present at the place where the shorthand was declared; b) you see the full view 2) In the presence of a "with" clause: a) you see the same view as at the place where the shorthand was declared b) you see the full view 3) In the presence of a "limited with" clause: a) you see the same view as at the place where the shorthand was declared b) you see the limited/incomplete view We haven't talked much about (1), but for package renaming, we are proposing 2(b) and 3(b) -- the "local" with clause wins. For subtypes, we agree that for 3, we want (a), that is the subtype doesn't "become incomplete". We are proposing that we allow "shorthand" subtypes of incomplete types, but we haven't really concluded whether we want 2(a) or 2(b) for them, that is do we want them to "become full" when viewed from a place that has a full view on the package. For deref of access-to-incomplete, we seem to have 2(b) and 3(b). When neither a "with" or a "limited with" is in scope, we seem to treat it as an incomplete view, but allow it to match the full view. If you look at it from the transition and maintenance point of view, it would seem ideal if changing a "with" to a "limited with" really *only* affected the compilation units in the scope of the limited with clause. Any other compilation units are unaffected. This would say that our rule for deref of access-to-incomplete is not quite right. We shouldn't require the location of the deref (e.g. procedure P3 in example given near the top) to add a "with" clause just because the place where the access type was declared (package P2) changed a "with" to a "limited with." This would also argue that you see the full view via a shorthand, unless you are in the scope of a limited with. An implication of this approach is that you end up with an implicit semantic dependence on the full view if you dereference an access-to-incomplete or use a subtype-of-incomplete name outside the scope of a limited with, and you use the deref or the subtype name in a way that would be illegal if it were incomplete (e.g. using a deref as a prefix, or using the subtype to declare an object). Similarly, if you use a package rename in a context outside of the scope of a limited with for the renamed package (or its enclosing library unit), other than in another renaming, you get the full view (and hence an implicit semantic dependence on the full view). This approach relates to the choice we made in Ada 95 to create an implicit semantic dependence on package System when the 'Address attribute is used. Essentially, we are saying that the clients of a package that has only a limited view on a package may end up with an implicit semantic dependence on the full view of the package. Equivalently, if you need to break circularity, then you need to change a with clause to a limited with, or *add* a limited with, on one or more units that are part of the circularity. But you never have to add a "with" clause on units *outside* the circularity. The problem with this approach is that Ada is an explicit language; implicit things are kept to a minimum. Implicit dependencies are bad for both implementers and maintenance programmers. Maintenance programmers are faced with operations that are mysteriously available, and it may take looking in a number of units to find their declarations - they could only be found by tracing back a chain of declarations. Moreover, changes in a unit could possibly affect some code that does not explicitly mention the unit, which is likely to be confusing. Implementations are complicated by implicit dependencies; many implementations find the declarations to load based on the explicit list of dependencies given in the context clause; anything that requires items to be made available later could be very hard to add. The existing case if implicit dependence doesn't suffer from these problems because Address is a magic attribute anyway, and no one is giving to be surprised where it is declared. Therefore, we reject any approach that requires implicit dependencies. BOTTOM LINE: We propose to allow subtype-of-incomplete and renaming limited views. We also want to minimize ripple effects. Finally, we want to preserve the principle that the context clause determines the semantic dependences (ignoring language-defined implicit dependences on certain standard packages). The net effect is that when you change a with clause of some unit Q from "with P" to "limited with P," some of the clients of Q may need to add their own "with P" to remain compilable. This is because Q may now be exporting access-to-incomplete, subtype-of-incomplete, and renaming-of-limited, and a "with P" may be needed in the client to turn these back into access-to-complete, subtype-of-complete, and renaming-of-full-view. It is the intent that no other changes to clients would be necessary to survive this shift in Q. !example (See discussion.) !corrigendum 3.10.1(2) @dinsa @xcode<@fa@ft<@b>@fa< defining_identifier [discriminant_part];>> @dinsa Similarly, if a @fa denotes a @fa defining a subtype of an incomplete view @i, the @fa denotes an incomplete view except under the same two circumstances given above, in which case it denotes the full view of @i. !corrigendum 3.10.1(7) @drepl @xbullet defining the subtype of a parameter or result of an @fa;> @dby @xbullet in the @fa of a @fa; the @fa shall not have a @fa or a @fa;> !corrigendum 8.3(20) @drepl @xbullet) is hidden from all visibility except at places that are within its declarative region or within the scope of a @fa that mentions it. For each declaration or renaming of a generic unit as a child of some parent generic package, there is a corresponding declaration nested immediately within each instance of the parent. Such a nested declaration is hidden from all visibility except at places that are within the scope of a @fa that mentions the child.> @dby @xbullet) is hidden from all visibility at places outside its declarative region that are not within the scope of a @fa that mentions it. The limited view of a library package is hidden from all visibility at places that are not within the scope of a @fa that mentions it; in addition, the limited view is hidden from all visibility within the declarative region of the package, as well as within the scope of any @fa that mentions it. Where the declaration of the limited view of a package is visible, any name that denotes the package denotes the limited view, including those provided by a package renaming.> @xbullet