Version 1.2 of acs/ac-00147.txt

Unformatted version of acs/ac-00147.txt version 1.2
Other versions for file acs/ac-00147.txt

!standard 4.9.1(1.2/2)          07-10-15 AC95-00147/01
!class confirmation 07-10-15
!status received no action 07-10-15
!status received 07-06-15
!subject Limited types, initialization, and type conversion
!summary
!appendix

From: Stephen W Baird
Sent: Tuesday, July 31, 2007  5:06 PM

The following example is illegal, but it seems that what the user is
trying to accomplish is reasonable:

  procedure Test is
    package Pkg is
        type T is tagged limited null record;
        function F (X : T) return T;
    end Pkg;
    use Pkg;

    type Ref is access all T'Class;

    function Alloc (Ptr : Ref) return Ref is
    begin
        return new T'Class'(T'Class (F (Ptr.all)));
    end Alloc;

    package body Pkg is separate;
  begin
    null;
  end Test;

This example is illegal because 7.5(2.1/2) doesn't allow type conversions.

In private correspondence, Pascal pointed out that this restriction
could be worked around by adding a function:

    function Alloc (Ptr : Ref) return Ref is
      function Foo return T'Class is
      begin
         return F (Ptr.all);
      end Foo;
    begin
       return new T'Class'(Foo);
    end Alloc;

This function allows the conversion from T to T'Class to be performed
implicitly rather than explicitly.

Thus, we have a situation where we disallow a clearer, more concise expression
of a program but allow a dynamically equivalent version which requires an
auxiliary function whose purpose does not seem obvious.

Pascal also observed that this was similar to the situation with AI05-0032:
    "It seems that there are analogies: in both cases
     class-wide types are involved, and in both cases an extra function
     is needed to fix the problem."
and suggested that this discussion may belong in that AI.

There are good reasons for 7.5(2.1/2)'s treatment of type conversions
in the case where the target type of the conversion is specific.

On the other hand, allowing a conversion to a class-wide type which includes
the type of its operand does not introduce any new cases that
cannot already be produced using Pascal's conversion function trick.

I think we should consider modifying 7.5(2.1/2) to allow such type
conversions.

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

From: Tucker Taft
Sent: Tuesday, July 31, 2007  5:26 PM

I'm a little unclear why in the example you don't
just write:

     return new T'(F(Ptr.all));

What are the contexts where you need to qualify
with a class-wide type?

In any case, I don't have an objection to allowing
conversions to a class-wide type, though I am
curious in what "copying" contexts they would be needed.

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

From: Stephen W Baird
Sent: Wednesday, August 1, 2007  3:52 PM

> I'm a little unclear why in the example you don't just write:
>
>      return new T'(F(Ptr.all));

Good point.

> In any case, I don't have an objection to allowing
> conversions to a class-wide type, though I am
> curious in what "copying" contexts they would be needed.

It sounds like this is "insufficiently broken".

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

[Editor's note: October 15, 2007]

It is clear that only a few kinds of type conversions would be safe
in build-in-place contexts. Surely, we couldn't allow anything that
could change representation or discard portions of a type. That
suggests that allowing those kinds of conversions could be confusing
for the user, as the rules would appear rather arbitrary. Thus, I don't
think this is a good idea.

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

!topic Ada 2005 makes limited private idiom illegal
!reference 7.5
!from Adam Beneschan 08-05-21
!discussion


I've run into a problem where an idiom I found useful in Ada 95 is
no longer allowed in Ada 2005.  

Suppose you have a package whose purpose is to open a communication
channel to another computer, using Unix sockets:

   package Socket_Communication is
       type Channel is limited private;
       function Open_Channel (Computer_Name : String) return Channel;
       ... other operations
   private
       type Channel is record  -- not limited
          ...
       end record;
       ... other definitions
   end Socket_Communication;

(Note that the full type is not limited, so there wouldn't have been any
return-by-reference issues in Ada 95.)

Later, you want to write a different communication package for some more
specialized purpose---say a package that connects to a specified central
warehouse to get information on the current inventory.  In the abstract,
you might want the visible part of the package spec to say something like:

   package Warehouses is
       type Warehouse_Connection is limited private;
       function Open_Connection (Warehouse_Number : Integer)
           return Warehouse_Connection;

For the implementation, let's say you decide to implement it using
Socket_Communication.  Reasonable enough, right?  In general, a
Warehouse_Connection may need to carry additional information besides
the Socket_Communication info, but let's say it doesn't.  It seems
natural to want to implement it as:

   with Socket_Communication;
   package Warehouses is
       type Warehouse_Connection is limited private;
       function Open_Connection (Warehouse_Number : Integer)
           return Warehouse_Connection;
       ...
   private
       type Warehouse_Connection is new Socket_Communication.Channel;
       ...

and then implement Open_Connection as

       function Open_Connection (Warehouse_Number : Integer)
            return Warehouse_Connection is
           Name : constant String := 
                    Warehouse_Computer_Name_Of (Warehouse_Number);
       begin
           return Warehouse_Connection 
                    (Socket_Communication.Open_Channel (Name));
       end Open_Connection;

Seems simple enough, right?  BZZZZZZZT!  It isn't legal.  It was legal
in Ada 95, but no more, because:

(1) only the partial view of Socket_Communication.Channel is
    available, and that type has the word "limited" in its definition;
    therefore Socket_Communication.Channel is limited by 7.5(4);

(2) Warehouse_Connection is derived from Socket_Communication.Channel,
    and is therefore limited by 7.5(6.1);

(3) 7.5(2.1) and 7.5(2.8) apply, and since a type conversion isn't
    listed as one of the possible expressions that can appear in a
    return statement, it isn't legal.

I don't know what the solution is, or if one is needed.  I haven't
studied things enough to figure out whether type conversions, or certain
kinds of type conversions, could be allowed in 7.5(2.1).

I actually ran into this while trying to implement both Ada.Text_IO and
Ada.Wide_Text_IO, putting all the interesting code in Wide_Text_IO and
implementing Text_IO mostly by making calls to Wide_Text_IO.
(File_Type is limited private in both packages, so code I had gotten
working for Ada 95 no longer compiles, for the language-defined functions
that return File_Type.)  But I think the problem is more general than that.
I'm not sure how serious it is; the problem can be worked around by making
the full view of Warehouse_Connection a (nonlimited) record with a
Socket_Communication.Channel component, but it seems a bit hokey to have
to do this.

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

From: Adam Beneschan
Sent: Wednesday, May 21, 2008 11:14 AM

> I actually ran into this while trying to implement both Ada.Text_IO 
> and Ada.Wide_Text_IO, putting all the interesting code in Wide_Text_IO 
> and implementing Text_IO mostly by making calls to Wide_Text_IO.
> (File_Type is limited private in both packages, so code I had gotten 
> working for Ada 95 no longer compiles, for the language-defined 
> functions that return File_Type.)  But I think the problem is more 
> general than that.  I'm not sure how serious it is; the problem can be 
> worked around by making the full view of Warehouse_Connection a
> (nonlimited) record with a Socket_Communication.Channel component, but 
> it seems a bit hokey to have to do this.

I must be asleep---the record would still be limited, but you could at
least write an aggregate in the return statement.  It does seem strange
that you would have to do that.  It seems that if you can say

   type T2 is record
      X : T1;  -- T1 is limited
   end record;

   return T2' (X => Some_Func_Call(...));

you ought to be able to say

   type T2 is new T1;

   return T2 (Some_Func_Call (...));

I don't know whether all type conversions ought to be allowable by 7.5(2.1),
but at the least it should be OK to allow untagged type conversions where
the target type is derived from the operand type.

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

From: Tucker Taft
Sent: Wednesday, May 21, 2008 11:43 AM

If it is declared limited, then that means from the outside, you aren't allowed
to copy it.  The Ada 95 rules implied privacy breakage.

In Ada 2005, function return always involves the creation of a new object, either
by copying an old one (which isn't allowed if it is limited), or by creating a
new object with an aggregate or an extended return statement.

If you want some clients to be able to copy it, then you need to make it
non-limited, or move those clients that want to take advantage of its underlying
non-limitedness into child packages.

On the other hand, you have illustrated a case where you use non-tagged type
derivation, and it might be argued that a type conversion between non-tagged
derived types need not create a new object, presuming there weren't any
rep-clauses applied to the derived type.

On the other other hand, it seems that either these types are very closely
related, in which case it would make sense for the derived type to be declared
in a child package, or they are pretty much unrelated, in which case you could
define the new type by wrapping the old type in a record with only one component,
as you suggested.

Or you could make the types tagged, in which case you can use an extension
aggregate in the return statement.

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

From: Robert A. Duff
Sent: Wednesday, May 21, 2008  4:43 PM

> If it is declared limited, then that means from the outside, you 
> aren't allowed to copy it.  The Ada 95 rules implied privacy breakage.

Really?  How do the Ada 95 rules break privacy?

An alternative rule would be to allow return-by-copy (e.g. return of a global
object) if the type is not a build-in-place type.  That would break privacy,
which is evil.  But the existing Ada 2005 rules break compatibility, which is
even more evil.  I think ARG made the wrong trade-off between breaking privacy
and breaking compatibility in this case.

Experiments with AdaCore's bug database show that almost all of the violations
of the new Ada 2005 rule (return expression must be a function call or
aggregate) in existing Ada 95 code are the full-type-non-limited case.
That is, return-by-reference is quite rare in Ada 95, so disallowing that would
have been much more compatible.  (I suspect some of the return-by-ref cases are
accidental!)

I guess things get more interesting with generic formal limited private types
-- some instances use return-by-ref, and some use return-by-copy!

> In Ada 2005, function return always involves the creation of a new 
> object, either by copying an old one (which isn't allowed if it is 
> limited), or by creating a new object with an aggregate or an extended 
> return statement.

It doesn't have to be extended.  You can say "return F(...)", which is equivalent
to "return R : constant T := F(...);".

P.S. Build-in-place is a very cool feature.  Too bad Ichbiah didn't think of
it in the first place!

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

From: Adam Beneschan
Sent: Wednesday, May 21, 2008  5:01 PM

> > If it is declared limited, then that means from the outside, you 
> > aren't allowed to copy it.  The Ada 95 rules implied privacy 
> > breakage.
> 
> Really?  How do the Ada 95 rules break privacy?

That's an interesting question, since the return-by-reference rules in
Ada 95 are mostly in Dynamic Semantics, and someone (was it you?) pointed
out to me some time ago that the "privacy breaking" thing doesn't apply to
what happens at run-time, i.e. dynamic semantics.
However, a quick textual search of the Ada 95 AARM did find one place where
return-by-reference was referred to in Static Semantics, in 3.10.2, having
to do with the accessibility level of a return object.
Since any detailed analysis of 3.10.2 causes the exception Migraine_Error
to be raised, I haven't tried to construct an example in detail.

But in a case like this:

   package Pak1 is
      type T is limited private;
   private
      type T is ???;
   end Pak1;
   
   with Pak1;
   package Pak2 is
      type T2 is limited private;
   private
      type T2 is new Pak1.T;
   end Pak1;

the rules for determining whether T2 is a return-by-reference type
(RM95 6.5(11-16)) do seem to imply that one needs to look in the private part
of Pak1 to answer the question.  And since it seems possible (thanks to RM95
3.10.2(10)) to create a case where the compiler must determine whether a
function result type is return-by-reference in order to determine whether
a program is legal, I guess that does mean there's a privacy breakage.
That may be the sort of thing Tuck was referring to.

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

From: Tucker Taft
Sent: Wednesday, May 21, 2008 10:36 PM

>> If it is declared limited, then that means from the outside, you 
>> aren't allowed to copy it.  The Ada 95 rules implied privacy 
>> breakage.
> 
> Really?  How do the Ada 95 rules break privacy?

They break privacy because whether or not you can return a local object
of the private type is determined by whether the full type is or is
not a return-by-reference type.

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

From: Randy Brukardt
Sent: Wednesday, June 11, 2008 11:39 PM

> On the other hand, you have illustrated a case where you use 
> non-tagged type derivation, and it might be argued that a type 
> conversion between non-tagged derived types need not create a new 
> object, presuming there weren't any rep-clauses applied to the derived 
> type.

We discussed this issue (allowing a few forms of type conversion for
build-in-place types) here last July. While that discussion was
inconclusive, the discussion about whether to make it an AI brought up
some additional issues which I annotated onto the AC (AC-147). Since
probably no one here read them, I'll attach them here in an effort to
lay this idea to rest:

"It is clear that only a few kinds of type conversions would be safe
in build-in-place contexts. Surely, we couldn't allow anything that
could change representation or discard portions of a type. That suggests
that allowing [only safe] kinds of conversions could be confusing for
the user, as the rules would appear rather arbitrary. Thus, I don't
think this is a good idea."

The previous discussion suggested allowing conversion to a class-wide type;
this one suggests allowing non-tagged derived type that don't have
representation clauses. We could also probably allow conversions between
a null extension and it's parent. And that's it. I can't imagine a
much weirder set of rules, and it seems dubious that they would help
in many real situations (and depending on rep. clauses could break
privacy, as the rep. clauses can be given in the private part) so I
still don't think this is a good idea.

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

From: Gary Dismukes
Sent: Thursday, May 22, 2008 12:26 AM

I agree with your assessment Randy.  I don't think it makes sense to
allow conversions in build-in-place contexts.  The privacy-breaking issue
in particular is a nonstarter, and allowing just the few cases where
there couldn't be a problem doesn't seem worth it.

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

From: Adam Beneschan
Sent: Thursday, June 19, 2008  4:48 PM

I'm not convinced this is the same issue at all.  The example I started
with dealt with a type T2 derived from a limited private type T, and
the problem of writing a function to return a value of type T2. 

This isn't a build-in-place issue at all, because the build-in-place
rule for function results (7.5(8.1)) only applies to explicitly limited
record types (and tasks and protected types), *not* limited private
types (see AARM 7.5(9.a)).  

As for representation clauses: it appears that the language may allow
representation items on types derived from private types, as long as the
private type isn't by-reference, which you have to look at the full view
of the type to determine (13.1(10)).  If this is true, that a
representation item may be allowed on a type derived from a private type,
then there appears to be a privacy breakage in the language rules.
If it's not allowed, though, then the representation clause shouldn't be
an issue for the type conversion problem I asked about.

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

From: Randy Brukardt
Sent: Saturday, June 28, 2008  4:16 PM

...
> This isn't a build-in-place issue at all, because the build-in-place 
> rule for function results (7.5(8.1)) only applies to explicitly 
> limited record types (and tasks and protected types), *not* limited 
> private types (see AARM 7.5(9.a)).

True, but for the purposes of legality rules they're treated like
build-in-place types in order to avoid breaking privacy. After all,
whether or not a limited private type is build-in-place depends solely
on the declaration of the full type. That's why the legality rules
apply to all limited types.

Essentially, these legality rules are "assume-the-worst" when it comes
to limited private types, because otherwise it would be easy for a
client to have an implied dependency on the form of the declaration
of the full type. We don't want clients to become illegal if someone
adds a protected component, for instance.

> As for representation clauses: it appears that the language may allow 
> representation items on types derived from private types, as long as 
> the private type isn't by-reference, which you have to look at the 
> full view of the type to determine (13.1(10)).  If this is true, that 
> a representation item may be allowed on a type derived from a private 
> type, then there appears to be a privacy breakage in the language 
> rules.  If it's not allowed, though, then the representation clause 
> shouldn't be an issue for the type conversion problem I asked about.

Representation items generally don't have any effect on legality rules
(other than for other rep. items). There are a couple of exceptions but
we really don't want to be adding any more, because rep. items generally
can be hidden and thus such dependencies break privacy.

You seem to be implying that this sort of conversion should be allowed
because the full parent type (and derived type) in question don't prevent
it. But that's backwards to the usual Ada model, where the clients can't
be made illegal by maintenance on the parent type. I don't think we want
to be changing that.

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

From: Adam Beneschan
Sent: Wednesday, July 2, 2008  10:20 AM

...
> Representation items generally don't have any effect on legality rules 
> (other than for other rep. items). There are a couple of exceptions 
> but we really don't want to be adding any more, because rep. items 
> generally can be hidden and thus such dependencies break privacy.
> 
> You seem to be implying that this sort of conversion should be allowed 
> because the full parent type (and derived type) in question don't 
> prevent it. But that's backwards to the usual Ada model, where the 
> clients can't be made illegal by maintenance on the parent type. I 
> don't think we want to be changing that.

I'm having trouble following this and understanding why it argues
against any point I was making, and perhaps that's because I didn't make
myself clear enough last time.  Let me try again...

Suppose you write:

    package Pak1 is
        type T1 is limited private;
    private
        type T1 is record
            F1 : Character;
            F2 : Integer;
        end record;
    end Pak1;

    with Pak1;
    package Pak2 is
        type T2 is new Pak1.T1;
        pragma Pack (T2);        -- legal?
    end Pak2;

I'm not clear on whether the noted rep clause is legal or not.  I think
it is---I don't see any rule that makes this illegal.  [Pack is allowed
on composite subtypes, and 3.2(4.1) says that partial views are composite.]

However, if you changed the full type of T1 to make it "tagged", or "limited",
or add another component whose type is a tagged record, then T1 becomes a
by-reference type (even the private view becomes a by-reference view, by
6.2(9)), and the rep clause is illegal by 13.1(10).

So if your concern is that clients shouldn't be made illegal by maintenance
on the parent type, it appears that we *already* have a case that violates
this principle---if the Pack pragma is legal.  

However, if I've missed something, and the Pack clause is actually illegal,
then I don't see how my "proposal" (see below) causes any new privacy
breakage problems.

I realize that I don't think I ever made a specific proposal here; I
pointed out a problem and made some general comments.  Looking back, some
of those comments seemed to suggest that maybe type conversions should be
allowed even where private types aren't involved, and that may have been
incorrect and may have led to some confusion.  So I'm going to try
something more specific:

I propose changing 7.5(2.1), which currently reads:

    In the following contexts, an expression of a limited type is not
    permitted unless it is an aggregate, a function_call, or a
    parenthesized expression or qualified_expression whose operand is
    permitted by this rule:

to

    In the following contexts, an expression of a limited type is not
    permitted unless it is an aggregate, a function_call, or a
    parenthesized expression or qualified_expression whose operand is
    permitted by this rule, or a type_conversion whose operand is
    permitted by this rule, if the type_conversion is between a
    derived type and an ancestor of the derived type, where the
    ancestor is a limited untagged partial view:

or something to that effect.

I've narrowed this down to cases where a type is derived from an untagged
limited private type.  If representation clauses on types derived from
private types are illegal, then I think one of the main objections, that
such a type conversion could require a copy of a limited object due to a
representation change, would not apply.  If it's possible to come up with
a case involving a *chain* of derived types where the type conversion would
still involve a representation change, perhaps "ancestor" could be changed
to "parent" in the above.

Anyway, assuming that rep clauses such as the above are illegal, then
I don't see any privacy breakage concerns with this proposal.

My reasons in support of the proposal:

(1) It eliminates a backward incompatibility---a case where code that
    was legal in Ada 95 is illegal in Ada 2005.

(2) The code that Ada 2005 made illegal actually came up in practice.

(3) Although some workarounds have been mentioned, such as this in my
    original example:

    package Pak1 is
       type T1 is limited private; ...

    package Pak2 is
       type T2 is limited private;
    private
       type T2 is record
          The_Data : Pak1.T1;
       end record; ...

    the original case, where the full view of T2 was a derived type:

    package Pak2 is
       type T2 is limited private;
    private
       type T2 is new Pak1.T1; ...

    is not illegal, and it's possible to implement all of Pak2 in that
    manner.  But later, if it is desired to add a new operation to
    Pak2 that is a function that returns T2, it's only then that the
    Pak2 implementor finds that a derived type won't work, and that he
    therefore needs to redefine T2 and fix all uses of it in the body
    of T2, which is annoying.

It may also be desirable to modify 13.1(10) to prevent rep clauses on types
derived from private types, to eliminate the possible privacy breakage that
comes from making the legality depend on whether the parent type is a
by-reference type (assuming there isn't some other rule somewhere else that
already prevents this).

However, if the privacy breakage in 13.1(10) is considered acceptable, then
I can see how my proposed change to 7.5(2.1) could make things worse from
a privacy breakage standpoint.

Anyway, I hope this is clearer.

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

From: Randy Brukardt
Sent: Wednesday, July 2, 2008  3:59 PM

> I'm not clear on whether the noted rep clause is legal or not.  I 
> think it is---I don't see any rule that makes this illegal.  [Pack is 
> allowed on composite subtypes, and
> 3.2(4.1) says that partial views are composite.]

The ARG cannot agree on much when it comes to pragma Pack (other than
that we don't want to discuss it anymore), so there can be no definitive
answer.

However, attribute clauses for 'Size and 'Alignment would be allowed here
(although a particular implementation could reject them if it wished).

> However, if you changed the full type of T1 to make it "tagged", or 
> "limited", or add another component whose type is a tagged record, 
> then T1 becomes a by-reference type (even the private view becomes a 
> by-reference view, by 6.2(9)), and the rep clause is illegal by 
> 13.1(10).
> 
> So if your concern is that clients shouldn't be made illegal by 
> maintenance on the parent type, it appears that we
> *already* have a case that violates this principle---if the Pack 
> pragma is legal.

I though that is what I said: there are some exceptions for representation
items that break the privacy model, but we surely don't want to be adding
any more.

Remember that the following is legal:

     with Pak1;
     package Pak3 is
         type T3 is new Pak1.T1;
     private
         for T3'Alignment use <some-expr>;
     end Pak3;

and thus the clients of T3 do not even know if there is a representation
item on the type.

...
> I propose changing 7.5(2.1), which currently reads:
> 
>     In the following contexts, an expression of a limited type is not
>     permitted unless it is an aggregate, a function_call, or a
>     parenthesized expression or qualified_expression whose operand is
>     permitted by this rule:
> 
> to
> 
>     In the following contexts, an expression of a limited type is not
>     permitted unless it is an aggregate, a function_call, or a
>     parenthesized expression or qualified_expression whose operand is
>     permitted by this rule, or a type_conversion whose operand is
>     permitted by this rule, if the type_conversion is between a
>     derived type and an ancestor of the derived type, where the
>     ancestor is a limited untagged partial view:
> 
> or something to that effect.

This is exactly the extremely narrow (and complex) exception for type conversions
that pretty much everyone has agreed that we don't want. Especially as it would
have to break privacy in order to include a rule disallowing a type like T3.
Also recall that the full type for T1 could easily be immutably limited, and
then the build-in-place rules would be triggered. A value type conversion is
considered a copying operation, after all, and trying to redefine this case
as a view conversion doesn't seem worth the effort.

> I've narrowed this down to cases where a type is derived from an 
> untagged limited private type.  If representation clauses on types 
> derived from private types are illegal, then I think one of the main 
> objections, that such a type conversion could require a copy of a 
> limited object due to a representation change, would not apply.  If 
> it's possible to come up with a case involving a *chain* of derived 
> types where the type conversion would still involve a representation 
> change, perhaps "ancestor" could be changed to "parent" in the above.
> 
> Anyway, assuming that rep clauses such as the above are illegal, then 
> I don't see any privacy breakage concerns with this proposal.

They're legal, although an implementation could reject them for their own
reasons (I haven't been able to think of any case covered by the Recommended
Level of Support).
 
...
> However, if the privacy breakage in 13.1(10) is considered acceptable, 
> then I can see how my proposed change to 7.5(2.1) could make things 
> worse from a privacy breakage standpoint.

I don't think the privacy breakage is acceptable per-se (if it was up to me,
I would do the entire set of representation items differently), but eliminating
it could be a significant incompatibility and surely would be a disincentive
to using private types. I don't think we want such disincentives. And even
with such a rule change, we'd still need the complex and narrow rule you
proposed, which at best looks like a wart and at worst looks like something
added to appease someone.

Indeed the rule reminds me of the speed limit signs in Illinois. When you reach
the Illinois border, you first see a large "Speed Limit 65" sign. But then
shortly afterwards you see "Speed Limit 55 <many lines of tiny text>".
There is never enough time to read the tiny text as you drive by at highway
speeds. It starts out "Trucks over nnn GW...", but the last few lines could
hide almost anything. I always tell people that the last time is ", and green
cars on Tuesdays." (which is relevant because I now drive a dark green
car.) I doubt that most drivers have actually read the whole list, meaning
that they have no way to know if it applies to them. I don't think we need
rules like that in Ada.

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


Questions? Ask the ACAA Technical Agent