Version 1.6 of ais/ai-00396.txt

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

!standard 3.9.4(1)          05-05-18 AI95-00396/03
!standard 7.3(7)
!class amendment 05-01-25
!status Amendment 200Y 05-03-13
!status WG9 Approved 06-06-09
!status ARG Approved 9-0-1 05-02-13
!status work item 05-01-25
!status received 05-01-25
!priority High
!difficulty Easy
!subject The "no hidden interfaces" rule
!summary
(See proposal.)
!problem
The "no hidden interfaces" rule of the tenth paragraph of the subclause 3.9.4 added by AI-251 is overly zealous. It should be allowed to hide interfaces when the partial view is untagged. Also, the justification for this rule in the AARM is obscure.
!proposal
Allow an untagged partial view to be completed by a tagged type that implements some interfaces. Also improve the AARM justification.
!wording
Delete of the tenth paragraph of the subclause 3.9.4 added by AI-251.
Add after 7.3(7):
If a full type has a partial view that is tagged, then:
* the partial view shall be a synchronized tagged type (see 3.9.4) if and only if the full type is a synchronized tagged type;
AARM Note: Reason: Since we do not allow record extensions of synchronized tagged types, this property has to be visible in the partial view to avoid privacy breaking. Generic formals do not need a similar rule as any extensions are rechecked for legality in the specification, and extensions of tagged formals are always illegal in a generic body.
* the partial view shall be a descendant of an interface type (see 3.9.4) if and only if the full type is a descendant of the interface type.
AARM Note: Reason: Consider the following example:
package P is package Pkg is type Ifc is interface; procedure Foo (X : Ifc) is abstract; end Pkg;
type Parent_1 is tagged null record;
type T1 is new Parent_1 with private; private type Parent_2 is new Parent_1 and Pkg.Ifc with null record; procedure Foo (X : Parent_2); -- Foo #1
type T1 is new Parent_2 with null record; -- Illegal. end P;
with P; package P_Client is type T2 is new P.T1 and P.Pkg.Ifc with null record; procedure Foo (X : T2); -- Foo #2 X : T2; end P_Client;
with P_Client; package body P is ...
procedure Bar (X : T1'Class) is begin Pkg.Foo (X); -- should call Foo #1 or an override thereof end;
begin Pkg.Foo (Pkg.Ifc'Class (P_Client.X)); -- should call Foo #2 Bar (T1'Class (P_Client.X)); end P;
If this example were legal (it is illegal because the completion of T1 is descended from an interface that the partial view is not descended from), T2 would implement Ifc twice. Once in the visible part of P, and once in the visible part of P_Client. We would need to decide how Foo #1 and Foo #2 relate to each other. There are two options: either Foo #2 overrides Foo #1, or it doesn't.
If Foo #2 overrides Foo #1, we have a problem because the client redefines a behavior that it doesn't know about, and we try to avoid this at all costs, as it would lead to a breakdown of whatever abstraction was implemented. If the abstraction didn't expose that it implements Ifc, there must be a reason, and it should be able to depend on the fact that no overriding takes place in clients. Also, during maintenance, things may change and the full view might implement a different set of interfaces. Furthermore, the situation is even worse if the full type implements another interface Ifc2 that happens to have a conforming Foo (otherwise unrelated, except for its name and profile).
If Foo #2 doesn't override Foo #1, there is some similarity with the case of normal tagged private types, where a client can declare an operation that happens to conform to some private operation, and that's OK, it gets a different slot. The problem here is that T2 would implement Ifc in two different ways, and through conversions to Ifc'Class we could end up with visibility on these two different implementations. This is the "diamond inheritance" problem of C++ all over again, and we would need some kind of a preference rule to pick one implementation. We don't want to go there (if we did, we might as well provide full-fledged multiple inheritance).
Note that there wouldn't be any difficulty to implement the first option, so the restriction is essentially methodological. The second option might be harder to implement, depending on the language rules that we would choose.
End AARM Note.
!discussion
(See proposal.)
!example
(See wording.)
!comment The wording of AI-251 for 3.9.4 is omitted here; as a new section,
!comment we can't reference it. The changes are placed into the conflict file.
!comment We just put a dummy paragraph here:
!corrigendum 3.9.4(1)
Insert new clause:
An interface type is an abstract tagged type that provides a restricted form of multiple inheritance. A tagged, task, or protected type may have one or more interface types as ancestors.
!corrigendum 7.3(7)
Insert after the paragraph:
If the partial view is tagged, then the full view shall be tagged. On the other hand, if the partial view is untagged, then the full view may be tagged or untagged. In the case where the partial view is untagged and the full view is tagged, no derivatives of the partial view are allowed within the immediate scope of the partial view; derivatives of the full view are allowed.
the new paragraphs:
If a full type has a partial view that is tagged, then:
!ACATS test
An ACATS C-Test should be created to test that an untagged private type can be completed with a type that has interfaces.
!appendix

From: Pascal Leroy
Sent: Monday, May 9, 2005 10:29 AM

> 3.4 (5) should say "record_extension_part or an
> interface_list"  (and-> or;
>   no parenthesis; Reason: simple logic)

In a subsequent message you wrote "have an interface_list in a derived
type definition, then the parent must be tagged (which a task type is
not)".  Hmm.  Unfortunately, a task type *may* be tagged, precisely if it
is derived from one or more interfaces (that's important for Class to
work, see 3.9(2.1)).  So we need to say that the parent is not a tagged
task or protected type.  It would be simple enough to say "if and only if
the parent type is a tagged record type".  But what about privacy?
Consider:

	package P is
	   type T is tagged limited private;
	private
	   task type T is new I with ...
	end P;

	type NT is new P.T with ...; -- Legal?  Hopefully not!

HELP!  What is it that makes NT illegal?

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

From: Bob Duff
Sent: Monday, May 9, 2005  1:59 PM

Isn't that illegal by 3.9.4(10/2) (in the 13 April 2005 version)?

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

From: Gary Dismukes
Sent: Monday, May 9, 2005  3:42 PM

Right, that prevents it.

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

From: Pascal Leroy
Sent: Tuesday, May 10, 2005  4:31 AM

Yes, you're right and I realized that after sending my message.  But
consider the following variation on the above example:

	type I is limited interface;

	package P is
	   type T is limited new I with private;
	private
	   task type T is new I with ...;
	end P;

	type NT is new P.T with ...; -- Legal?

As far as I can tell the declaration of P.T is legal (and useful).  The
full view of P.T may or may not be a task (or protected) type, and
evidently this should not have any bearing on the legality of NT.

So it seems to me that extending a limited tagged partial view that has
limited progenitors should be disallowed.

And of course, there is a dual problem with generics.

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

From: Bob Duff
Sent: Tuesday, May 10, 2005  7:11 AM

But that seems useful, too!

Imagine explaining this to a programmer, who wants to do something simple:

        type I is limited interface;

        package P is
           type T is limited new I with private;
        private
           type T is new I with record...; <----------------
        end P;

        type NT is new P.T with ...; -- Legal?

Programmer: Why can't I do that?
Language lawyer: Because you *might* have said "task type T..."!
Programmer: But my program is not even multi-tasking!  Grumble.

What's the workaround?  Avoid limited interfaces?
Avoid private extensions?  Maybe I'm missing something,
but this seems pretty severe.

I'm sorry, but I don't really understand the problem.
I guess there's a principle that says "no type extensions
for tasks (even if the task-ness is private)"?
And (again I'm guessing) this is for implementation reasons,
not semantic reasons?

Tucker always talks about the "parent is like a component" model.
Well, there's no problem creating a record containing a P.T,
so...  Is it really a problem?

If so, wouldn't it be better to restrict tasks, than to restrict the
basic OOP features of the language?  Something along the lines of "no
private tagged/task-ness".  I.e. if a private type inherits from
limited interfaces, the full type shall not be a task type.

> And of course, there is a dual problem with generics.

Of course.  ;-)

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

From: Tucker Taft
Sent: Tuesday, May 10, 2005  7:17 AM

> So it seems to me that extending a limited tagged partial view that has
> limited progenitors should be disallowed.

Yuck, this seems like a bizarre limitation, and would
exist solely because synchronized tagged types exist.

I would rather require that if a private extension
is completed by a task or protected type, the private extension
shall be derived from a synchronized interface.

That is, no hidden "synchronized-tagged-ness".
Equivalently, if a full view is synchronized tagged, then
the partial view shall be untagged, or synchronized tagged.

> And of course, there is a dual problem with generics.

Blech.

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

From: Pascal Leroy
Sent: Tuesday, May 10, 2005  7:40 AM

> Programmer: Why can't I do that?
> Language lawyer: Because you *might* have said "task type T..."!
> Programmer: But my program is not even multi-tasking!  Grumble.

I knew you would say something like.  But get real, the Reference Manual
has 1054 pages of rules like that ;-)

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

From: Pascal Leroy
Sent: Tuesday, May 10, 2005  7:43 AM

> I would rather require that if a private extension
> is completed by a task or protected type, the private
> extension shall be derived from a synchronized interface.
>
> That is, no hidden "synchronized-tagged-ness".
> Equivalently, if a full view is synchronized tagged, then
> the partial view shall be untagged, or synchronized tagged.

This makes much more sense than what I was proposing.

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

From: Robert I. Eachus
Sent: Tuesday, May 10, 2005  5:47 PM

> I would rather require that if a private extension
> is completed by a task or protected type, the private extension
> shall be derived from a synchronized interface.

This sounds much more reasonable to me as well.  Since if you want to
extend a non-synchronized interface with a task you can make it a record
component, there is no significant burden on users who do want to extend
interfaces this way.  And anyone who doesn't want or need tasking can
ignore that part of the RM.

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

From: Tucker Taft
Sent: Tuesday, May 10, 2005  9:15 AM

Here is a possible wording fix:

   Replace 3.9.4(10/2) with the following:

      If a full type has a partial view that is tagged (see 7.3), then:

        * the partial view shall be a descendant of an interface type
          if and only if the full type is a descendant of the
          interface type;

        * the partial view shall be a synchronized tagged type if
          and only if the full type is a synchronized tagged type.

I would consider moving this whole paragraph to immediately after
7.3(7), as it seems more sensible as a rule about partial views,
rather than a rule about full types.  7.3(7) is where we state
that if a partial view is tagged, then its full view shall
be tagged.

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


Questions? Ask the ACAA Technical Agent